feat: Add assembly action handling and UI components

- Implemented `useAssemblyHandler` to manage assembly actions for humans.
- Enhanced `useHumanActions` to include assembly action handling.
- Updated `HumanInstance` to support assembly processes and animations.
- Modified `HumanUi` to allow for assembly point configuration and rotation.
- Created `AssemblyAction` component for setting process time and material swap options.
- Updated simulation types to include assembly action properties.
- Adjusted existing action handlers to accommodate assembly actions alongside worker actions.
- Refactored `MaterialAnimator` and `VehicleAnimator` to manage attachment states and visibility based on load.
- Updated product store types to include human point actions.
This commit is contained in:
2025-07-07 15:00:16 +05:30
parent b7f29bf5db
commit 74094aee9f
20 changed files with 592 additions and 859 deletions

View File

@@ -1,62 +0,0 @@
import * as THREE from 'three';
import * as Types from "../../../../types/world/worldTypes";
import * as CONSTANTS from "../../../../types/world/worldConstants";
import texturePath from "../../../../assets/textures/floor/white1.png";
import texturePathDark from "../../../../assets/textures/floor/black.png";
// Cache for materials
const materialCache = new Map<string, THREE.Material>();
export default function addFloorToScene(
shape: THREE.Shape,
layer: number,
floorGroup: Types.RefGroup,
userData: any,
) {
const savedTheme: string | null = localStorage.getItem('theme');
const textureLoader = new THREE.TextureLoader();
const textureScale = CONSTANTS.floorConfig.textureScale;
const materialKey = `floorMaterial_${textureScale}`;
let material: THREE.Material;
if (materialCache.has(materialKey)) {
material = materialCache.get(materialKey) as THREE.Material;
} else {
const floorTexture = textureLoader.load(savedTheme === "dark" ? texturePathDark : texturePath);
// const floorTexture = textureLoader.load(texturePath);
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set(textureScale, textureScale);
floorTexture.colorSpace = THREE.SRGBColorSpace;
material = new THREE.MeshStandardMaterial({
map: floorTexture,
side: THREE.DoubleSide,
});
materialCache.set(materialKey, material);
}
const extrudeSettings = {
depth: CONSTANTS.floorConfig.height,
bevelEnabled: false,
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const mesh = new THREE.Mesh(geometry, material);
mesh.receiveShadow = true;
mesh.position.y = (layer) * CONSTANTS.wallConfig.height;
mesh.rotateX(Math.PI / 2);
mesh.name = `Floor_Layer_${layer}`;
// Store UUIDs for debugging or future processing
mesh.userData.uuids = userData;
floorGroup.current.add(mesh);
}

View File

@@ -1,190 +0,0 @@
import * as THREE from 'three';
import * as turf from '@turf/turf';
import * as CONSTANTS from '../../../../types/world/worldConstants';
import * as Types from "../../../../types/world/worldTypes";
// temp
import blueFloorImage from "../../../../assets/textures/floor/blue.png"
function loadOnlyFloors(
floorGroup: Types.RefGroup,
linesByLayer: any,
layer: any,
): void {
////////// Creating polygon floor based on the onlyFloorlines.current which does not add roof to it, The lines are still stored in Lines.current as well //////////
let floorsInLayer = linesByLayer[layer];
floorsInLayer = floorsInLayer.filter((line: any) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.floorName);
const floorResult = floorsInLayer.map((pair: [THREE.Vector3, string, number, string][]) =>
pair.map((point) => ({
position: [point[0].x, point[0].z],
uuid: point[1]
}))
);
const FloorLineFeatures = floorResult.map((line: any) => turf.lineString(line.map((p: any) => p.position)));
function identifyPolygonsAndConnectedLines(FloorLineFeatures: any) {
const floorpolygons = [];
const connectedLines = [];
const unprocessedLines = [...FloorLineFeatures]; // Copy the features
while (unprocessedLines.length > 0) {
const currentLine = unprocessedLines.pop();
const coordinates = currentLine.geometry.coordinates;
// Check if the line is closed (forms a polygon)
if (
coordinates[0][0] === coordinates[coordinates.length - 1][0] &&
coordinates[0][1] === coordinates[coordinates.length - 1][1]
) {
floorpolygons.push(turf.polygon([coordinates])); // Add as a polygon
continue;
}
// Check if the line connects to another line
let connected = false;
for (let i = unprocessedLines.length - 1; i >= 0; i--) {
const otherCoordinates = unprocessedLines[i].geometry.coordinates;
// Check if lines share a start or end point
if (
coordinates[0][0] === otherCoordinates[otherCoordinates.length - 1][0] &&
coordinates[0][1] === otherCoordinates[otherCoordinates.length - 1][1]
) {
// Merge lines
const mergedCoordinates = [...otherCoordinates, ...coordinates.slice(1)];
unprocessedLines[i] = turf.lineString(mergedCoordinates);
connected = true;
break;
} else if (
coordinates[coordinates.length - 1][0] === otherCoordinates[0][0] &&
coordinates[coordinates.length - 1][1] === otherCoordinates[0][1]
) {
// Merge lines
const mergedCoordinates = [...coordinates, ...otherCoordinates.slice(1)];
unprocessedLines[i] = turf.lineString(mergedCoordinates);
connected = true;
break;
}
}
if (!connected) {
connectedLines.push(currentLine); // Add unconnected line as-is
}
}
return { floorpolygons, connectedLines };
}
const { floorpolygons, connectedLines } = identifyPolygonsAndConnectedLines(FloorLineFeatures);
function convertConnectedLinesToPolygons(connectedLines: any) {
return connectedLines.map((line: any) => {
const coordinates = line.geometry.coordinates;
// If the line has more than two points, close the polygon
if (coordinates.length > 2) {
const firstPoint = coordinates[0];
const lastPoint = coordinates[coordinates.length - 1];
// Check if already closed; if not, close it
if (firstPoint[0] !== lastPoint[0] || firstPoint[1] !== lastPoint[1]) {
coordinates.push(firstPoint);
}
// Convert the closed line into a polygon
return turf.polygon([coordinates]);
}
// If not enough points for a polygon, return the line unchanged
return line;
});
}
const convertedConnectedPolygons = convertConnectedLinesToPolygons(connectedLines);
if (convertedConnectedPolygons.length > 0) {
const validPolygons = convertedConnectedPolygons.filter(
(polygon: any) => polygon.geometry?.type === "Polygon"
);
if (validPolygons.length > 0) {
floorpolygons.push(...validPolygons);
}
}
function convertPolygonsToOriginalFormat(floorpolygons: any, originalLines: [THREE.Vector3, string, number, string][][]) {
return floorpolygons.map((polygon: any) => {
const coordinates = polygon.geometry.coordinates[0]; // Extract the coordinates array (assume it's a single polygon)
// Map each coordinate back to its original structure
const mappedPoints = coordinates.map((coord: [number, number]) => {
const [x, z] = coord;
// Find the original point matching this coordinate
const originalPoint = originalLines.flat().find(([point]) => point.x === x && point.z === z);
if (!originalPoint) {
throw new Error(`Original point for coordinate [${x}, ${z}] not found.`);
}
return originalPoint;
});
// Create pairs of consecutive points
const pairs: typeof originalLines = [];
for (let i = 0; i < mappedPoints.length - 1; i++) {
pairs.push([mappedPoints[i], mappedPoints[i + 1]]);
}
return pairs;
});
}
const convertedFloorPolygons: Types.OnlyFloorLines = convertPolygonsToOriginalFormat(floorpolygons, floorsInLayer);
convertedFloorPolygons.forEach((floor) => {
const points: THREE.Vector3[] = [];
floor.forEach((lineSegment) => {
const startPoint = lineSegment[0][0];
points.push(new THREE.Vector3(startPoint.x, startPoint.y, startPoint.z));
});
const lastLine = floor[floor.length - 1];
const endPoint = lastLine[1][0];
points.push(new THREE.Vector3(endPoint.x, endPoint.y, endPoint.z));
const shape = new THREE.Shape();
shape.moveTo(points[0].x, points[0].z);
points.forEach(point => shape.lineTo(point.x, point.z));
shape.closePath();
const extrudeSettings = {
depth: 0.3,
bevelEnabled: false
};
const texture = new THREE.TextureLoader().load(blueFloorImage);
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.colorSpace = THREE.SRGBColorSpace;
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.floorConfig.defaultColor, side: THREE.DoubleSide, map: texture });
const mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.position.y = (floor[0][0][2] - 0.99) * CONSTANTS.wallConfig.height;
mesh.rotateX(Math.PI / 2);
mesh.name = `Only_Floor_Line_${floor[0][0][2]}`;
mesh.userData = floor;
floorGroup?.current?.add(mesh);
});
}
export default loadOnlyFloors;

