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

@@ -0,0 +1,44 @@
import React from "react";
import InputRange from "../../../../../ui/inputs/InputRange";
import SwapAction from "./SwapAction";
interface AssemblyActionProps {
processTime: {
value: number;
min: number;
max: number;
disabled?: boolean,
onChange: (value: number) => void;
};
swapOptions: string[];
swapDefaultOption: string;
onSwapSelect: (value: string) => void;
}
const AssemblyAction: React.FC<AssemblyActionProps> = ({
processTime,
swapOptions,
swapDefaultOption,
onSwapSelect,
}) => {
return (
<>
<InputRange
label="Process Time"
value={processTime.value}
min={processTime.min}
max={processTime.max}
disabled={processTime.disabled}
onClick={() => { }}
onChange={processTime.onChange}
/>
<SwapAction
options={swapOptions}
defaultOption={swapDefaultOption}
onSelect={onSwapSelect}
/>
</>
);
};
export default AssemblyAction;

View File

@@ -5,18 +5,21 @@ import RenameInput from "../../../../../ui/inputs/RenameInput";
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
import Trigger from "../trigger/Trigger";
import ActionsList from "../components/ActionsList";
import { useSelectedEventData, useSelectedAction, useSelectedAnimation } from "../../../../../../store/simulation/useSimulationStore";
import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
import { useVersionContext } from "../../../../../../modules/builder/version/versionContext";
import { useSceneContext } from "../../../../../../modules/scene/sceneContext";
import { useParams } from "react-router-dom";
import WorkerAction from "../actions/workerAction";
import AssemblyAction from "../actions/assemblyAction";
function HumanMechanics() {
const [activeOption, setActiveOption] = useState<"worker">("worker");
const [activeOption, setActiveOption] = useState<"worker" | "assembly">("worker");
const [speed, setSpeed] = useState("0.5");
const [loadCapacity, setLoadCapacity] = useState("1");
const [processTime, setProcessTime] = useState(10);
const [swappedMaterial, setSwappedMaterial] = useState("Default material");
const [currentAction, setCurrentAction] = useState<HumanAction | undefined>();
const [selectedPointData, setSelectedPointData] = useState<HumanPointSchema | undefined>();
const { selectedEventData } = useSelectedEventData();
@@ -48,6 +51,8 @@ function HumanMechanics() {
) as HumanEventSchema | undefined
)?.speed?.toString() || "1");
setLoadCapacity(point.action.loadCapacity.toString());
setProcessTime(point.action.processTime || 10);
setSwappedMaterial(point.action.swapMaterial || "Default material");
}
} else {
clearSelectedAction();
@@ -158,6 +163,52 @@ function HumanMechanics() {
setLoadCapacity(value);
};
const handleProcessTimeChange = (value: number) => {
if (!currentAction || !selectedPointData || !selectedAction.actionId) return;
const updatedAction = { ...currentAction };
updatedAction.processTime = value
const updatedPoint = { ...selectedPointData, action: updatedAction };
const event = updateAction(
selectedProduct.productUuid,
selectedAction.actionId,
updatedAction
);
if (event) {
updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event);
}
setCurrentAction(updatedAction);
setSelectedPointData(updatedPoint);
setProcessTime(value);
};
const handleMaterialChange = (value: string) => {
if (!currentAction || !selectedPointData || !selectedAction.actionId) return;
const updatedAction = { ...currentAction };
updatedAction.swapMaterial = value
const updatedPoint = { ...selectedPointData, action: updatedAction };
const event = updateAction(
selectedProduct.productUuid,
selectedAction.actionId,
updatedAction
);
if (event) {
updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event);
}
setCurrentAction(updatedAction);
setSelectedPointData(updatedPoint);
setSwappedMaterial(value);
};
const handleClearPoints = () => {
if (!currentAction || !selectedPointData || !selectedAction.actionId) return;
@@ -262,23 +313,38 @@ function HumanMechanics() {
<LabledDropdown
label="Action Type"
defaultOption={activeOption}
options={["worker"]}
options={["worker", "assembly"]}
onSelect={handleSelectActionType}
disabled={true}
/>
</div>
<WorkerAction
loadCapacity={{
value: loadCapacity,
min: 1,
max: 5,
step: 1,
defaultValue: "1",
disabled: true,
onChange: handleLoadCapacityChange,
}}
clearPoints={handleClearPoints}
/>
{currentAction.actionType === 'worker' &&
<WorkerAction
loadCapacity={{
value: loadCapacity,
min: 1,
max: 5,
step: 1,
defaultValue: "10",
disabled: true,
onChange: handleLoadCapacityChange,
}}
clearPoints={handleClearPoints}
/>
}
{currentAction.actionType === 'assembly' &&
<AssemblyAction
processTime={{
value: processTime,
min: 1,
max: 60,
onChange: handleProcessTimeChange,
}}
swapOptions={["Default material", "Material 1", "Material 2", "Material 3"]}
swapDefaultOption={swappedMaterial}
onSwapSelect={handleMaterialChange}
/>
}
<div className="tirgger">
<Trigger selectedPointData={selectedPointData as any} type="Human" />
</div>

View File

@@ -80,7 +80,6 @@ const InputRange: React.FC<InputToggleProps> = ({
disabled={disabled}
onKeyUp={(e) => {
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
console.log("e.key: ", e.key);
handlekey(e);
}
}}

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;

View File

@@ -0,0 +1,36 @@
import { useCallback } from "react";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
export function useAssemblyHandler() {
const { materialStore, humanStore, productStore } = useSceneContext();
const { getMaterialById } = materialStore();
const { getModelUuidByActionUuid } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { incrementHumanLoad, addCurrentMaterial } = humanStore();
const assemblyLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
}
const handleAssembly = useCallback((action: HumanAction, materialId?: string) => {
if (!action || action.actionType !== 'assembly' || !materialId) return;
const material = getMaterialById(materialId);
if (!material) return;
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
if (!modelUuid) return;
incrementHumanLoad(modelUuid, 1);
addCurrentMaterial(modelUuid, material.materialType, material.materialId);
assemblyLogStatus(material.materialName, `performing assembly action`);
}, [getMaterialById]);
return {
handleAssembly,
};
}

View File

@@ -1,13 +1,19 @@
import { useEffect, useCallback } from 'react';
import { useWorkerHandler } from './actionHandler/useWorkerHandler';
import { useAssemblyHandler } from './actionHandler/useAssemblyHandler';
export function useHumanActions() {
const { handleWorker } = useWorkerHandler();
const { handleAssembly } = useAssemblyHandler();
const handleWorkerAction = useCallback((action: HumanAction, materialId: string) => {
handleWorker(action, materialId);
}, [handleWorker]);
const handleAssemblyAction = useCallback((action: HumanAction, materialId: string) => {
handleAssembly(action, materialId);
}, [handleAssembly]);
const handleHumanAction = useCallback((action: HumanAction, materialId: string) => {
if (!action) return;
@@ -15,10 +21,13 @@ export function useHumanActions() {
case 'worker':
handleWorkerAction(action, materialId);
break;
case 'assembly':
handleAssemblyAction(action, materialId);
break;
default:
console.warn(`Unknown Human action type: ${action.actionType}`);
}
}, [handleWorkerAction]);
}, [handleWorkerAction, handleAssemblyAction]);
const cleanup = useCallback(() => {
}, []);

View File

@@ -39,7 +39,7 @@ export function useActionHandler() {
case 'store': case 'retrieve':
handleStorageAction(action as StorageAction, materialId as string);
break;
case 'worker':
case 'worker': case 'assembly':
handleHumanAction(action as HumanAction, materialId as string);
break;
default:

View File

@@ -6,6 +6,7 @@ import { MaterialModel } from '../../../materials/instances/material/materialMod
const MaterialAnimator = ({ human }: { human: HumanStatus }) => {
const meshRef = useRef<any>(null!);
const [hasLoad, setHasLoad] = useState(false);
const [isAttached, setIsAttached] = useState(false);
const { scene } = useThree();
useEffect(() => {
@@ -13,32 +14,45 @@ const MaterialAnimator = ({ human }: { human: HumanStatus }) => {
}, [human.currentLoad]);
useEffect(() => {
if (!hasLoad || !meshRef.current) return;
if (!hasLoad || !meshRef.current) {
setIsAttached(false);
return;
}
const humanModel = scene.getObjectByProperty("uuid", human.modelUuid) as THREE.Object3D;
if (!humanModel) return;
meshRef.current.visible = false;
const bone = humanModel.getObjectByName('PlaceObjectRefBone') as THREE.Bone;
if (bone) {
if (meshRef.current.parent) {
meshRef.current.parent.remove(meshRef.current);
}
bone.add(meshRef.current);
meshRef.current.position.set(0, 0, 0);
meshRef.current.rotation.set(0, 0, 0);
meshRef.current.scale.set(1, 1, 1);
meshRef.current.visible = true;
setIsAttached(true);
}
}, [hasLoad, human.modelUuid]);
}, [hasLoad, human.modelUuid, scene]);
return (
<>
{hasLoad && human.currentMaterials.length > 0 && (
{hasLoad && human.point.action.actionType === 'worker' && human.currentMaterials.length > 0 && (
<MaterialModel
matRef={meshRef}
materialId={human.currentMaterials[0].materialId || ''}
materialType={human.currentMaterials[0].materialType || 'Default material'}
visible={isAttached}
/>
)}
</>
);
};
export default MaterialAnimator;
export default MaterialAnimator;

View File

@@ -16,7 +16,7 @@ function HumanInstance({ human }: { human: HumanStatus }) {
const { isPlaying } = usePlayButtonStore();
const { scene } = useThree();
const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext();
const { removeMaterial, setEndTime } = materialStore();
const { removeMaterial, setEndTime, setMaterial } = materialStore();
const { getStorageUnitById } = storageUnitStore();
const { getArmBotById } = armBotStore();
const { getConveyorById } = conveyorStore();
@@ -41,6 +41,13 @@ function HumanInstance({ human }: { human: HumanStatus }) {
const previousTimeRef = useRef<number | null>(null);
const animationFrameIdRef = useRef<number | null>(null);
const humanAsset = getAssetById(human.modelUuid);
const processStartTimeRef = useRef<number | null>(null);
const processTimeRef = useRef<number>(0);
const processAnimationIdRef = useRef<number | null>(null);
const accumulatedPausedTimeRef = useRef<number>(0);
const lastPauseTimeRef = useRef<number | null>(null);
const hasLoggedHalfway = useRef(false);
const hasLoggedCompleted = useRef(false);
useEffect(() => {
isPausedRef.current = isPaused;
@@ -94,6 +101,16 @@ function HumanInstance({ human }: { human: HumanStatus }) {
cancelAnimationFrame(animationFrameIdRef.current)
animationFrameIdRef.current = null
}
if (processAnimationIdRef.current) {
cancelAnimationFrame(processAnimationIdRef.current);
processAnimationIdRef.current = null;
}
processStartTimeRef.current = null;
processTimeRef.current = 0;
accumulatedPausedTimeRef.current = 0;
lastPauseTimeRef.current = null;
hasLoggedHalfway.current = false;
hasLoggedCompleted.current = false;
const object = scene.getObjectByProperty('uuid', human.modelUuid);
if (object && human) {
object.position.set(human.position[0], human.position[1], human.position[2]);
@@ -103,7 +120,103 @@ function HumanInstance({ human }: { human: HumanStatus }) {
useEffect(() => {
if (isPlaying) {
if (!human.point.action.pickUpPoint || !human.point.action.dropPoint) return;
if (!human.point.action.assemblyPoint || human.point.action.actionType === 'worker') return;
if (!human.isActive && human.state === 'idle' && currentPhase === 'init') {
setHumanState(human.modelUuid, 'idle');
setCurrentPhase('waiting');
setHumanPicking(human.modelUuid, false);
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Human is waiting for material in assembly');
} else if (!human.isActive && human.state === 'idle' && currentPhase === 'waiting') {
if (human.currentMaterials.length > 0 && humanAsset && humanAsset.animationState?.current !== 'working_standing') {
setCurrentAnimation(human.modelUuid, 'working_standing', true, true, false);
setHumanState(human.modelUuid, 'running');
setCurrentPhase('assembling');
setHumanPicking(human.modelUuid, true);
setHumanActive(human.modelUuid, true);
processStartTimeRef.current = performance.now();
processTimeRef.current = human.point.action.processTime || 0;
accumulatedPausedTimeRef.current = 0;
lastPauseTimeRef.current = null;
hasLoggedHalfway.current = false;
hasLoggedCompleted.current = false;
if (!processAnimationIdRef.current) {
processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess);
}
}
} else if (human.isActive && human.state === 'running' && human.currentMaterials.length > 0 && humanAsset && humanAsset.animationState?.current === 'working_standing' && humanAsset.animationState?.isCompleted) {
if (human.point.action.assemblyPoint && currentPhase === 'assembling') {
setHumanState(human.modelUuid, 'idle');
setCurrentPhase('waiting');
setHumanPicking(human.modelUuid, false);
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Human is waiting for material in assembly');
decrementHumanLoad(human.modelUuid, 1);
const material = removeLastMaterial(human.modelUuid);
if (material) {
triggerPointActions(human.point.action, material.materialId);
}
}
}
} else {
reset()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [human, currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]);
const trackAssemblyProcess = useCallback(() => {
const now = performance.now();
if (!processStartTimeRef.current || !human.point.action.processTime) {
return;
}
if (isPausedRef.current) {
if (!lastPauseTimeRef.current) {
lastPauseTimeRef.current = now;
}
processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess);
return;
} else if (lastPauseTimeRef.current) {
accumulatedPausedTimeRef.current += now - lastPauseTimeRef.current;
lastPauseTimeRef.current = null;
}
const elapsed = (now - processStartTimeRef.current - accumulatedPausedTimeRef.current) * isSpeedRef.current;
const totalProcessTimeMs = human.point.action.processTime * 1000;
if (elapsed >= totalProcessTimeMs / 2 && !hasLoggedHalfway.current) {
hasLoggedHalfway.current = true;
if (human.currentMaterials.length > 0) {
setMaterial(human.currentMaterials[0].materialId, human.point.action.swapMaterial || 'Default Material');
}
humanStatus(human.modelUuid, `🟡 Human ${human.modelUuid} reached halfway in assembly.`);
}
if (elapsed >= totalProcessTimeMs && !hasLoggedCompleted.current) {
hasLoggedCompleted.current = true;
setCurrentAnimation(human.modelUuid, 'working_standing', true, true, true);
if (processAnimationIdRef.current) {
cancelAnimationFrame(processAnimationIdRef.current);
processAnimationIdRef.current = null;
}
humanStatus(human.modelUuid, `✅ Human ${human.modelUuid} completed assembly process.`);
return;
}
processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess);
}, [human.modelUuid, human.point.action.processTime, human.currentMaterials]);
useEffect(() => {
if (isPlaying) {
if (!human.point.action.pickUpPoint || !human.point.action.dropPoint || human.point.action.actionType === 'assembly') return;
if (!human.isActive && human.state === 'idle' && currentPhase === 'init') {
const toPickupPath = computePath(
@@ -145,7 +258,7 @@ function HumanInstance({ human }: { human: HumanStatus }) {
setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true);
humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point');
}
} else if (human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0) {
} else if (human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0 && humanAsset?.animationState?.current !== 'pickup') {
setCurrentAnimation(human.modelUuid, 'pickup', true, false, false);
}
} else if (!human.isActive && human.state === 'idle' && currentPhase === 'dropping' && human.currentLoad === 0) {

View File

@@ -9,13 +9,16 @@ import { useVersionContext } from '../../../../builder/version/versionContext';
import { useParams } from 'react-router-dom';
import startPoint from "../../../../../assets/gltf-glb/ui/human-ui-green.glb";
import startEnd from "../../../../../assets/gltf-glb/ui/human-ui-orange.glb";
import assembly from "../../../../../assets/gltf-glb/ui/human-ui-assembly.glb";
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
function HumanUi() {
const { scene: startScene } = useGLTF(startPoint) as any;
const { scene: endScene } = useGLTF(startEnd) as any;
const { scene: assemblyScene } = useGLTF(assembly) as any;
const startMarker = useRef<Group>(null);
const endMarker = useRef<Group>(null);
const assemblyMarker = useRef<Group>(null);
const outerGroup = useRef<Group>(null);
const prevMousePos = useRef({ x: 0, y: 0 });
const { controls, raycaster, camera } = useThree();
@@ -28,6 +31,7 @@ function HumanUi() {
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]);
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]);
const [startRotation, setStartRotation] = useState<[number, number, number]>([0, Math.PI, 0]);
const [assemblyRotation, setAssemblyRotation] = useState<[number, number, number]>([0, 0, 0]);
const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]);
const { isDragging, setIsDragging } = useIsDragging();
const { isRotating, setIsRotating } = useIsRotating();
@@ -44,6 +48,10 @@ function HumanUi() {
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const selectedHuman = selectedEventSphere ? getHumanById(selectedEventSphere.userData.modelUuid) : null;
const actionType = selectedHuman?.point?.action?.actionType || null;
const isAssembly = actionType === 'assembly';
const updateBackend = (
productName: string,
productUuid: string,
@@ -99,6 +107,13 @@ function HumanUi() {
setEndPosition([0, 1, 0]);
setEndRotation([0, 0, 0]);
}
if (action.assemblyPoint?.rotation) {
setAssemblyRotation(action.assemblyPoint.rotation);
} else {
setAssemblyRotation([0, 0, 0]);
}
}, [selectedEventSphere, outerGroup.current, selectedAction, humans]);
const handlePointerDown = (
@@ -106,6 +121,7 @@ function HumanUi() {
state: "start" | "end",
rotation: "start" | "end"
) => {
if (isAssembly) return;
e.stopPropagation();
const intersection = new Vector3();
const pointer = new Vector2((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1);
@@ -144,54 +160,68 @@ function HumanUi() {
setIsDragging(null);
setIsRotating(null);
if (selectedEventSphere?.userData.modelUuid && selectedAction.actionId) {
const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid);
if (!selectedEventSphere?.userData.modelUuid || !selectedAction?.actionId) return;
if (selectedHuman && outerGroup.current && startMarker.current && endMarker.current) {
const worldPosStart = new Vector3(...startPosition);
const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone());
const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid);
if (!selectedHuman || !outerGroup.current) return;
const worldPosEnd = new Vector3(...endPosition);
const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone());
const isAssembly = selectedHuman.point?.action?.actionType === 'assembly';
const updatedAction = {
...selectedHuman.point.action,
pickUpPoint: {
position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number],
rotation: startRotation
},
dropPoint: {
position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number],
rotation: endRotation
}
};
let updatedAction;
const event = updateEvent(
selectedProduct.productUuid,
selectedEventSphere.userData.modelUuid,
{
...selectedHuman,
point: {
...selectedHuman.point,
action: updatedAction
}
}
);
if (isAssembly) {
updatedAction = {
...selectedHuman.point.action,
assemblyPoint: {
rotation: assemblyRotation
},
};
} else {
if (!startMarker.current || !endMarker.current) return;
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
}
const worldPosStart = new Vector3(...startPosition);
const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone());
const worldPosEnd = new Vector3(...endPosition);
const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone());
updatedAction = {
...selectedHuman.point.action,
pickUpPoint: {
position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number],
rotation: startRotation,
},
dropPoint: {
position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number],
rotation: endRotation,
},
};
}
const event = updateEvent(
selectedProduct.productUuid,
selectedEventSphere.userData.modelUuid,
{
...selectedHuman,
point: {
...selectedHuman.point,
action: updatedAction,
},
}
);
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
}
};
useFrame(() => {
if (!isDragging || !plane.current || !raycaster || !outerGroup.current) return;
if (isAssembly || !isDragging || !plane.current || !raycaster || !outerGroup.current) return;
const intersectPoint = new Vector3();
const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint);
if (!intersects) return;
@@ -210,11 +240,17 @@ function HumanUi() {
const currentPointerX = state.pointer.x;
const deltaX = currentPointerX - prevMousePos.current.x;
prevMousePos.current.x = currentPointerX;
const marker = isRotating === "start" ? startMarker.current : endMarker.current;
const marker = isRotating === "start" ? isAssembly ? assemblyMarker.current : startMarker.current : isAssembly ? assemblyMarker.current : endMarker.current;
if (marker) {
const rotationSpeed = 10;
marker.rotation.y += deltaX * rotationSpeed;
if (isRotating === "start") {
if (isAssembly && isRotating === "start") {
setAssemblyRotation([
marker.rotation.x,
marker.rotation.y,
marker.rotation.z,
]);
} else if (isRotating === "start") {
setStartRotation([
marker.rotation.x,
marker.rotation.y,
@@ -245,7 +281,7 @@ function HumanUi() {
return () => {
window.removeEventListener("pointerup", handleGlobalPointerUp);
};
}, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation]);
}, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation, assemblyRotation]);
return (
<>
@@ -255,43 +291,69 @@ function HumanUi() {
ref={outerGroup}
rotation={[0, Math.PI, 0]}
>
<primitive
name="startMarker"
object={startScene}
ref={startMarker}
position={startPosition}
rotation={startRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "start", "start");
}}
onPointerMissed={() => {
(controls as any).enabled = true;
setIsDragging(null);
setIsRotating(null);
}}
/>
{isAssembly ? (
<primitive
ref={assemblyMarker}
object={assemblyScene}
position={[0, 1, 0]}
rotation={assemblyRotation}
onPointerDown={(e: any) => {
if (e.object.parent.name === "handle") {
e.stopPropagation();
const normalizedX = (e.clientX / window.innerWidth) * 2 - 1;
prevMousePos.current.x = normalizedX;
setIsRotating("start");
setIsDragging(null);
if (controls) (controls as any).enabled = false;
}
}}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/>
) : (
<>
<primitive
name="startMarker"
object={startScene}
ref={startMarker}
position={startPosition}
rotation={startRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "start", "start");
}}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/>
<primitive
name="endMarker"
object={endScene}
ref={endMarker}
position={endPosition}
rotation={endRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "end", "end");
}}
onPointerMissed={() => {
(controls as any).enabled = true;
setIsDragging(null);
setIsRotating(null);
}}
/>
<primitive
name="endMarker"
object={endScene}
ref={endMarker}
position={endPosition}
rotation={endRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "end", "end");
}}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/>
</>
)}
</group>
)}
</>
)
);
}
export default HumanUi