View File

@@ -1,291 +0,0 @@
// import { useEffect } from "react";
// import {
// useObjectPosition,
// useObjectRotation,
// useSelectedWallItem,
// useSocketStore,
// useWallItems,
// useSelectedItem,
// useToolMode,
// } from "../../../store/builder/store";
// import { Csg } from "../csg/csg";
// import * as Types from "../../../types/world/worldTypes";
// import * as CONSTANTS from "../../../types/world/worldConstants";
// import * as THREE from "three";
// import { useThree } from "@react-three/fiber";
// import handleMeshMissed from "../eventFunctions/handleMeshMissed";
// import DeleteWallItems from "../geomentries/walls/deleteWallItems";
// import loadInitialWallItems from "../IntialLoad/loadInitialWallItems";
// import AddWallItems from "../geomentries/walls/addWallItems";
// import useModuleStore from "../../../store/useModuleStore";
// import { useParams } from "react-router-dom";
// import { getUserData } from "../../../functions/getUserData";
// import { useVersionContext } from "../version/versionContext";
// const WallItemsGroup = ({
// currentWallItem,
// hoveredDeletableWallItem,
// selectedItemsIndex,
// setSelectedItemsIndex,
// CSGGroup,
// }: any) => {
// const state = useThree();
// const { socket } = useSocketStore();
// const { pointer, camera, raycaster } = state;
// const { toolMode } = useToolMode();
// const { wallItems, setWallItems } = useWallItems();
// const { setObjectPosition } = useObjectPosition();
// const { setObjectRotation } = useObjectRotation();
// const { setSelectedWallItem } = useSelectedWallItem();
// const { activeModule } = useModuleStore();
// const { selectedItem } = useSelectedItem();
// const { selectedVersionStore } = useVersionContext();
// const { selectedVersion } = selectedVersionStore();
// const { projectId } = useParams();
// const { userId, organization } = getUserData();
// useEffect(() => {
// // Load Wall Items from the backend
// if (!projectId || !selectedVersion) return;
// loadInitialWallItems(setWallItems, projectId, selectedVersion?.versionId);
// }, [selectedVersion?.versionId]);
// ////////// Update the Position value changes in the selected item //////////
// useEffect(() => {
// const canvasElement = state.gl.domElement;
// function handlePointerMove(e: any) {
// if (selectedItemsIndex !== null && toolMode === 'cursor' && e.buttons === 1) {
// const Raycaster = state.raycaster;
// const intersects = Raycaster.intersectObjects(CSGGroup.current?.children[0].children!, true);
// const Object = intersects.find((child) => child.object.name.includes("WallRaycastReference"));
// if (Object) {
// (state.controls as any)!.enabled = false;
// setWallItems((prevItems: any) => {
// const updatedItems = [...prevItems];
// let position: [number, number, number] = [0, 0, 0];
// if (updatedItems[selectedItemsIndex].type === "fixed-move") {
// position = [
// Object!.point.x,
// Math.floor(Object!.point.y / CONSTANTS.wallConfig.height) *
// CONSTANTS.wallConfig.height,
// Object!.point.z,
// ];
// } else if (updatedItems[selectedItemsIndex].type === "free-move") {
// position = [Object!.point.x, Object!.point.y, Object!.point.z];
// }
// requestAnimationFrame(() => {
// setObjectPosition(new THREE.Vector3(...position));
// setObjectRotation({
// x: THREE.MathUtils.radToDeg(Object!.object.rotation.x),
// y: THREE.MathUtils.radToDeg(Object!.object.rotation.y),
// z: THREE.MathUtils.radToDeg(Object!.object.rotation.z),
// });
// });
// updatedItems[selectedItemsIndex] = {
// ...updatedItems[selectedItemsIndex],
// position: position,
// quaternion: Object!.object.quaternion.clone() as Types.QuaternionType,
// };
// return updatedItems;
// });
// }
// }
// }
// async function handlePointerUp() {
// const Raycaster = state.raycaster;
// const intersects = Raycaster.intersectObjects(
// CSGGroup.current?.children[0].children!,
// true
// );
// const Object = intersects.find((child) =>
// child.object.name.includes("WallRaycastReference")
// );
// if (Object) {
// if (selectedItemsIndex !== null) {
// let currentItem: any = null;
// setWallItems((prevItems: any) => {
// const updatedItems = [...prevItems];
// const WallItemsForStorage = updatedItems.map((item) => {
// const { model, ...rest } = item;
// return {
// ...rest,
// modelUuid: model?.uuid,
// };
// });
// currentItem = updatedItems[selectedItemsIndex];
// localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
// return updatedItems;
// });
// setTimeout(async () => {
// //REST
// // await setWallItem(
// // organization,
// // currentItem?.model?.uuid,
// // currentItem.modelName,
// // currentItem.assetId,
// // currentItem.type!,
// // currentItem.csgposition!,
// // currentItem.csgscale!,
// // currentItem.position,
// // currentItem.quaternion,
// // currentItem.scale!,
// // )
// //SOCKET
// const data = {
// organization,
// modelUuid: currentItem.model?.uuid!,
// assetId: currentItem.assetId,
// modelName: currentItem.modelName!,
// type: currentItem.type!,
// csgposition: currentItem.csgposition!,
// csgscale: currentItem.csgscale!,
// position: currentItem.position!,
// quaternion: currentItem.quaternion,
// scale: currentItem.scale!,
// socketId: socket.id,
// versionId: selectedVersion?.versionId || '',
// projectId,
// userId
// };
// // console.log('data: ', data);
// socket.emit("v1:wallItems:set", data);
// }, 0);
// (state.controls as any)!.enabled = true;
// }
// }
// }
// canvasElement.addEventListener("pointermove", handlePointerMove);
// canvasElement.addEventListener("pointerup", handlePointerUp);
// return () => {
// canvasElement.removeEventListener("pointermove", handlePointerMove);
// canvasElement.removeEventListener("pointerup", handlePointerUp);
// };
// }, [selectedItemsIndex, selectedVersion?.versionId]);
// useEffect(() => {
// const canvasElement = state.gl.domElement;
// let drag = false;
// let isLeftMouseDown = false;
// const onMouseDown = (evt: any) => {
// if (evt.button === 0) {
// isLeftMouseDown = true;
// drag = false;
// }
// };
// const onMouseUp = (evt: any) => {
// if (evt.button === 0) {
// isLeftMouseDown = false;
// if (!drag && toolMode === '3D-Delete' && activeModule === "builder") {
// DeleteWallItems(
// hoveredDeletableWallItem,
// setWallItems,
// wallItems,
// socket,
// projectId,
// selectedVersion?.versionId || '',
// );
// }
// }
// };
// const onMouseMove = () => {
// if (isLeftMouseDown) {
// drag = true;
// }
// };
// const onDrop = (event: any) => {
// if (selectedItem.category !== 'Fenestration') return;
// pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
// pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
// raycaster.setFromCamera(pointer, camera);
// if (selectedItem.id && selectedVersion && projectId) {
// if (selectedItem.subCategory) {
// AddWallItems(
// selectedItem,
// raycaster,
// CSGGroup,
// setWallItems,
// socket,
// projectId,
// selectedVersion?.versionId || '',
// );
// }
// event.preventDefault();
// }
// };
// const onDragOver = (event: any) => {
// event.preventDefault();
// };
// canvasElement.addEventListener("mousedown", onMouseDown);
// canvasElement.addEventListener("mouseup", onMouseUp);
// canvasElement.addEventListener("mousemove", onMouseMove);
// canvasElement.addEventListener("drop", onDrop);
// canvasElement.addEventListener("dragover", onDragOver);
// return () => {
// canvasElement.removeEventListener("mousedown", onMouseDown);
// canvasElement.removeEventListener("mouseup", onMouseUp);
// canvasElement.removeEventListener("mousemove", onMouseMove);
// canvasElement.removeEventListener("drop", onDrop);
// canvasElement.removeEventListener("dragover", onDragOver);
// };
// }, [toolMode, wallItems, selectedItem, camera, selectedVersion?.versionId]);
// useEffect(() => {
// if (toolMode && activeModule === "builder") {
// handleMeshMissed(
// currentWallItem,
// setSelectedWallItem,
// setSelectedItemsIndex
// );
// setSelectedWallItem(null);
// setSelectedItemsIndex(null);
// }
// }, [toolMode]);
// return (
// <>
// {wallItems.map((item: Types.WallItem, index: number) => (
// <group
// key={index}
// position={item.position}
// quaternion={item.quaternion}
// scale={item.scale}
// >
// <Csg
// position={item.csgposition!}
// scale={item.csgscale!}
// model={item.model!}
// hoveredDeletableWallItem={hoveredDeletableWallItem}
// />
// </group>
// ))}
// </>
// );
// };
// export default WallItemsGroup;

View File

@@ -1,74 +0,0 @@
// import { Geometry } from "@react-three/csg";
// import {
// useSelectedWallItem,
// useToggleView,
// useToolMode,
// useWallItems,
// useWalls,
// } from "../../../store/builder/store";
// import handleMeshDown from "../eventFunctions/handleMeshDown";
// import handleMeshMissed from "../eventFunctions/handleMeshMissed";
// import WallsMesh from "./wallsMesh";
// import WallItemsGroup from "./wallItemsGroup";
// const WallsAndWallItems = ({
// CSGGroup,
// setSelectedItemsIndex,
// selectedItemsIndex,
// currentWallItem,
// csg,
// lines,
// hoveredDeletableWallItem,
// }: any) => {
// const { walls } = useWalls();
// const { wallItems } = useWallItems();
// const { toggleView } = useToggleView();
// const { toolMode } = useToolMode();
// const { setSelectedWallItem } = useSelectedWallItem();
// return (
// <mesh
// ref={CSGGroup as any}
// name="Walls"
// key={walls.length}
// receiveShadow
// visible={!toggleView}
// onClick={(event) => {
// if (toolMode === "cursor") {
// handleMeshDown(
// event,
// currentWallItem,
// setSelectedWallItem,
// setSelectedItemsIndex,
// wallItems,
// toggleView
// );
// }
// }}
// onPointerMissed={() => {
// if (toolMode === "cursor") {
// handleMeshMissed(
// currentWallItem,
// setSelectedWallItem,
// setSelectedItemsIndex
// );
// setSelectedWallItem(null);
// setSelectedItemsIndex(null);
// }
// }}
// >
// <Geometry ref={csg as any} computeVertexNormals useGroups>
// <WallsMesh lines={lines} />
// <WallItemsGroup
// currentWallItem={currentWallItem}
// hoveredDeletableWallItem={hoveredDeletableWallItem}
// selectedItemsIndex={selectedItemsIndex}
// setSelectedItemsIndex={setSelectedItemsIndex}
// CSGGroup={CSGGroup}
// />
// </Geometry>
// </mesh>
// );
// };
// export default WallsAndWallItems;