View File

@@ -301,7 +301,6 @@ const VehicleUI = () => {
return selectedVehicleData ? (
<group
position={selectedVehicleData.position}
rotation={selectedVehicleData.rotation}
ref={outerGroup}
>
<group

View File

@@ -299,6 +299,10 @@ export function useTriggerHandler() {
const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid);
const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid);
if (human && human.modelUuid === "cc62adae-7000-447b-b845-6d4910de503a") {
console.log(human);
}
setPreviousLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
@@ -330,7 +334,9 @@ export function useTriggerHandler() {
if (vehicle) {
if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -341,7 +347,9 @@ export function useTriggerHandler() {
addVehicleToMonitor(vehicle.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -354,7 +362,9 @@ export function useTriggerHandler() {
if (vehicle) {
if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -365,7 +375,9 @@ export function useTriggerHandler() {
addVehicleToMonitor(vehicle.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -382,7 +394,9 @@ export function useTriggerHandler() {
if (conveyor) {
if (!conveyor.isPaused) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -393,7 +407,9 @@ export function useTriggerHandler() {
addConveyorToMonitor(conveyor.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -406,7 +422,9 @@ export function useTriggerHandler() {
if (conveyor) {
if (!conveyor.isPaused) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -417,7 +435,9 @@ export function useTriggerHandler() {
addConveyorToMonitor(conveyor.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -434,7 +454,9 @@ export function useTriggerHandler() {
if (machine) {
if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) {
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -443,7 +465,9 @@ export function useTriggerHandler() {
addMachineToMonitor(machine.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -456,7 +480,9 @@ export function useTriggerHandler() {
if (machine) {
if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) {
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -465,7 +491,9 @@ export function useTriggerHandler() {
addMachineToMonitor(machine.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -480,7 +508,9 @@ export function useTriggerHandler() {
// Handle current action from arm bot
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -489,7 +519,9 @@ export function useTriggerHandler() {
setIsPaused(materialId, true);
addHumanToMonitor(human.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId)
}
);
@@ -501,7 +533,9 @@ export function useTriggerHandler() {
// Handle current action from arm bot
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -510,7 +544,9 @@ export function useTriggerHandler() {
setIsPaused(materialId, true);
addHumanToMonitor(human.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId)
}
);
@@ -1280,7 +1316,7 @@ export function useTriggerHandler() {
if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
const material = getMaterialById(materialId);
if (material) {
if (material && action.actionType === 'worker') {
setPreviousLocation(material.materialId, {
modelUuid: material.current.modelUuid,
@@ -1410,6 +1446,28 @@ export function useTriggerHandler() {
handleAction(action, material.materialId);
}
} else if (material && action.actionType === 'assembly') {
setPreviousLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
})
setCurrentLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
});
setNextLocation(material.materialId, {
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid,
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid,
})
setIsPaused(material.materialId, false);
setIsVisible(material.materialId, true);
}
}

View File

@@ -6,40 +6,53 @@ import { MaterialModel } from '../../../materials/instances/material/materialMod
const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => {
const meshRef = useRef<any>(null!);
const [hasLoad, setHasLoad] = useState(false);
const [isAttached, setIsAttached] = useState(false);
const { scene } = useThree();
const offset = new THREE.Vector3(0, 0.85, 0);
useEffect(() => {
setHasLoad(agvDetail.currentLoad > 0);
const loadState = agvDetail.currentLoad > 0;
setHasLoad(loadState);
if (!loadState) {
setIsAttached(false);
if (meshRef.current?.parent) {
meshRef.current.parent.remove(meshRef.current);
}
}
}, [agvDetail.currentLoad]);
useFrame(() => {
if (!hasLoad || !meshRef.current) return;
if (!hasLoad || !meshRef.current || isAttached) return;
const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D;
if (agvModel) {
const worldPosition = offset.clone().applyMatrix4(agvModel.matrixWorld);
meshRef.current.position.copy(worldPosition);
meshRef.current.rotation.copy(agvModel.rotation);
if (agvModel && !isAttached) {
if (meshRef.current.parent) {
meshRef.current.parent.remove(meshRef.current);
}
agvModel.add(meshRef.current);
meshRef.current.position.copy(offset);
meshRef.current.rotation.set(0, 0, 0);
meshRef.current.scale.set(1, 1, 1);
setIsAttached(true);
}
});
return (
<>
{hasLoad && (
<>
{agvDetail.currentMaterials.length > 0 &&
<MaterialModel
matRef={meshRef}
materialId={agvDetail.currentMaterials[0].materialId || ''}
materialType={agvDetail.currentMaterials[0].materialType || 'Default material'}
/>
}
</>
{hasLoad && agvDetail.currentMaterials.length > 0 && (
<MaterialModel
matRef={meshRef}
materialId={agvDetail.currentMaterials[0].materialId || ''}
materialType={agvDetail.currentMaterials[0].materialType || 'Default material'}
visible={isAttached}
/>
)}
</>
);
};
export default MaterialAnimator;
export default MaterialAnimator;

View File

@@ -80,7 +80,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
const distances = [];
let accumulatedDistance = 0;
let index = 0;
const rotationSpeed = 1;
const rotationSpeed = 0.75;
for (let i = 0; i < currentPath.length - 1; i++) {
const start = new THREE.Vector3(...currentPath[i]);
@@ -100,17 +100,25 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
const end = new THREE.Vector3(...currentPath[index + 1]);
const segmentDistance = distances[index];
const currentDirection = new THREE.Vector3().subVectors(end, start).normalize();
const targetAngle = Math.atan2(currentDirection.x, currentDirection.z);
const currentAngle = object.rotation.y;
const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0)));
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
targetQuaternion.multiply(y180);
let angleDifference = targetAngle - currentAngle;
if (angleDifference > Math.PI) angleDifference -= 2 * Math.PI;
if (angleDifference < -Math.PI) angleDifference += 2 * Math.PI;
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < 0.01) {
object.quaternion.copy(targetQuaternion);
} else {
const step = rotationSpeed * delta * speed * agvDetail.speed;
const angle = object.quaternion.angleTo(targetQuaternion);
const maxRotationStep = (rotationSpeed * speed * agvDetail.speed) * delta;
object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep);
const isAligned = Math.abs(angleDifference) < 0.01;
if (angle < step) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
}
const isAligned = angle < 0.01;
if (isAligned) {
progressRef.current += delta * (speed * agvDetail.speed);
@@ -122,17 +130,25 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
if (progressRef.current >= totalDistance) {
if (restRotation && objectRotation) {
const targetEuler = new THREE.Euler(
objectRotation.x,
objectRotation.y - agvDetail.point.action.steeringAngle,
objectRotation.z
);
const targetQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
object.quaternion.slerp(targetQuaternion, delta * (rotationSpeed * speed * agvDetail.speed));
if (object.quaternion.angleTo(targetQuaternion) < 0.01) {
const targetEuler = new THREE.Euler(0, objectRotation.y - agvDetail.point.action.steeringAngle, 0);
const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
const targetQuaternion = baseQuaternion.multiply(y180);
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < 0.01) {
object.quaternion.copy(targetQuaternion);
object.rotation.copy(targetEuler);
setRestingRotation(false);
} else {
const step = rotationSpeed * delta * speed * agvDetail.speed;
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < step) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
}
return;
}

View File

@@ -18,13 +18,13 @@ type ProductsStore = {
updateEvent: (productUuid: string, modelUuid: string, updates: Partial<EventsSchema>) => EventsSchema | undefined;
// Point-level actions
addPoint: (productUuid: string, modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema) => void;
addPoint: (productUuid: string, modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema) => void;
removePoint: (productUuid: string, modelUuid: string, pointUuid: string) => void;
updatePoint: (
productUuid: string,
modelUuid: string,
pointUuid: string,
updates: Partial<ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema>
updates: Partial<ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema>
) => EventsSchema | undefined;
// Action-level actions
@@ -65,9 +65,9 @@ type ProductsStore = {
getEventByActionUuid: (productUuid: string, actionUuid: string) => EventsSchema | undefined;
getEventByTriggerUuid: (productUuid: string, triggerUuid: string) => EventsSchema | undefined;
getEventByPointUuid: (productUuid: string, pointUuid: string) => EventsSchema | undefined;
getPointByUuid: (productUuid: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | undefined;
getActionByUuid: (productUuid: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined;
getActionByPointUuid: (productUuid: string, pointUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined;
getPointByUuid: (productUuid: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema | undefined;
getActionByUuid: (productUuid: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['action']) | undefined;
getActionByPointUuid: (productUuid: string, pointUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['action']) | undefined;
getModelUuidByPointUuid: (productUuid: string, actionUuid: string) => (string) | undefined;
getModelUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined;
getPointUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined;

View File

@@ -72,7 +72,10 @@ interface StorageAction {
interface HumanAction {
actionUuid: string;
actionName: string;
actionType: "worker";
actionType: "worker" | "assembly";
processTime?: number;
swapMaterial?: string;
assemblyPoint?: { rotation: [number, number, number] | null; }
pickUpPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; }
dropPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; }
loadCapacity: number;