View File

@@ -1,82 +0,0 @@
// import * as THREE from "three";
// import * as Types from "../../../types/world/worldTypes";
// import * as CONSTANTS from "../../../types/world/worldConstants";
// import { Base } from "@react-three/csg";
// import { MeshDiscardMaterial } from "@react-three/drei";
// import { useUpdateScene, useWalls } from "../../../store/builder/store";
// import React, { useEffect } from "react";
// import { getLines } from "../../../services/factoryBuilder/lines/getLinesApi";
// import objectLinesToArray from "../geomentries/lines/lineConvertions/objectLinesToArray";
// import loadWalls from "../geomentries/walls/loadWalls";
// import texturePath from "../../../assets/textures/floor/wall-tex.png";
// import { useParams } from "react-router-dom";
// import { getUserData } from "../../../functions/getUserData";
// import { useVersionContext } from "../version/versionContext";
// const WallsMeshComponent = ({ lines }: any) => {
// const { walls, setWalls } = useWalls();
// const { updateScene, setUpdateScene } = useUpdateScene();
// const { projectId } = useParams();
// const { selectedVersionStore } = useVersionContext();
// const { selectedVersion } = selectedVersionStore();
// const { organization } = getUserData();
// useEffect(() => {
// if (updateScene) {
// if (!selectedVersion) {
// setUpdateScene(false);
// return;
// };
// getLines(organization, projectId, selectedVersion?.versionId || '').then((data) => {
// const Lines: Types.Lines = objectLinesToArray(data);
// localStorage.setItem("Lines", JSON.stringify(Lines));
// if (Lines) {
// loadWalls(lines, setWalls);
// }
// });
// setUpdateScene(false);
// }
// }, [updateScene, selectedVersion?.versionId]);
// const textureLoader = new THREE.TextureLoader();
// const wallTexture = textureLoader.load(texturePath);
// wallTexture.wrapS = wallTexture.wrapT = THREE.RepeatWrapping;
// wallTexture.repeat.set(0.1, 0.1);
// wallTexture.colorSpace = THREE.SRGBColorSpace;
// return (
// <>
// {walls.map((wall: Types.Wall, index: number) => (
// <mesh key={index} renderOrder={1}>
// <Base
// name={`Wall${index + 1}`}
// geometry={wall[0]}
// rotation={wall[1]}
// position={wall[2]}
// userData={{ WallType: wall[3], Layer: wall[4] }}
// >
// <meshStandardMaterial
// side={THREE.DoubleSide}
// color={CONSTANTS.wallConfig.defaultColor}
// map={wallTexture}
// />
// </Base>
// <mesh
// castShadow
// geometry={wall[0]}
// rotation={wall[1]}
// position={wall[2]}
// name={`WallRaycastReference_${index + 1}`}
// >
// <MeshDiscardMaterial />
// </mesh>
// </mesh>
// ))}
// </>
// );
// };
// const WallsMesh = React.memo(WallsMeshComponent);
// export default WallsMesh;