From c309af135dcd8eaf4c3bc99a63bc4685d91f32ac Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 18 Jul 2025 14:14:34 +0530 Subject: [PATCH 01/18] moveControls and other controls altered --- app/src/modules/builder/asset/assetsGroup.tsx | 4 +- .../asset/functions/assetBoundingBox.tsx | 54 +++- .../builder/asset/models/model/model.tsx | 32 ++- .../selection2D/moveControls2D.tsx | 4 +- .../selection3D/copyPasteControls3D.tsx | 233 ++++++++------- .../selection3D/duplicationControls3D.tsx | 247 +++++++++------- .../selection3D/moveControls3D.tsx | 267 ++++++++++-------- .../selection3D/rotateControls3D.tsx | 250 ++++++++-------- .../selection3D/selectionControls3D.tsx | 28 +- 9 files changed, 634 insertions(+), 485 deletions(-) diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index a05e423..85c0f88 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -44,9 +44,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); - dracoLoader.setDecoderPath( - "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/" - ); + dracoLoader.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"); loader.setDRACOLoader(dracoLoader); useEffect(() => { diff --git a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx index d74f37d..b26485d 100644 --- a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx +++ b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx @@ -1,20 +1,46 @@ -import { Box3, BoxGeometry, EdgesGeometry, Vector3 } from "three"; +import { Line } from "@react-three/drei"; +import { Box3, Vector3 } from "three"; +import { useMemo } from "react"; + +export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => { + const { points } = useMemo(() => { + if (!boundingBox) return { points: [], center: new Vector3() }; + + const min = boundingBox.min; + const max = boundingBox.max; + const center = boundingBox.getCenter(new Vector3()); + + const edges: Array<[number, number, number]> = [ + [min.x, min.y, min.z], [max.x, min.y, min.z], + [max.x, min.y, min.z], [max.x, max.y, min.z], + [max.x, max.y, min.z], [min.x, max.y, min.z], + [min.x, max.y, min.z], [min.x, min.y, min.z], + + [min.x, min.y, max.z], [max.x, min.y, max.z], + [max.x, min.y, max.z], [max.x, max.y, max.z], + [max.x, max.y, max.z], [min.x, max.y, max.z], + [min.x, max.y, max.z], [min.x, min.y, max.z], + + [min.x, min.y, min.z], [min.x, min.y, max.z], + [max.x, min.y, min.z], [max.x, min.y, max.z], + [max.x, max.y, min.z], [max.x, max.y, max.z], + [min.x, max.y, min.z], [min.x, max.y, max.z], + ]; + + return { points: edges, center }; + }, [boundingBox]); -export const AssetBoundingBox = ({ boundingBox }: { boundingBox: Box3 | null }) => { if (!boundingBox) return null; - const size = boundingBox.getSize(new Vector3()); - const center = boundingBox.getCenter(new Vector3()); - - const boxGeometry = new BoxGeometry(size.x, size.y, size.z); - const edges = new EdgesGeometry(boxGeometry); - return ( - - - - - + + ); -}; \ No newline at end of file +}; diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 6819cb4..429a9c3 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -4,7 +4,7 @@ import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils'; import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; import { ThreeEvent, useFrame, useThree } from '@react-three/fiber'; -import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; +import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; import { AssetBoundingBox } from '../../functions/assetBoundingBox'; import { CameraControls } from '@react-three/drei'; import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore'; @@ -22,6 +22,7 @@ import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIK function Model({ asset }: { readonly asset: Asset }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; + const savedTheme: string = localStorage.getItem("theme") || "light"; const { camera, controls, gl } = useThree(); const { activeTool } = useActiveTool(); const { toolMode } = useToolMode(); @@ -51,6 +52,7 @@ function Model({ asset }: { readonly asset: Asset }) { const [isRendered, setIsRendered] = useState(false); const [gltfScene, setGltfScene] = useState(null); const [boundingBox, setBoundingBox] = useState(null); + const [isSelected, setIsSelected] = useState(false); const groupRef = useRef(null); const mixerRef = useRef(); const actions = useRef<{ [name: string]: THREE.AnimationAction }>({}); @@ -62,6 +64,7 @@ function Model({ asset }: { readonly asset: Asset }) { const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); const { projectId } = useParams(); + const { selectedAssets } = useSelectedAssets(); const updateBackend = ( productName: string, @@ -456,6 +459,18 @@ function Model({ asset }: { readonly asset: Asset }) { }, [gl]) + useEffect(() => { + if (selectedAssets.length > 0) { + if (selectedAssets.some((selectedAsset: THREE.Object3D) => selectedAsset.userData.modelUuid === asset.modelUuid)) { + setIsSelected(true); + } else { + setIsSelected(false); + } + } else { + setIsSelected(false); + } + }, [selectedAssets]) + return ( {gltfScene && ( - isRendered ? ( - - ) : ( - - ) + <> + {isRendered ? ( + + ) : ( + + )} + {isSelected && + + } + )} ); diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx index 790a330..d91cebb 100644 --- a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx @@ -83,7 +83,7 @@ function MoveControls2D({ if (keyCombination === "G") { if (selectedPoints.length > 0) { - moveAssets(); + movePoints(); } } @@ -164,7 +164,7 @@ function MoveControls2D({ return new THREE.Vector3().subVectors(pointPosition, hitPoint); }, []); - const moveAssets = useCallback(() => { + const movePoints = useCallback(() => { if (selectedPoints.length === 0) return; const states: Record = {}; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index fa3e8c7..8593758 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { SkeletonUtils } from "three-stdlib"; import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; @@ -10,20 +10,16 @@ import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; -// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; - const CopyPasteControls3D = ({ copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, - selectionGroup, setDuplicatedObjects, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, - boundingBoxRef }: any) => { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const { toggleView } = useToggleView(); @@ -33,31 +29,67 @@ const CopyPasteControls3D = ({ const { assetStore, eventStore } = useSceneContext(); const { addEvent } = eventStore(); const { projectId } = useParams(); - const { assets, addAsset } = assetStore(); + const { assets, addAsset, setPosition, updateAsset, removeAsset, getAssetById } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); + const [isPasting, setIsPasting] = useState(false); + const [relativePositions, setRelativePositions] = useState([]); + const [centerOffset, setCenterOffset] = useState(null); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ + left: false, + right: false, + }); + + const calculateRelativePositions = useCallback((objects: THREE.Object3D[]) => { + if (objects.length === 0) return { center: new THREE.Vector3(), relatives: [] }; + + const box = new THREE.Box3(); + objects.forEach(obj => box.expandByObject(obj)); + const center = new THREE.Vector3(); + box.getCenter(center); + + const relatives = objects.map(obj => { + const relativePos = new THREE.Vector3().subVectors(obj.position, center); + return relativePos; + }); + + return { center, relatives }; + }, []); + useEffect(() => { if (!camera || !scene || toggleView) return; const canvasElement = gl.domElement; canvasElement.tabIndex = 0; - let isMoving = false; + let isPointerMoving = false; - const onPointerDown = () => { - isMoving = false; + const onPointerDown = (event: PointerEvent) => { + isPointerMoving = false; + if (event.button === 0) mouseButtonsDown.current.left = true; + if (event.button === 2) mouseButtonsDown.current.right = true; }; const onPointerMove = () => { - isMoving = true; + isPointerMoving = true; }; const onPointerUp = (event: PointerEvent) => { - if (!isMoving && pastedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { + if (event.button === 0) mouseButtonsDown.current.left = false; + if (event.button === 2) mouseButtonsDown.current.right = false; + + if (!isPointerMoving && pastedObjects.length > 0 && event.button === 0) { event.preventDefault(); addPastedObjects(); } + if (!isPointerMoving && pastedObjects.length > 0 && event.button === 2) { + event.preventDefault(); + clearSelection(); + pastedObjects.forEach((obj: THREE.Object3D) => { + removeAsset(obj.userData.modelUuid); + }); + } }; const onKeyDown = (event: KeyboardEvent) => { @@ -69,6 +101,13 @@ const CopyPasteControls3D = ({ if (keyCombination === "Ctrl+V" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { pasteCopiedObjects(); } + if (keyCombination === "ESCAPE" && pastedObjects.length > 0) { + event.preventDefault(); + clearSelection(); + pastedObjects.forEach((obj: THREE.Object3D) => { + removeAsset(obj.userData.modelUuid); + }); + } }; if (!toggleView) { @@ -84,27 +123,22 @@ const CopyPasteControls3D = ({ canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); }; - }, [assets, camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, movedObjects, socket, rotatedObjects]); useFrame(() => { - if (pastedObjects.length > 0) { - const intersectionPoint = new THREE.Vector3(); - raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - const position = new THREE.Vector3(); - if (boundingBoxRef.current) { - boundingBoxRef.current?.getWorldPosition(position) - selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z)); - } else { - const box = new THREE.Box3(); - pastedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone())); - const center = new THREE.Vector3(); - box.getCenter(center); - selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z)); - } - } + if (!isPasting || pastedObjects.length === 0) return; + if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) return; + + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit && centerOffset) { + pastedObjects.forEach((pastedObject: THREE.Object3D, index: number) => { + const newPos = new THREE.Vector3().addVectors(hit, relativePositions[index]); + setPosition(pastedObject.userData.modelUuid, [newPos.x, 0, newPos.z]); + }); + } }); @@ -122,31 +156,41 @@ const CopyPasteControls3D = ({ const pasteCopiedObjects = () => { if (copiedObjects.length > 0 && pastedObjects.length === 0) { - const newClones = copiedObjects.map((obj: THREE.Object3D) => { - const clone = obj.clone(); - clone.position.copy(obj.position); + const { center, relatives } = calculateRelativePositions(copiedObjects); + setRelativePositions(relatives); + + const newPastedObjects = copiedObjects.map((obj: any) => { + const clone = SkeletonUtils.clone(obj); + clone.userData.modelUuid = THREE.MathUtils.generateUUID(); return clone; }); - selectionGroup.current.add(...newClones); - setpastedObjects([...newClones]); - setSelectedAssets([...newClones]); - const intersectionPoint = new THREE.Vector3(); + setpastedObjects(newPastedObjects); + raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - const position = new THREE.Vector3(); - if (boundingBoxRef.current) { - boundingBoxRef.current?.getWorldPosition(position) - selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z)); - } else { - const box = new THREE.Box3(); - newClones.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone())); - const center = new THREE.Vector3(); - box.getCenter(center); - selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z)); - } + if (hit) { + const offset = new THREE.Vector3().subVectors(center, hit); + setCenterOffset(offset); + setIsPasting(true); + + newPastedObjects.forEach((obj: THREE.Object3D, index: number) => { + const initialPos = new THREE.Vector3().addVectors(hit, relatives[index]); + const asset: Asset = { + modelUuid: obj.userData.modelUuid, + modelName: obj.userData.modelName, + assetId: obj.userData.assetId, + position: initialPos.toArray(), + rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + isLocked: false, + isVisible: true, + isCollidable: false, + opacity: 0.5, + }; + addAsset(asset); + }); } } }; @@ -154,33 +198,33 @@ const CopyPasteControls3D = ({ const addPastedObjects = () => { if (pastedObjects.length === 0) return; - pastedObjects.forEach(async (obj: THREE.Object3D) => { - if (obj) { - const worldPosition = new THREE.Vector3(); - obj.getWorldPosition(worldPosition); - obj.position.copy(worldPosition); + pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => { + if (pastedAsset) { + const assetUuid = pastedAsset.userData.modelUuid; + const asset = getAssetById(assetUuid); + if (!asset) return; const newFloorItem: Types.FloorItemType = { - modelUuid: THREE.MathUtils.generateUUID(), - modelName: obj.userData.modelName, - assetId: obj.userData.assetId, - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, }, + modelUuid: pastedAsset.userData.modelUuid, + modelName: pastedAsset.userData.modelName, + assetId: pastedAsset.userData.assetId, + position: asset.position, + rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] }, isLocked: false, isVisible: true, }; let updatedEventData = null; - if (obj.userData.eventData) { - updatedEventData = JSON.parse(JSON.stringify(obj.userData.eventData)); + if (pastedAsset.userData.eventData) { + updatedEventData = JSON.parse(JSON.stringify(pastedAsset.userData.eventData)); updatedEventData.modelUuid = newFloorItem.modelUuid; const eventData: any = { - type: obj.userData.eventData.type, + type: pastedAsset.userData.eventData.type, }; - if (obj.userData.eventData.type === "Conveyor") { + if (pastedAsset.userData.eventData.type === "Conveyor") { const ConveyorEvent: ConveyorEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -212,7 +256,7 @@ const CopyPasteControls3D = ({ rotation: point.rotation })); - } else if (obj.userData.eventData.type === "Vehicle") { + } else if (pastedAsset.userData.eventData.type === "Vehicle") { const vehicleEvent: VehicleEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -250,7 +294,7 @@ const CopyPasteControls3D = ({ rotation: vehicleEvent.point.rotation }; - } else if (obj.userData.eventData.type === "ArmBot") { + } else if (pastedAsset.userData.eventData.type === "ArmBot") { const roboticArmEvent: RoboticArmEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -284,7 +328,7 @@ const CopyPasteControls3D = ({ rotation: roboticArmEvent.point.rotation }; - } else if (obj.userData.eventData.type === "StaticMachine") { + } else if (pastedAsset.userData.eventData.type === "StaticMachine") { const machineEvent: MachineEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -312,7 +356,7 @@ const CopyPasteControls3D = ({ position: machineEvent.point.position, rotation: machineEvent.point.rotation }; - } else if (obj.userData.eventData.type === "Storage") { + } else if (pastedAsset.userData.eventData.type === "Storage") { const storageEvent: StorageEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -339,7 +383,7 @@ const CopyPasteControls3D = ({ position: storageEvent.point.position, rotation: storageEvent.point.rotation }; - } else if (obj.userData.eventData.type === "Human") { + } else if (pastedAsset.userData.eventData.type === "Human") { const humanEvent: HumanEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -374,15 +418,16 @@ const CopyPasteControls3D = ({ } newFloorItem.eventData = eventData; + //REST // await setAssetsApi( // organization, - // obj.uuid, - // obj.userData.name, + // pastedAsset.uuid, + // pastedAsset.userData.name, // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, + // { "x": pastedAsset.rotation.x, "y": pastedAsset.rotation.y, "z": pastedAsset.rotation.z }, + // pastedAsset.userData.modelId, // false, // true, // ); @@ -395,7 +440,7 @@ const CopyPasteControls3D = ({ modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, @@ -405,16 +450,8 @@ const CopyPasteControls3D = ({ projectId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); - obj.userData = { - name: newFloorItem.modelName, - modelId: newFloorItem.assetId, - modelUuid: newFloorItem.modelUuid, - eventData: JSON.parse(JSON.stringify(eventData)) - }; - const asset: Asset = { modelUuid: data.modelUuid, modelName: data.modelName, @@ -426,33 +463,17 @@ const CopyPasteControls3D = ({ isVisible: data.isVisible, opacity: 1, eventData: data.eventData - } - - addAsset(asset); + }; + updateAsset(asset.modelUuid, asset); } else { - - //REST - - // await setAssetsApi( - // organization, - // obj.uuid, - // obj.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, - // false, - // true, - // ); - - //SOCKET const data = { organization, modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, @@ -461,7 +482,6 @@ const CopyPasteControls3D = ({ userId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); const asset: Asset = { @@ -474,9 +494,9 @@ const CopyPasteControls3D = ({ isCollidable: false, isVisible: data.isVisible, opacity: 1, - } + }; - addAsset(asset); + updateAsset(asset.modelUuid, asset); } } }); @@ -486,15 +506,14 @@ const CopyPasteControls3D = ({ }; const clearSelection = () => { - selectionGroup.current.children = []; - selectionGroup.current.position.set(0, 0, 0); - selectionGroup.current.rotation.set(0, 0, 0); setMovedObjects([]); setpastedObjects([]); setDuplicatedObjects([]); setRotatedObjects([]); setSelectedAssets([]); - } + setIsPasting(false); + setCenterOffset(null); + }; return null; }; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index ec92d6d..4f08cc7 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { SkeletonUtils } from "three-stdlib"; import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; @@ -10,18 +10,14 @@ import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; -// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; - const DuplicationControls3D = ({ duplicatedObjects, setDuplicatedObjects, setpastedObjects, - selectionGroup, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, - boundingBoxRef }: any) => { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const { toggleView } = useToggleView(); @@ -31,39 +27,71 @@ const DuplicationControls3D = ({ const { assetStore, eventStore } = useSceneContext(); const { addEvent } = eventStore(); const { projectId } = useParams(); - const { assets, addAsset } = assetStore(); + const { assets, addAsset, setPosition, updateAsset, removeAsset, getAssetById } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); + const [dragOffset, setDragOffset] = useState(null); + const [initialPositions, setInitialPositions] = useState>({}); + const [isDuplicating, setIsDuplicating] = useState(false); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ + left: false, + right: false, + }); + + const calculateDragOffset = useCallback((point: THREE.Vector3, hitPoint: THREE.Vector3) => { + const pointPosition = new THREE.Vector3().copy(point); + return new THREE.Vector3().subVectors(pointPosition, hitPoint); + }, []); + useEffect(() => { if (!camera || !scene || toggleView) return; const canvasElement = gl.domElement; canvasElement.tabIndex = 0; - let isMoving = false; + let isPointerMoving = false; - const onPointerDown = () => { - isMoving = false; + const onPointerDown = (event: PointerEvent) => { + isPointerMoving = false; + if (event.button === 0) mouseButtonsDown.current.left = true; + if (event.button === 2) mouseButtonsDown.current.right = true; }; const onPointerMove = () => { - isMoving = true; + isPointerMoving = true; }; const onPointerUp = (event: PointerEvent) => { - if (!isMoving && duplicatedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { + if (event.button === 0) mouseButtonsDown.current.left = false; + if (event.button === 2) mouseButtonsDown.current.right = false; + + if (!isPointerMoving && duplicatedObjects.length > 0 && event.button === 0) { event.preventDefault(); addDuplicatedAssets(); } + if (!isPointerMoving && duplicatedObjects.length > 0 && event.button === 2) { + event.preventDefault(); + clearSelection(); + duplicatedObjects.forEach((obj: THREE.Object3D) => { + removeAsset(obj.userData.modelUuid); + }); + } }; const onKeyDown = (event: KeyboardEvent) => { const keyCombination = detectModifierKeys(event); - if (keyCombination === "Ctrl+D" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { + if (keyCombination === "Ctrl+D" && movedObjects.length === 0 && rotatedObjects.length === 0) { duplicateSelection(); } + if (keyCombination === "ESCAPE" && duplicatedObjects.length > 0) { + event.preventDefault(); + clearSelection(); + duplicatedObjects.forEach((obj: THREE.Object3D) => { + removeAsset(obj.userData.modelUuid); + }); + } }; if (!toggleView) { @@ -79,82 +107,125 @@ const DuplicationControls3D = ({ canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); }; - }, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects]); useFrame(() => { - if (duplicatedObjects.length > 0) { - const intersectionPoint = new THREE.Vector3(); - raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - const position = new THREE.Vector3(); - if (boundingBoxRef.current) { - boundingBoxRef.current?.getWorldPosition(position) - selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z)); - } else { - const box = new THREE.Box3(); - duplicatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone())); - const center = new THREE.Vector3(); - box.getCenter(center); - selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z)); + if (!isDuplicating || duplicatedObjects.length === 0) return; + + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit) { + if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) { + const assetUuid = duplicatedObjects[0].userData.modelUuid; + const asset = getAssetById(assetUuid); + if (!asset) return; + if (duplicatedObjects[0]) { + const newOffset = calculateDragOffset(new THREE.Vector3(...asset.position), intersectionPoint); + setDragOffset(newOffset); } + return; + } + + if (dragOffset) { + const adjustedHit = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + + duplicatedObjects.forEach((duplicatedObject: THREE.Object3D) => { + if (duplicatedObject.userData.modelUuid) { + const initialPosition = initialPositions[duplicatedObject.userData.modelUuid]; + + if (initialPosition) { + + const relativeOffset = new THREE.Vector3().subVectors( + initialPosition, + initialPositions[duplicatedObjects[0].userData.modelUuid] + ); + + const newPosition = new THREE.Vector3().addVectors(adjustedHit, relativeOffset); + const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; + + setPosition(duplicatedObject.userData.modelUuid, positionArray); + } + } + }); } } }); - const duplicateSelection = () => { + const duplicateSelection = useCallback(() => { if (selectedAssets.length > 0 && duplicatedObjects.length === 0) { - const newClones = selectedAssets.map((asset: any) => { - const clone = SkeletonUtils.clone(asset); - clone.position.copy(asset.position); + const positions: Record = {}; + + const newDuplicatedObjects = selectedAssets.map((obj: any) => { + const clone = SkeletonUtils.clone(obj); + clone.userData.modelUuid = THREE.MathUtils.generateUUID(); + positions[clone.userData.modelUuid] = new THREE.Vector3().copy(obj.position); return clone; }); - selectionGroup.current.add(...newClones); - setDuplicatedObjects(newClones); + setDuplicatedObjects(newDuplicatedObjects); + setInitialPositions(positions); - const intersectionPoint = new THREE.Vector3(); raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - const position = new THREE.Vector3(); - boundingBoxRef.current?.getWorldPosition(position) - selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z)); + if (hit) { + const offset = calculateDragOffset(selectedAssets[0].position, hit); + setDragOffset(offset); } + + setIsDuplicating(true); + + newDuplicatedObjects.forEach((obj: THREE.Object3D) => { + const asset: Asset = { + modelUuid: obj.userData.modelUuid, + modelName: obj.userData.modelName, + assetId: obj.userData.assetId, + position: [obj.position.x, 0, obj.position.z], + rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + isLocked: false, + isVisible: true, + isCollidable: false, + opacity: 0.5, + }; + addAsset(asset); + }); } - }; + }, [selectedAssets, duplicatedObjects]); + const addDuplicatedAssets = () => { if (duplicatedObjects.length === 0) return; - duplicatedObjects.forEach(async (obj: THREE.Object3D) => { - if (obj) { - const worldPosition = new THREE.Vector3(); - obj.getWorldPosition(worldPosition); - obj.position.copy(worldPosition); + duplicatedObjects.forEach(async (duplicatedAsset: THREE.Object3D) => { + if (duplicatedAsset) { + const assetUuid = duplicatedAsset.userData.modelUuid; + const asset = getAssetById(assetUuid); + if (!asset) return; const newFloorItem: Types.FloorItemType = { - modelUuid: THREE.MathUtils.generateUUID(), - modelName: obj.userData.modelName, - assetId: obj.userData.assetId, - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, }, + modelUuid: duplicatedAsset.userData.modelUuid, + modelName: duplicatedAsset.userData.modelName, + assetId: duplicatedAsset.userData.assetId, + position: asset.position, + rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] }, isLocked: false, isVisible: true, }; let updatedEventData = null; - if (obj.userData.eventData) { - updatedEventData = JSON.parse(JSON.stringify(obj.userData.eventData)); + + if (duplicatedAsset.userData.eventData) { + updatedEventData = JSON.parse(JSON.stringify(duplicatedAsset.userData.eventData)); updatedEventData.modelUuid = newFloorItem.modelUuid; const eventData: any = { - type: obj.userData.eventData.type, + type: duplicatedAsset.userData.eventData.type, }; - if (obj.userData.eventData.type === "Conveyor") { + if (duplicatedAsset.userData.eventData.type === "Conveyor") { const ConveyorEvent: ConveyorEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -186,7 +257,7 @@ const DuplicationControls3D = ({ rotation: point.rotation })); - } else if (obj.userData.eventData.type === "Vehicle") { + } else if (duplicatedAsset.userData.eventData.type === "Vehicle") { const vehicleEvent: VehicleEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -224,7 +295,7 @@ const DuplicationControls3D = ({ rotation: vehicleEvent.point.rotation }; - } else if (obj.userData.eventData.type === "ArmBot") { + } else if (duplicatedAsset.userData.eventData.type === "ArmBot") { const roboticArmEvent: RoboticArmEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -258,7 +329,7 @@ const DuplicationControls3D = ({ rotation: roboticArmEvent.point.rotation }; - } else if (obj.userData.eventData.type === "StaticMachine") { + } else if (duplicatedAsset.userData.eventData.type === "StaticMachine") { const machineEvent: MachineEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -286,7 +357,7 @@ const DuplicationControls3D = ({ position: machineEvent.point.position, rotation: machineEvent.point.rotation }; - } else if (obj.userData.eventData.type === "Storage") { + } else if (duplicatedAsset.userData.eventData.type === "Storage") { const storageEvent: StorageEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -313,7 +384,7 @@ const DuplicationControls3D = ({ position: storageEvent.point.position, rotation: storageEvent.point.rotation }; - } else if (obj.userData.eventData.type === "Human") { + } else if (duplicatedAsset.userData.eventData.type === "Human") { const humanEvent: HumanEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -353,11 +424,11 @@ const DuplicationControls3D = ({ // await setAssetsApi( // organization, - // obj.uuid, - // obj.userData.name, + // pastedAsset.uuid, + // pastedAsset.userData.name, // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, + // { "x": pastedAsset.rotation.x, "y": pastedAsset.rotation.y, "z": pastedAsset.rotation.z }, + // pastedAsset.userData.modelId, // false, // true, // ); @@ -370,17 +441,16 @@ const DuplicationControls3D = ({ modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, eventData: eventData, versionId: selectedVersion?.versionId || '', - projectId, - userId + userId, + projectId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); const asset: Asset = { @@ -394,43 +464,25 @@ const DuplicationControls3D = ({ isVisible: data.isVisible, opacity: 1, eventData: data.eventData - } - - addAsset(asset); + }; + updateAsset(asset.modelUuid, asset); } else { - - //REST - - // await setAssetsApi( - // organization, - // obj.uuid, - // obj.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, - // false, - // true, - // ); - - //SOCKET - const data = { organization, modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, versionId: selectedVersion?.versionId || '', - userId, - projectId + projectId, + userId }; - // console.log('data: ', data);/ socket.emit("v1:model-asset:add", data); const asset: Asset = { @@ -443,27 +495,26 @@ const DuplicationControls3D = ({ isCollidable: false, isVisible: data.isVisible, opacity: 1, - } + }; - addAsset(asset); + updateAsset(asset.modelUuid, asset); } } }); echo.success("Object duplicated!"); clearSelection(); - } + }; const clearSelection = () => { - selectionGroup.current.children = []; - selectionGroup.current.position.set(0, 0, 0); - selectionGroup.current.rotation.set(0, 0, 0); setMovedObjects([]); setpastedObjects([]); setDuplicatedObjects([]); setRotatedObjects([]); setSelectedAssets([]); - } + setIsDuplicating(false); + setDragOffset(null); + }; return null; }; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 6658f62..d6732b3 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store"; import * as Types from "../../../../../types/world/worldTypes"; @@ -22,7 +22,6 @@ function MoveControls3D({ setpastedObjects, duplicatedObjects, setDuplicatedObjects, - selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef, @@ -39,11 +38,19 @@ function MoveControls3D({ const { userId, organization } = getUserData(); const { projectId } = useParams(); const { assetStore, eventStore, productStore } = useSceneContext(); - const { updateAsset } = assetStore(); - const AssetGroup = useRef(undefined); + const { updateAsset, setPosition, getAssetById } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const [dragOffset, setDragOffset] = useState(null); + const [initialPositions, setInitialPositions] = useState>({}); + const [initialStates, setInitialStates] = useState>({}); + const [isMoving, setIsMoving] = useState(false); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ + left: false, + right: false, + }); + const updateBackend = ( productName: string, productUuid: string, @@ -65,22 +72,10 @@ function MoveControls3D({ const canvasElement = gl.domElement; canvasElement.tabIndex = 0; - const itemsGroup: any = scene.getObjectByName("Asset Group"); - AssetGroup.current = itemsGroup; - - if (!AssetGroup.current) { - console.error("Asset Group not found in the scene."); - return; - } - - let isMoving = false; - - const onPointerDown = () => { - isMoving = false; - }; + let isPointerMoving = false; const onPointerMove = () => { - isMoving = true; + isPointerMoving = true; }; const onKeyUp = (event: KeyboardEvent) => { @@ -91,21 +86,25 @@ function MoveControls3D({ } }; + const onPointerDown = (event: PointerEvent) => { + isPointerMoving = false; + + if (event.button === 0) mouseButtonsDown.current.left = true; + if (event.button === 2) mouseButtonsDown.current.right = true; + }; + const onPointerUp = (event: PointerEvent) => { - if (!isMoving && movedObjects.length > 0 && event.button === 0) { + if (event.button === 0) mouseButtonsDown.current.left = false; + if (event.button === 2) mouseButtonsDown.current.right = false; + + if (!isPointerMoving && movedObjects.length > 0 && event.button === 0) { event.preventDefault(); placeMovedAssets(); } - if (!isMoving && movedObjects.length > 0 && event.button === 2) { + if (!isPointerMoving && movedObjects.length > 0 && event.button === 2) { event.preventDefault(); - + resetToInitialPositions(); clearSelection(); - movedObjects.forEach((asset: any) => { - if (AssetGroup.current) { - AssetGroup.current.attach(asset); - } - }); - setMovedObjects([]); } setKeyEvent(""); @@ -131,14 +130,8 @@ function MoveControls3D({ if (keyCombination === "ESCAPE") { event.preventDefault(); - + resetToInitialPositions(); clearSelection(); - movedObjects.forEach((asset: any) => { - if (AssetGroup.current) { - AssetGroup.current.attach(asset); - } - }); - setMovedObjects([]); } }; @@ -158,118 +151,154 @@ function MoveControls3D({ canvasElement.removeEventListener("keydown", onKeyDown); canvasElement?.removeEventListener("keyup", onKeyUp); }; - }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent,]); + }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent]); - let moveSpeed = keyEvent === "Ctrl" || "Ctrl+Shift" ? 1 : 0.25; + const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { + const pointPosition = new THREE.Vector3().copy(point.position); + return new THREE.Vector3().subVectors(pointPosition, hitPoint); + }, []); + + const resetToInitialPositions = useCallback(() => { + setTimeout(() => { + movedObjects.forEach((movedObject: THREE.Object3D) => { + if (movedObject.userData.modelUuid && initialStates[movedObject.uuid]) { + const initialState = initialStates[movedObject.uuid]; + const positionArray: [number, number, number] = [ + initialState.position.x, + initialState.position.y, + initialState.position.z + ]; + + updateAsset(movedObject.userData.modelUuid, { + position: positionArray, + rotation: [ + initialState.rotation?.x || 0, + initialState.rotation?.y || 0, + initialState.rotation?.z || 0 + ], + }); + + movedObject.position.copy(initialState.position); + if (initialState.rotation) { + movedObject.rotation.copy(initialState.rotation); + } + } + }); + }, 0) + }, [movedObjects, initialStates, updateAsset]); useFrame(() => { - if (movedObjects.length > 0) { - const intersectionPoint = new THREE.Vector3(); - raycaster.setFromCamera(pointer, camera); - let point = raycaster.ray.intersectPlane(plane, intersectionPoint); - const floorsGroup = scene.getObjectByName("Floors-Group") as Types.Group | null; - const floorChildren = floorsGroup?.children ?? []; - const floorIntersections = raycaster.intersectObjects([...floorChildren], true); - const intersectedFloor = floorIntersections.find((intersect) => intersect.object.name.includes("Floor")); + if (!isMoving || movedObjects.length === 0) return; - if (intersectedFloor && selectedAssets.length === 1) { - if (intersectedFloor.object.userData.floorUuid) { - point = new THREE.Vector3(intersectedFloor.point.x, intersectedFloor.object.userData.floorDepth, intersectedFloor.point.z); + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit) { + if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) { + if (movedObjects[0]) { + const newOffset = calculateDragOffset(movedObjects[0], intersectionPoint); + setDragOffset(newOffset); } + return; } - if (point) { - let targetX = point.x; - let targetY = point.y; - let targetZ = point.z; + if (dragOffset) { + const baseNewPosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + + let moveSpeed = keyEvent === "Shift" || "Ctrl+Shift" ? 0.25 : 1; if (keyEvent === "Ctrl") { - targetX = snapControls(targetX, "Ctrl"); - targetZ = snapControls(targetZ, "Ctrl"); + baseNewPosition.x = snapControls(baseNewPosition.x, "Ctrl"); + baseNewPosition.z = snapControls(baseNewPosition.z, "Ctrl"); } - // else if (keyEvent === "Ctrl+Shift") { - // targetX = snapControls(targetX, "Ctrl+Shift"); - // targetZ = snapControls(targetZ, "Ctrl+Shift"); - // } else if (keyEvent === "Shift") { - // targetX = snapControls(targetX, "Shift"); - // targetZ = snapControls(targetZ, "Shift"); - // } else { - // } + movedObjects.forEach((movedAsset: THREE.Object3D) => { + if (movedAsset.userData.modelUuid) { + const initialPosition = initialPositions[movedAsset.userData.modelUuid]; + + if (initialPosition) { + const relativeOffset = new THREE.Vector3().subVectors( + initialPosition, + initialPositions[movedObjects[0].uuid] + ); + + const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset); + const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; + + setPosition(movedAsset.userData.modelUuid, positionArray); + } + } + }); const position = new THREE.Vector3(); - if (boundingBoxRef.current) { boundingBoxRef.current.getWorldPosition(position); - selectionGroup.current.position.lerp( - new THREE.Vector3( - targetX - (position.x - selectionGroup.current.position.x), - targetY - (position.y - selectionGroup.current.position.y), - targetZ - (position.z - selectionGroup.current.position.z) - ), - moveSpeed - ); } else { const box = new THREE.Box3(); - movedObjects.forEach((obj: THREE.Object3D) => - box.expandByObject(obj) - ); + movedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj)); const center = new THREE.Vector3(); box.getCenter(center); - - selectionGroup.current.position.lerp( - new THREE.Vector3( - targetX - (center.x - selectionGroup.current.position.x), - selectionGroup.current.position.y, - targetZ - (center.z - selectionGroup.current.position.z) - ), - moveSpeed - ); } } } }); const moveAssets = () => { - setMovedObjects(selectedAssets); - selectedAssets.forEach((asset: any) => { - selectionGroup.current.attach(asset); + const states: Record = {}; + const positions: Record = {}; + + selectedAssets.forEach((asset: THREE.Object3D) => { + states[asset.uuid] = { + position: new THREE.Vector3().copy(asset.position), + rotation: asset.rotation ? new THREE.Euler().copy(asset.rotation) : undefined + }; + positions[asset.uuid] = new THREE.Vector3().copy(asset.position); }); + + setInitialStates(states); + setInitialPositions(positions); + + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit && selectedAssets[0]) { + const offset = calculateDragOffset(selectedAssets[0], hit); + setDragOffset(offset); + } + + setMovedObjects(selectedAssets); + setIsMoving(true); }; const placeMovedAssets = () => { if (movedObjects.length === 0) return; - movedObjects.forEach(async (obj: THREE.Object3D) => { - if (obj && AssetGroup.current) { - let worldPosition = new THREE.Vector3(); - obj.getWorldPosition(worldPosition); - - if (worldPosition.y < 0) { - worldPosition.y = 0; - } - - selectionGroup.current.remove(obj); - obj.position.copy(worldPosition); + movedObjects.forEach(async (movedAsset: THREE.Object3D) => { + if (movedAsset) { + const assetUuid = movedAsset.userData.modelUuid; + const asset = getAssetById(assetUuid); + if (!asset) return; const newFloorItem: Types.FloorItemType = { - modelUuid: obj.userData.modelUuid, - modelName: obj.userData.modelName, - assetId: obj.userData.assetId, - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + modelUuid: movedAsset.userData.modelUuid, + modelName: movedAsset.userData.modelName, + assetId: movedAsset.userData.assetId, + position: asset.position, + rotation: { x: movedAsset.rotation.x, y: movedAsset.rotation.y, z: movedAsset.rotation.z }, isLocked: false, isVisible: true, }; - if (obj.userData.eventData) { - const eventData = eventStore.getState().getEventByModelUuid(obj.userData.modelUuid); - const productData = productStore.getState().getEventByModelUuid(selectedProduct.productUuid, obj.userData.modelUuid); + if (movedAsset.userData.eventData) { + const eventData = eventStore.getState().getEventByModelUuid(movedAsset.userData.modelUuid); + const productData = productStore.getState().getEventByModelUuid(selectedProduct.productUuid, movedAsset.userData.modelUuid); if (eventData) { - eventStore.getState().updateEvent(obj.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + eventStore.getState().updateEvent(movedAsset.userData.modelUuid, { + position: asset.position, + rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], }); } @@ -278,10 +307,10 @@ function MoveControls3D({ .getState() .updateEvent( selectedProduct.productUuid, - obj.userData.modelUuid, + movedAsset.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + position: asset.position, + rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], } ); @@ -298,9 +327,9 @@ function MoveControls3D({ } } - updateAsset(obj.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + updateAsset(movedAsset.userData.modelUuid, { + position: asset.position, + rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], }); //REST @@ -324,7 +353,7 @@ function MoveControls3D({ modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { x: movedAsset.rotation.x, y: movedAsset.rotation.y, z: movedAsset.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, @@ -333,22 +362,16 @@ function MoveControls3D({ userId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); - - AssetGroup.current.add(obj); } }); echo.success("Object moved!"); - + setIsMoving(false); clearSelection(); }; const clearSelection = () => { - selectionGroup.current.children = []; - selectionGroup.current.position.set(0, 0, 0); - selectionGroup.current.rotation.set(0, 0, 0); setpastedObjects([]); setDuplicatedObjects([]); setMovedObjects([]); @@ -365,4 +388,4 @@ function MoveControls3D({ ); } -export default MoveControls3D; +export default MoveControls3D; \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index fdde56a..f31bc9a 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { useEffect, useMemo, useRef } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; import * as Types from "../../../../../types/world/worldTypes"; @@ -20,11 +20,9 @@ function RotateControls3D({ pastedObjects, setpastedObjects, duplicatedObjects, - setDuplicatedObjects, - selectionGroup + setDuplicatedObjects }: any) { - - const { camera, controls, gl, scene, pointer, raycaster } = useThree(); + const { camera, gl, scene, pointer, raycaster } = useThree(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { toggleView } = useToggleView(); @@ -36,26 +34,33 @@ function RotateControls3D({ const { projectId } = useParams(); const { assetStore, eventStore, productStore } = useSceneContext(); const { updateAsset } = assetStore(); - const AssetGroup = useRef(undefined); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); - const updateBackend = ( + const [initialRotations, setInitialRotations] = useState>({}); + const [initialPositions, setInitialPositions] = useState>({}); + const [isRotating, setIsRotating] = useState(false); + const prevPointerPosition = useRef(null); + const rotationCenter = useRef(null); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ + left: false, + right: false, + }); + + const updateBackend = useCallback(( productName: string, productUuid: string, projectId: string, eventData: EventsSchema ) => { upsertProductOrEventApi({ - productName: productName, - productUuid: productUuid, - projectId: projectId, + productName, + productUuid, + projectId, eventDatas: eventData, versionId: selectedVersion?.versionId || '', - }) - } - - const prevPointerPosition = useRef(null); + }); + }, [selectedVersion]); useEffect(() => { if (!camera || !scene || toggleView) return; @@ -63,45 +68,37 @@ function RotateControls3D({ const canvasElement = gl.domElement; canvasElement.tabIndex = 0; - const itemsGroup: any = scene.getObjectByName("Asset Group"); - AssetGroup.current = itemsGroup; - - if (!AssetGroup.current) { - console.error("Asset Group not found in the scene."); - return; - } - - let isMoving = false; - - const onPointerDown = () => { - isMoving = false; - }; + let isPointerMoving = false; const onPointerMove = () => { - isMoving = true; + isPointerMoving = true; + }; + + const onPointerDown = (event: PointerEvent) => { + isPointerMoving = false; + if (event.button === 0) mouseButtonsDown.current.left = true; + if (event.button === 2) mouseButtonsDown.current.right = true; }; const onPointerUp = (event: PointerEvent) => { - if (!isMoving && rotatedObjects.length > 0 && event.button === 0) { + if (event.button === 0) mouseButtonsDown.current.left = false; + if (event.button === 2) mouseButtonsDown.current.right = false; + + if (!isPointerMoving && rotatedObjects.length > 0 && event.button === 0) { event.preventDefault(); placeRotatedAssets(); } - if (!isMoving && rotatedObjects.length > 0 && event.button === 2) { + if (!isPointerMoving && rotatedObjects.length > 0 && event.button === 2) { event.preventDefault(); - + resetToInitialRotations(); clearSelection(); - rotatedObjects.forEach((asset: any) => { - if (AssetGroup.current) { - AssetGroup.current.attach(asset); - } - }); - setRotatedObjects([]); } }; const onKeyDown = (event: KeyboardEvent) => { if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || movedObjects.length > 0) return; + if (event.key.toLowerCase() === "r") { if (selectedAssets.length > 0) { rotateAssets(); @@ -109,15 +106,8 @@ function RotateControls3D({ } if (event.key.toLowerCase() === "escape") { event.preventDefault(); - + resetToInitialRotations(); clearSelection(); - rotatedObjects.forEach((asset: any) => { - if (AssetGroup.current) { - AssetGroup.current.attach(asset); - } - }); - - setRotatedObjects([]); } }; @@ -135,41 +125,85 @@ function RotateControls3D({ canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); }; - }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, rotatedObjects, movedObjects]); + }, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects]); + + const resetToInitialRotations = useCallback(() => { + rotatedObjects.forEach((obj: THREE.Object3D) => { + const uuid = obj.uuid; + if (obj.userData.modelUuid) { + const initialRotation = initialRotations[uuid]; + const initialPosition = initialPositions[uuid]; + + if (initialRotation && initialPosition) { + const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,]; + const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,]; + + updateAsset(obj.userData.modelUuid, { + rotation: rotationArray, + position: positionArray, + }); + + obj.rotation.copy(initialRotation); + obj.position.copy(initialPosition); + } + } + }); + }, [rotatedObjects, initialRotations, initialPositions, updateAsset]); useFrame(() => { - if (rotatedObjects.length > 0) { - const intersectionPoint = new THREE.Vector3(); - raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (!isRotating || rotatedObjects.length === 0) return; - if (point && prevPointerPosition.current) { - const box = new THREE.Box3(); - rotatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj)); - const center = new THREE.Vector3(); - box.getCenter(center); - - const delta = new THREE.Vector3().subVectors(point, center); - const prevPointerPosition3D = new THREE.Vector3(prevPointerPosition.current.x, 0, prevPointerPosition.current.y); - - const angle = Math.atan2(delta.z, delta.x) - Math.atan2(prevPointerPosition3D.z - center.z, prevPointerPosition3D.x - center.x); - - selectionGroup.current.rotation.y += -angle; - - selectionGroup.current.position.sub(center); - selectionGroup.current.position.applyAxisAngle(new THREE.Vector3(0, 1, 0), -angle); - selectionGroup.current.position.add(center); + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) { + if (point) { prevPointerPosition.current = new THREE.Vector2(point.x, point.z); } + return; + } + + if (point && prevPointerPosition.current && rotationCenter.current) { + const center = rotationCenter.current; + + const currentAngle = Math.atan2(point.z - center.z, point.x - center.x); + const prevAngle = Math.atan2( + prevPointerPosition.current.y - center.z, + prevPointerPosition.current.x - center.x + ); + const angleDelta = prevAngle - currentAngle; + + rotatedObjects.forEach((obj: THREE.Object3D) => { + if (obj.userData.modelUuid) { + const relativePos = new THREE.Vector3().subVectors(obj.position, center); + relativePos.applyAxisAngle(new THREE.Vector3(0, 1, 0), angleDelta); + obj.position.copy(center).add(relativePos); + obj.rotation.y += angleDelta; + } + }); + + prevPointerPosition.current = new THREE.Vector2(point.x, point.z); } }); - const rotateAssets = () => { + const rotateAssets = useCallback(() => { + const rotations: Record = {}; + const positions: Record = {}; + const box = new THREE.Box3(); - selectedAssets.forEach((asset: any) => box.expandByObject(asset)); + selectedAssets.forEach((obj: THREE.Object3D) => box.expandByObject(obj)); const center = new THREE.Vector3(); box.getCenter(center); + rotationCenter.current = center; + + selectedAssets.forEach((obj: THREE.Object3D) => { + rotations[obj.uuid] = new THREE.Euler().copy(obj.rotation); + positions[obj.uuid] = new THREE.Vector3().copy(obj.position); + }); + + setInitialRotations(rotations); + setInitialPositions(positions); const intersectionPoint = new THREE.Vector3(); raycaster.setFromCamera(pointer, camera); @@ -179,56 +213,54 @@ function RotateControls3D({ prevPointerPosition.current = new THREE.Vector2(point.x, point.z); } - selectedAssets.forEach((asset: any) => { - selectionGroup.current.attach(asset); - }); - setRotatedObjects(selectedAssets); - }; + setIsRotating(true); + }, [selectedAssets, camera, pointer, raycaster, plane]); - const placeRotatedAssets = () => { + const placeRotatedAssets = useCallback(() => { if (rotatedObjects.length === 0) return; - rotatedObjects.forEach(async (obj: THREE.Object3D) => { - if (obj && AssetGroup.current) { - const worldPosition = new THREE.Vector3(); - const worldQuaternion = new THREE.Quaternion(); + rotatedObjects.forEach((obj: THREE.Object3D) => { + if (obj && obj.userData.modelUuid) { + const rotationArray: [number, number, number] = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - obj.getWorldPosition(worldPosition); - obj.getWorldQuaternion(worldQuaternion); - - selectionGroup.current.remove(obj); - - obj.position.copy(worldPosition); - obj.quaternion.copy(worldQuaternion); + const positionArray: [number, number, number] = [obj.position.x, obj.position.y, obj.position.z]; const newFloorItem: Types.FloorItemType = { modelUuid: obj.userData.modelUuid, modelName: obj.userData.modelName, assetId: obj.userData.assetId, - position: [worldPosition.x, worldPosition.y, worldPosition.z], + position: positionArray, rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, - isVisible: true + isVisible: true, }; if (obj.userData.eventData) { const eventData = eventStore.getState().getEventByModelUuid(obj.userData.modelUuid); - const productData = productStore.getState().getEventByModelUuid(selectedProductStore.getState().selectedProduct.productUuid, obj.userData.modelUuid); + const productData = productStore.getState().getEventByModelUuid( + selectedProduct.productUuid, + obj.userData.modelUuid + ); if (eventData) { eventStore.getState().updateEvent(obj.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], - }) + position: positionArray, + rotation: rotationArray, + }); } - if (productData) { - const event = productStore.getState().updateEvent(selectedProductStore.getState().selectedProduct.productUuid, obj.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], - }) - if (event && organization) { + if (productData) { + const event = productStore.getState().updateEvent( + selectedProduct.productUuid, + obj.userData.modelUuid, + { + position: positionArray, + rotation: rotationArray, + } + ); + + if (event) { updateBackend( selectedProduct.productName, selectedProduct.productUuid, @@ -242,8 +274,8 @@ function RotateControls3D({ } updateAsset(obj.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + position: positionArray, + rotation: rotationArray, }); //REST @@ -267,7 +299,11 @@ function RotateControls3D({ modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { + x: obj.rotation.x, + y: obj.rotation.y, + z: obj.rotation.z + }, isLocked: false, isVisible: true, socketId: socket.id, @@ -276,29 +312,23 @@ function RotateControls3D({ userId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); - - AssetGroup.current.add(obj); } }); - echo.success("Object rotated!"); + setIsRotating(false); clearSelection(); - } + }, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]); const clearSelection = () => { - selectionGroup.current.children = []; - selectionGroup.current.position.set(0, 0, 0); - selectionGroup.current.rotation.set(0, 0, 0); setpastedObjects([]); setDuplicatedObjects([]); setMovedObjects([]); setRotatedObjects([]); setSelectedAssets([]); - } + }; return null; } -export default RotateControls3D \ No newline at end of file +export default RotateControls3D; \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx index 080a5d8..eb8bdae 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx @@ -1,9 +1,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import * as THREE from "three"; -import { useFrame, useThree } from "@react-three/fiber"; +import { useThree } from "@react-three/fiber"; import { SelectionHelper } from "../selectionHelper"; import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox"; -import * as Types from "../../../../../types/world/worldTypes"; import useModuleStore from "../../../../../store/useModuleStore"; import { useParams } from "react-router-dom"; @@ -13,7 +12,6 @@ import { useVersionContext } from "../../../../builder/version/versionContext"; import { useProductContext } from "../../../../simulation/products/productContext"; import { useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store"; import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; -import BoundingBox from "./boundingBoxHelper3D"; import DuplicationControls3D from "./duplicationControls3D"; import CopyPasteControls3D from "./copyPasteControls3D"; import MoveControls3D from "./moveControls3D"; @@ -23,7 +21,6 @@ import RotateControls3D from "./rotateControls3D"; const SelectionControls3D: React.FC = () => { const { camera, controls, gl, scene, raycaster, pointer } = useThree(); - const selectionGroup = useRef() as Types.RefGroup; const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const [movedObjects, setMovedObjects] = useState([]); @@ -229,12 +226,6 @@ const SelectionControls3D: React.FC = () => { } }, [activeModule, toolMode, toggleView]); - useFrame(() => { - if (pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { - selectionGroup.current.position.set(0, 0, 0); - } - }); - const selectAssets = useCallback(() => { selectionBox.endPoint.set(pointer.x, pointer.y, 0); if (controls) (controls as any).enabled = true; @@ -267,9 +258,6 @@ const SelectionControls3D: React.FC = () => { }, [selectionBox, pointer, controls, selectedAssets, setSelectedAssets]); const clearSelection = () => { - selectionGroup.current.children = []; - selectionGroup.current.position.set(0, 0, 0); - selectionGroup.current.rotation.set(0, 0, 0); setpastedObjects([]); setDuplicatedObjects([]); setSelectedAssets([]); @@ -345,19 +333,13 @@ const SelectionControls3D: React.FC = () => { return ( <> - - - - - + - + - + - - - + ); }; From 5ae8d8e27344576ae6570841aec445ff5b0141a9 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 18 Jul 2025 15:11:57 +0530 Subject: [PATCH 02/18] move speed control --- .../selection3D/moveControls3D.tsx | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index d6732b3..d74c2da 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -116,10 +116,12 @@ function MoveControls3D({ if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return; - if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { - setKeyEvent(keyCombination); - } else { - setKeyEvent(""); + if (keyCombination !== keyEvent) { + if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } else { + setKeyEvent(""); + } } if (keyCombination === "G") { @@ -204,11 +206,18 @@ function MoveControls3D({ } if (dragOffset) { - const baseNewPosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); - let moveSpeed = keyEvent === "Shift" || "Ctrl+Shift" ? 0.25 : 1; + let moveSpeed = keyEvent.includes("Shift") ? 0.25 : 1; - if (keyEvent === "Ctrl") { + const initialBasePosition = initialPositions[movedObjects[0].uuid]; + const positionDifference = new THREE.Vector3().subVectors(rawBasePosition, initialBasePosition); + + const adjustedDifference = positionDifference.multiplyScalar(moveSpeed); + + const baseNewPosition = new THREE.Vector3().addVectors(initialBasePosition, adjustedDifference); + + if (keyEvent.includes("Ctrl")) { baseNewPosition.x = snapControls(baseNewPosition.x, "Ctrl"); baseNewPosition.z = snapControls(baseNewPosition.z, "Ctrl"); } From c9dc6c864233d3a5c6fa54d25f52cdc6e0120dc0 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 18 Jul 2025 15:25:22 +0530 Subject: [PATCH 03/18] bug fix --- .../selection3D/copyPasteControls3D.tsx | 23 ++++++---------- .../selection3D/duplicationControls3D.tsx | 23 ++++++---------- .../selection3D/moveControls3D.tsx | 21 +++++---------- .../selection3D/rotateControls3D.tsx | 27 +++++-------------- 4 files changed, 29 insertions(+), 65 deletions(-) diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index 8593758..0d7cf7e 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -10,6 +10,8 @@ import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; +// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; + const CopyPasteControls3D = ({ copiedObjects, setCopiedObjects, @@ -419,21 +421,6 @@ const CopyPasteControls3D = ({ newFloorItem.eventData = eventData; - //REST - - // await setAssetsApi( - // organization, - // pastedAsset.uuid, - // pastedAsset.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": pastedAsset.rotation.x, "y": pastedAsset.rotation.y, "z": pastedAsset.rotation.z }, - // pastedAsset.userData.modelId, - // false, - // true, - // ); - - //SOCKET - const data = { organization, modelUuid: newFloorItem.modelUuid, @@ -450,6 +437,12 @@ const CopyPasteControls3D = ({ projectId }; + //REST + + // await setAssetsApi(data); + + //SOCKET + socket.emit("v1:model-asset:add", data); const asset: Asset = { diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index 4f08cc7..f4f5048 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -10,6 +10,8 @@ import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; +// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; + const DuplicationControls3D = ({ duplicatedObjects, setDuplicatedObjects, @@ -420,21 +422,6 @@ const DuplicationControls3D = ({ newFloorItem.eventData = eventData; - //REST - - // await setAssetsApi( - // organization, - // pastedAsset.uuid, - // pastedAsset.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": pastedAsset.rotation.x, "y": pastedAsset.rotation.y, "z": pastedAsset.rotation.z }, - // pastedAsset.userData.modelId, - // false, - // true, - // ); - - //SOCKET - const data = { organization, modelUuid: newFloorItem.modelUuid, @@ -451,6 +438,12 @@ const DuplicationControls3D = ({ projectId }; + //REST + + // await setAssetsApi(data); + + //SOCKET + socket.emit("v1:model-asset:add", data); const asset: Asset = { diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index d74c2da..e58cc44 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -341,21 +341,6 @@ function MoveControls3D({ rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], }); - //REST - - // await setAssetsApi( - // organization, - // obj.uuid, - // obj.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, - // false, - // true, - // ); - - //SOCKET - const data = { organization, modelUuid: newFloorItem.modelUuid, @@ -371,6 +356,12 @@ function MoveControls3D({ userId }; + //REST + + // await setAssetsApi(data); + + //SOCKET + socket.emit("v1:model-asset:add", data); } }); diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index f31bc9a..83266fd 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -278,32 +278,13 @@ function RotateControls3D({ rotation: rotationArray, }); - //REST - - // await setAssetsApi( - // organization, - // obj.uuid, - // obj.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, - // false, - // true, - // ); - - //SOCKET - const data = { organization, modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { - x: obj.rotation.x, - y: obj.rotation.y, - z: obj.rotation.z - }, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, @@ -312,6 +293,12 @@ function RotateControls3D({ userId }; + //REST + + // await setAssetsApi(data); + + //SOCKET + socket.emit("v1:model-asset:add", data); } }); From ccf64e0f970d257ba6218c5ab036b0aad7625951 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 18 Jul 2025 17:10:40 +0530 Subject: [PATCH 04/18] bug fix --- .../builder/asset/functions/assetBoundingBox.tsx | 13 +++++++++---- .../Instances/Instances/wallAssetInstance.tsx | 6 +++--- .../selection3D/moveControls3D.tsx | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx index b26485d..5923321 100644 --- a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx +++ b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx @@ -2,13 +2,14 @@ import { Line } from "@react-three/drei"; import { Box3, Vector3 } from "three"; import { useMemo } from "react"; -export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => { - const { points } = useMemo(() => { - if (!boundingBox) return { points: [], center: new Vector3() }; +export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => { + const { points, size, center } = useMemo(() => { + if (!boundingBox) { return { points: [], center: new Vector3(), size: new Vector3(), }; } const min = boundingBox.min; const max = boundingBox.max; const center = boundingBox.getCenter(new Vector3()); + const size = boundingBox.getSize(new Vector3()); const edges: Array<[number, number, number]> = [ [min.x, min.y, min.z], [max.x, min.y, min.z], @@ -27,7 +28,7 @@ export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { nam [min.x, max.y, min.z], [min.x, max.y, max.z], ]; - return { points: edges, center }; + return { points: edges, center, size }; }, [boundingBox]); if (!boundingBox) return null; @@ -41,6 +42,10 @@ export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { nam color={color} lineWidth={lineWidth} /> + + + + ); }; diff --git a/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx b/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx index 1a674c7..203223d 100644 --- a/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx +++ b/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx @@ -242,7 +242,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { visible={wallAsset.isVisible} userData={wallAsset} > - + @@ -265,7 +265,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { e.stopPropagation(); let currentObject = e.object as THREE.Object3D; while (currentObject) { - if (currentObject.name === "Scene") { + if (currentObject.userData.wallUuid) { break; } currentObject = currentObject.parent as THREE.Object3D; @@ -286,7 +286,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { e.stopPropagation(); let currentObject = e.object as THREE.Object3D; while (currentObject) { - if (currentObject.name === "Scene") { + if (currentObject.userData.wallUuid) { break; } currentObject = currentObject.parent as THREE.Object3D; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index e58cc44..88b8bf6 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -208,7 +208,7 @@ function MoveControls3D({ if (dragOffset) { const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); - let moveSpeed = keyEvent.includes("Shift") ? 0.25 : 1; + let moveSpeed = keyEvent.includes("Shift") ? 0.05 : 1; const initialBasePosition = initialPositions[movedObjects[0].uuid]; const positionDifference = new THREE.Vector3().subVectors(rawBasePosition, initialBasePosition); From 88361b1623e9cce1eee199ad6ff646126dfa77fc Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 22 Jul 2025 10:19:49 +0530 Subject: [PATCH 05/18] human bug fix --- .../actions/conveyor/useConveyorActions.ts | 2 +- .../human/actionHandler/useWorkerHandler.ts | 3 +- .../actionHandler/useRetrieveHandler.ts | 3 +- .../eventManager/useHumanEventManager.ts | 139 +++++++++++------- .../triggerHandler/useTriggerHandler.ts | 31 ++-- .../instances/animator/vehicleAnimator.tsx | 1 - .../instances/instance/vehicleInstance.tsx | 3 +- app/src/store/simulation/useHumanStore.ts | 33 ----- app/src/types/simulationTypes.d.ts | 1 - 9 files changed, 104 insertions(+), 112 deletions(-) diff --git a/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts b/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts index 600fae5..f2dc047 100644 --- a/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts +++ b/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts @@ -37,7 +37,7 @@ export function useConveyorActions() { switch (action.actionType) { case 'default': - handleDefaultAction(action); + handleDefaultAction(action, materialId); break; case 'spawn': handleSpawnAction(action); diff --git a/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts b/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts index efefc39..67132e7 100644 --- a/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts +++ b/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts @@ -8,7 +8,7 @@ export function useWorkerHandler() { const { getModelUuidByActionUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { incrementHumanLoad, incrementLoadCount, addCurrentMaterial, addCurrentAction } = humanStore(); + const { incrementHumanLoad, addCurrentMaterial, addCurrentAction } = humanStore(); const workerLogStatus = (materialUuid: string, status: string) => { echo.info(`${materialUuid}, ${status}`); @@ -24,7 +24,6 @@ export function useWorkerHandler() { if (!modelUuid) return; incrementHumanLoad(modelUuid, 1); - incrementLoadCount(modelUuid, 1); addCurrentAction(modelUuid, action.actionUuid); addCurrentMaterial(modelUuid, material.materialType, material.materialId); diff --git a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts index 79d6401..03c7138 100644 --- a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts +++ b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts @@ -11,7 +11,7 @@ export function useRetrieveHandler() { const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore(); const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore(); const { getVehicleById, incrementVehicleLoad, addCurrentMaterial } = vehicleStore(); - const { getHumanById, incrementHumanLoad, incrementLoadCount, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore(); + const { getHumanById, incrementHumanLoad, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore(); const { getAssetById, setCurrentAnimation } = assetStore(); const { selectedProduct } = selectedProductStore(); const { getArmBotById, addCurrentAction } = armBotStore(); @@ -316,7 +316,6 @@ export function useRetrieveHandler() { removeLastMaterial(storageUnit.modelUuid); updateCurrentLoad(storageUnit.modelUuid, -1); incrementHumanLoad(human.modelUuid, 1); - incrementLoadCount(human.modelUuid, 1); addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId); retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`); } diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts index 2d2fc11..ac5a478 100644 --- a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -6,16 +6,21 @@ import { useProductContext } from '../../products/productContext'; export function useHumanEventManager() { const { humanStore, productStore, assetStore } = useSceneContext(); - const { getHumanById, clearLoadCount, setCurrentPhase } = humanStore(); + const { getHumanById, setCurrentPhase } = humanStore(); const { getAssetById } = assetStore(); const { getActionByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const callbacksRef = useRef void)[]>>(new Map()); - const actionQueueRef = useRef>(new Map()); - const isCooldownRef = useRef>(new Map()); - const isMonitoringRef = useRef(false); + const stateRef = useRef({ + humanStates: new Map void)[], + actionQueue: { actionType: "worker" | "assembly", actionUuid: string, actionName: string }[], + isCooldown: boolean + }>(), + callbackCounts: new Map>(), + isMonitoring: false + }); const { isPlaying } = usePlayButtonStore(); const { isPaused } = usePauseButtonStore(); @@ -23,10 +28,9 @@ export function useHumanEventManager() { useEffect(() => { if (isReset) { - callbacksRef.current.clear(); - actionQueueRef.current.clear(); - isCooldownRef.current.clear(); - isMonitoringRef.current = false; + stateRef.current.humanStates.clear(); + stateRef.current.callbackCounts.clear(); + stateRef.current.isMonitoring = false; } }, [isReset]); @@ -38,34 +42,64 @@ export function useHumanEventManager() { const actionType = action.actionType; if (actionType !== "worker" && actionType !== "assembly") return; - if (!callbacksRef.current.has(humanId)) { - callbacksRef.current.set(humanId, []); - actionQueueRef.current.set(humanId, []); + if (!stateRef.current.callbackCounts.has(humanId)) { + stateRef.current.callbackCounts.set(humanId, new Map()); } - callbacksRef.current.get(humanId)!.push(callback); - actionQueueRef.current.get(humanId)!.push({ actionType, actionUuid }); + const actionCounts = stateRef.current.callbackCounts.get(humanId)!; + if (!actionCounts.has(actionUuid)) { + actionCounts.set(actionUuid, 0); + } - isMonitoringRef.current = true; + const currentCount = actionCounts.get(actionUuid)!; + if (actionType === 'worker' && currentCount >= action.loadCount) { + return; + } + + if (!stateRef.current.humanStates.has(humanId)) { + stateRef.current.humanStates.set(humanId, { + callbacks: [], + actionQueue: [], + isCooldown: false + }); + } + + const humanState = stateRef.current.humanStates.get(humanId)!; + humanState.callbacks.push(callback); + humanState.actionQueue.push({ actionType, actionUuid, actionName: action.actionName }); + + stateRef.current.isMonitoring = true; }; const removeHumanFromMonitor = (humanId: string) => { - callbacksRef.current.delete(humanId); - actionQueueRef.current.delete(humanId); - isCooldownRef.current.delete(humanId); + // stateRef.current.humanStates.delete(humanId); - if (callbacksRef.current.size === 0) { - isMonitoringRef.current = false; + if (stateRef.current.humanStates.size === 0) { + stateRef.current.isMonitoring = false; } }; + const getCallbackCount = (humanId: string, actionUuid: string) => { + if (!stateRef.current.callbackCounts.has(humanId)) return 0; + return stateRef.current.callbackCounts.get(humanId)!.get(actionUuid) || 0; + }; + + const incrementCallbackCount = (humanId: string, actionUuid: string) => { + if (!stateRef.current.callbackCounts.has(humanId)) { + stateRef.current.callbackCounts.set(humanId, new Map()); + } + const actionCounts = stateRef.current.callbackCounts.get(humanId)!; + const currentCount = actionCounts.get(actionUuid) || 0; + actionCounts.set(actionUuid, currentCount + 1); + }; + useFrame(() => { - if (!isMonitoringRef.current || !isPlaying || isPaused) return; + if (!stateRef.current.isMonitoring || !isPlaying || isPaused) return; - callbacksRef.current.forEach((queue, humanId) => { - if (queue.length === 0 || isCooldownRef.current.get(humanId)) return; + stateRef.current.humanStates.forEach((humanState, humanId) => { + if (humanState.callbacks.length === 0 || humanState.isCooldown) return; - const actionQueue = actionQueueRef.current.get(humanId); + const actionQueue = humanState.actionQueue; if (!actionQueue || actionQueue.length === 0) return; const { actionType: expectedActionType, actionUuid } = actionQueue[0]; @@ -75,11 +109,22 @@ export function useHumanEventManager() { if (!humanAsset || !human || !action || action.actionType !== expectedActionType) return; - let conditionMet = false; + const currentCount = getCallbackCount(humanId, actionUuid); const currentAction = getActionByUuid(selectedProduct.productUuid, human.currentAction?.actionUuid || '') as HumanAction | undefined; + let conditionMet = false; + if (expectedActionType === "worker") { + if (currentAction && currentAction.actionType === 'worker' && currentCount >= currentAction.loadCount) { + humanState.callbacks.shift(); + actionQueue.shift(); + if (humanState.callbacks.length === 0) { + removeHumanFromMonitor(humanId); + } + return; + } + if (currentAction && currentAction.actionType === 'worker') { conditionMet = ( !human.isActive && @@ -88,15 +133,6 @@ export function useHumanEventManager() { human.currentLoad < currentAction.loadCapacity ); - if (human.totalLoadCount >= currentAction.loadCount && actionUuid === human.currentAction?.actionUuid) { - queue.shift(); - actionQueue.shift(); - if (queue.length === 0) { - removeHumanFromMonitor(humanId); - } - return; - } - if (conditionMet && actionUuid !== human.currentAction?.actionUuid) { setCurrentPhase(human.modelUuid, 'init'); } @@ -104,8 +140,7 @@ export function useHumanEventManager() { conditionMet = ( !human.isActive && human.state === "idle" && - humanAsset.animationState?.current === 'idle' && - human.currentLoad < action.loadCapacity + humanAsset.animationState?.current === 'idle' ); if (conditionMet && actionUuid !== human.currentAction?.actionUuid) { setCurrentPhase(human.modelUuid, 'init'); @@ -127,44 +162,34 @@ export function useHumanEventManager() { conditionMet = ( !human.isActive && human.state === "idle" && - humanAsset.animationState?.current === 'idle' && - human.currentLoad < action.loadCapacity + humanAsset.animationState?.current === 'idle' ) } - if (conditionMet) { - clearLoadCount(human.modelUuid); - } } if (conditionMet) { - const callback = queue.shift(); + const callback = humanState.callbacks.shift(); actionQueue.shift(); - if (callback) callback(); + if (callback) { + callback(); + incrementCallbackCount(humanId, actionUuid); + } - if (queue.length === 0) { + if (humanState.callbacks.length === 0) { removeHumanFromMonitor(humanId); } else { - isCooldownRef.current.set(humanId, true); + humanState.isCooldown = true; setTimeout(() => { - isCooldownRef.current.set(humanId, false); + humanState.isCooldown = false; }, 1000); } } }); - }, 0); - - useEffect(() => { - return () => { - callbacksRef.current.clear(); - actionQueueRef.current.clear(); - isCooldownRef.current.clear(); - isMonitoringRef.current = false; - }; - }, []); + }); return { addHumanToMonitor, removeHumanFromMonitor, }; -} +} \ No newline at end of file diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 6a0ef6b..62582af 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -478,8 +478,10 @@ export function useTriggerHandler() { if (toEvent?.type === 'transfer') { // Vehicle to Transfer if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const conveyor = getConveyorById(toEvent.modelUuid); const material = getMaterialById(materialId); - if (material) { + + if (material && conveyor) { const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); setPreviousLocation(material.materialId, { @@ -494,23 +496,29 @@ export function useTriggerHandler() { actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, }); + setIsPaused(materialId, false); + setIsVisible(materialId, true); if (action && action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid && action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid ) { + setNextLocation(material.materialId, { - modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid, - pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid, + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '', }); - handleAction(action, materialId); - } + addConveyorToMonitor(conveyor.modelUuid, () => { + setIsPaused(materialId, false); + + handleAction(action, materialId); + }) + } } } - } else if (toEvent?.type === 'vehicle') { // Vehicle to Vehicle @@ -539,7 +547,6 @@ export function useTriggerHandler() { setNextLocation(material.materialId, null); - if (action && armBot) { if (armBot.isActive === false && armBot.state === 'idle') { @@ -1439,13 +1446,11 @@ export function useTriggerHandler() { const material = getMaterialById(materialId); if (material) { - setIsPaused(material.materialId, false); + setIsPaused(material.materialId, true); const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); const vehicle = getVehicleById(trigger.triggeredAsset?.triggeredModel.modelUuid); - setNextLocation(material.materialId, null); - if (action) { if (vehicle) { @@ -1466,16 +1471,15 @@ export function useTriggerHandler() { actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, }); + setNextLocation(material.materialId, null); + // Handle current action from vehicle handleAction(action, materialId); } else { - setIsPaused(materialId, true); - addVehicleToMonitor(vehicle.modelUuid, () => { - setIsPaused(materialId, false); setIsVisible(materialId, false); setPreviousLocation(material.materialId, { @@ -1493,6 +1497,7 @@ export function useTriggerHandler() { setNextLocation(material.materialId, null); // Handle current action from vehicle + console.log('action: ', action); handleAction(action, materialId); } ) diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index 59cd9da..5ea19f9 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -325,7 +325,6 @@ function DraggableLineSegment({ }; const onPointerMove = (e: ThreeEvent) => { - console.log('isAnyDragging: ', isAnyDragging); if (isAnyDragging !== "line" || activeTool !== 'pen') return; const intersect = new THREE.Vector3(); diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 3d6d274..5de64cd 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -19,7 +19,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, productStore, assetStore } = useSceneContext(); const { removeMaterial, setEndTime, setIsVisible } = materialStore(); const { getStorageUnitById } = storageUnitStore(); - const { getHumanById, addCurrentAction, addCurrentMaterial, incrementHumanLoad , incrementLoadCount } = humanStore(); + const { getHumanById, addCurrentAction, addCurrentMaterial, incrementHumanLoad } = humanStore(); const { getArmBotById } = armBotStore(); const { getConveyorById } = conveyorStore(); const { triggerPointActions } = useTriggerHandler(); @@ -302,7 +302,6 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) setIsVisible(material.materialId, false); addCurrentMaterial(humanId, material.materialType, material.materialId); incrementHumanLoad(humanId, 1); - incrementLoadCount(humanId, 1); } return; } diff --git a/app/src/store/simulation/useHumanStore.ts b/app/src/store/simulation/useHumanStore.ts index bcaf303..685970f 100644 --- a/app/src/store/simulation/useHumanStore.ts +++ b/app/src/store/simulation/useHumanStore.ts @@ -27,11 +27,6 @@ interface HumansStore { incrementHumanLoad: (modelUuid: string, incrementBy: number) => void; decrementHumanLoad: (modelUuid: string, decrementBy: number) => void; - incrementLoadCount: (modelUuid: string, incrementBy: number) => void; - decrementLoadCount: (modelUuid: string, decrementBy: number) => void; - - clearLoadCount: (modelUuid: string) => void; - addCurrentMaterial: (modelUuid: string, materialType: string, materialId: string) => void; setCurrentMaterials: (modelUuid: string, materials: { materialType: string; materialId: string }[]) => void; removeLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined; @@ -65,7 +60,6 @@ export const createHumanStore = () => { isScheduled: false, idleTime: 0, activeTime: 0, - totalLoadCount: 0, currentLoad: 0, currentMaterials: [], distanceTraveled: 0 @@ -182,33 +176,6 @@ export const createHumanStore = () => { }); }, - incrementLoadCount: (modelUuid, incrementBy) => { - set((state) => { - const human = state.humans.find(h => h.modelUuid === modelUuid); - if (human) { - human.totalLoadCount += incrementBy; - } - }); - }, - - decrementLoadCount: (modelUuid, decrementBy) => { - set((state) => { - const human = state.humans.find(h => h.modelUuid === modelUuid); - if (human) { - human.totalLoadCount -= decrementBy; - } - }); - }, - - clearLoadCount: (modelUuid) => { - set((state) => { - const human = state.humans.find(h => h.modelUuid === modelUuid); - if (human) { - human.totalLoadCount = 0; - } - }); - }, - addCurrentMaterial: (modelUuid, materialType, materialId) => { set((state) => { const human = state.humans.find(h => h.modelUuid === modelUuid); diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 5d6b21f..dcb9f49 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -252,7 +252,6 @@ interface HumanStatus extends HumanEventSchema { isScheduled: boolean; idleTime: number; activeTime: number; - totalLoadCount: number; currentLoad: number; currentMaterials: { materialType: string; materialId: string; }[]; distanceTraveled: number; From 708c8a4ba19829a6c1bd2c7422167ba8bbffaf3f Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 22 Jul 2025 16:33:33 +0530 Subject: [PATCH 06/18] new human event mangaer --- .../actions/assemblyAction.tsx | 26 + .../eventProperties/actions/workerAction.tsx | 2 +- .../mechanics/humanMechanics.tsx | 37 +- app/src/modules/builder/asset/assetsGroup.tsx | 1 + .../builder/asset/functions/addAssetModel.ts | 1 + .../models/model/animator/modelAnimator.tsx | 94 +++ .../selection3D/copyPasteControls3D.tsx | 1 + .../selection3D/duplicationControls3D.tsx | 1 + app/src/modules/scene/sceneContext.tsx | 8 +- .../conveyorInstance/conveyorInstance.tsx | 16 +- .../eventManager/useHumanEventManager.ts | 227 +++--- .../instances/animator/assemblerAnimator.tsx | 172 +++++ .../instances/animator/materialAnimator.tsx | 2 +- .../{humanAnimator.tsx => workerAnimator.tsx} | 60 +- .../instance/actions/assemberInstance.tsx | 221 ++++++ .../instance/actions/workerInstance.tsx | 615 +++++++++++++++ .../instances/instance/humanInstance.tsx | 725 +----------------- .../triggerHandler/useTriggerHandler.ts | 117 +-- app/src/types/simulationTypes.d.ts | 21 + 19 files changed, 1355 insertions(+), 992 deletions(-) create mode 100644 app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx create mode 100644 app/src/modules/simulation/human/instances/animator/assemblerAnimator.tsx rename app/src/modules/simulation/human/instances/animator/{humanAnimator.tsx => workerAnimator.tsx} (80%) create mode 100644 app/src/modules/simulation/human/instances/instance/actions/assemberInstance.tsx create mode 100644 app/src/modules/simulation/human/instances/instance/actions/workerInstance.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx index ba74c7c..e371f6f 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx @@ -1,5 +1,6 @@ import React from "react"; import InputRange from "../../../../../ui/inputs/InputRange"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import SwapAction from "./SwapAction"; interface AssemblyActionProps { @@ -10,6 +11,15 @@ interface AssemblyActionProps { disabled?: boolean, onChange: (value: number) => void; }; + assemblyCount: { + value: number; + min: number; + max: number; + step: number; + defaultValue: string, + disabled: false, + onChange: (value: number) => void; + } swapOptions: string[]; swapDefaultOption: string; onSwapSelect: (value: string) => void; @@ -18,6 +28,7 @@ interface AssemblyActionProps { const AssemblyAction: React.FC = ({ processTime, + assemblyCount, swapOptions, swapDefaultOption, onSwapSelect, @@ -34,6 +45,21 @@ const AssemblyAction: React.FC = ({ onClick={() => { }} onChange={processTime.onChange} /> + + {assemblyCount && ( + { }} + onChange={(value) => assemblyCount.onChange(parseInt(value))} + /> + )} void; }; - loadCount?: { + loadCount: { value: number; min: number; max: number; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index 6d91d1c..6109361 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -20,6 +20,7 @@ function HumanMechanics() { const [activeOption, setActiveOption] = useState<"worker" | "assembly">("worker"); const [speed, setSpeed] = useState("0.5"); const [loadCount, setLoadCount] = useState(0); + const [assemblyCount, setAssemblyCount] = useState(0); const [loadCapacity, setLoadCapacity] = useState("1"); const [processTime, setProcessTime] = useState(10); const [swappedMaterial, setSwappedMaterial] = useState("Default material"); @@ -56,6 +57,7 @@ function HumanMechanics() { setLoadCapacity(firstAction.loadCapacity.toString()); setActiveOption(firstAction.actionType); setLoadCount(firstAction.loadCount || 0); + setAssemblyCount(firstAction.assemblyCount || 0); setProcessTime(firstAction.processTime || 10); setSwappedMaterial(firstAction.swapMaterial || "Default material"); } @@ -84,6 +86,7 @@ function HumanMechanics() { setActiveOption(newCurrentAction.actionType); setLoadCapacity(newCurrentAction.loadCapacity.toString()); setLoadCount(newCurrentAction.loadCount || 0); + setAssemblyCount(newCurrentAction.assemblyCount || 0); if (newCurrentAction.actionType === 'assembly') { setProcessTime(newCurrentAction.processTime || 10); @@ -200,6 +203,28 @@ function HumanMechanics() { setLoadCount(value); }; + const handleAssemblyCountChange = (value: number) => { + if (!currentAction || !selectedPointData || !selectedAction.actionId) return; + + const updatedAction = { ...currentAction, assemblyCount: value }; + const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action); + const updatedPoint = { ...selectedPointData, actions: updatedActions }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + setAssemblyCount(value); + }; + const handleProcessTimeChange = (value: number) => { if (!currentAction || !selectedPointData || !selectedAction.actionId) return; @@ -281,6 +306,7 @@ function HumanMechanics() { actionName: `Action ${selectedPointData.actions.length + 1}`, actionType: "worker", loadCount: 1, + assemblyCount: 1, loadCapacity: 1, processTime: 10, triggers: [], @@ -389,7 +415,7 @@ function HumanMechanics() { }} loadCount={{ value: loadCount, - min: 0, + min: 1, max: 20, step: 1, defaultValue: "1", @@ -407,6 +433,15 @@ function HumanMechanics() { max: 60, onChange: handleProcessTimeChange, }} + assemblyCount={{ + value: assemblyCount, + min: 1, + max: 20, + step: 1, + defaultValue: "1", + disabled: false, + onChange: handleAssemblyCountChange, + }} swapOptions={["Default material", "Material 1", "Material 2", "Material 3"]} swapDefaultOption={swappedMaterial} onSwapSelect={handleMaterialChange} diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index 85c0f88..deda3d8 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -265,6 +265,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { actionName: "Action 1", actionType: "worker", loadCount: 1, + assemblyCount: 1, loadCapacity: 1, processTime: 10, triggers: [] diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 4b72cd3..80d8b0a 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -384,6 +384,7 @@ async function handleModelLoad( actionName: "Action 1", actionType: "worker", loadCount: 1, + assemblyCount: 1, loadCapacity: 1, processTime: 10, triggers: [] diff --git a/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx b/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx new file mode 100644 index 0000000..d2c76eb --- /dev/null +++ b/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx @@ -0,0 +1,94 @@ +import { useEffect, useRef, useCallback, useState } from 'react'; +import * as THREE from 'three'; +import { useFrame } from '@react-three/fiber'; +import { useSceneContext } from '../../../../../scene/sceneContext'; +import useModuleStore from '../../../../../../store/useModuleStore'; +import { usePauseButtonStore, useAnimationPlaySpeed } from '../../../../../../store/usePlayButtonStore'; + +interface ModelAnimatorProps { + asset: Asset; + gltfScene: THREE.Object3D; +} + +export function ModelAnimator({ + asset, + gltfScene, +}: ModelAnimatorProps) { + const mixerRef = useRef(); + const actions = useRef<{ [name: string]: THREE.AnimationAction }>({}); + const [previousAnimation, setPreviousAnimation] = useState(null); + + const blendFactor = useRef(0); + const blendDuration = 0.5; + + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + const { assetStore } = useSceneContext(); + const { activeModule } = useModuleStore(); + const { setAnimations, setAnimationComplete } = assetStore(); + + const handleAnimationComplete = useCallback(() => { + if (asset.animationState) { + setAnimationComplete(asset.modelUuid, true); + } + }, [asset.animationState]); + + useEffect(() => { + if (!gltfScene || !gltfScene.animations || gltfScene.animations.length === 0) return; + + mixerRef.current = new THREE.AnimationMixer(gltfScene); + + gltfScene.animations.forEach((clip: THREE.AnimationClip) => { + const action = mixerRef.current!.clipAction(clip); + actions.current[clip.name] = action; + }); + + const animationNames = gltfScene.animations.map(clip => clip.name); + setAnimations(asset.modelUuid, animationNames); + + return () => { + mixerRef.current?.stopAllAction(); + mixerRef.current = undefined; + }; + }, [gltfScene]); + + useEffect(() => { + if (!asset.animationState || !mixerRef.current) return; + + const { current, loopAnimation, isPlaying } = asset.animationState; + const currentAction = actions.current[current]; + const previousAction = previousAnimation ? actions.current[previousAnimation] : null; + + if (isPlaying && currentAction && !isPaused) { + blendFactor.current = 0; + + currentAction.reset(); + currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1); + currentAction.clampWhenFinished = true; + + if (previousAction && previousAction !== currentAction) { + previousAction.crossFadeTo(currentAction, blendDuration, false); + } + + currentAction.play(); + mixerRef.current.addEventListener('finished', handleAnimationComplete); + setPreviousAnimation(current); + } else { + Object.values(actions.current).forEach((action) => action.stop()); + } + + return () => { + if (mixerRef.current) { + mixerRef.current.removeEventListener('finished', handleAnimationComplete); + } + }; + }, [asset.animationState?.current, asset.animationState?.isCompleted, asset.animationState?.isPlaying, isPaused, activeModule]); + + useFrame((_, delta) => { + if (mixerRef.current) { + mixerRef.current.update(delta * (activeModule === 'simulation' ? speed : 1)); + } + }); + + return null; +} diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index 0d7cf7e..8fd29e5 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -404,6 +404,7 @@ const CopyPasteControls3D = ({ actionName: "Action 1", actionType: "worker", loadCapacity: 1, + assemblyCount: 1, loadCount: 1, processTime: 10, triggers: [] diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index f4f5048..d32468a 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -405,6 +405,7 @@ const DuplicationControls3D = ({ actionName: "Action 1", actionType: "worker", loadCapacity: 1, + assemblyCount: 1, loadCount: 1, processTime: 10, triggers: [] diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index 67811c4..187d66a 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useMemo } from 'react'; +import { createContext, useContext, useMemo, useRef } from 'react'; import { createAssetStore, AssetStoreType } from '../../store/builder/useAssetStore'; import { createWallAssetStore, WallAssetStoreType } from '../../store/builder/useWallAssetStore'; @@ -38,6 +38,8 @@ type SceneContextValue = { storageUnitStore: StorageUnitStoreType; humanStore: HumanStoreType; + humanEventManagerRef: React.RefObject; + clearStores: () => void; layout: 'Main Layout' | 'Comparison Layout'; @@ -71,6 +73,8 @@ export function SceneProvider({ const storageUnitStore = useMemo(() => createStorageUnitStore(), []); const humanStore = useMemo(() => createHumanStore(), []); + const humanEventManagerRef = useRef({ humanStates: [] }); + const clearStores = useMemo(() => () => { assetStore.getState().clearAssets(); wallAssetStore.getState().clearWallAssets(); @@ -87,6 +91,7 @@ export function SceneProvider({ vehicleStore.getState().clearVehicles(); storageUnitStore.getState().clearStorageUnits(); humanStore.getState().clearHumans(); + humanEventManagerRef.current.humanStates = []; }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]); const contextValue = useMemo(() => ( @@ -106,6 +111,7 @@ export function SceneProvider({ vehicleStore, storageUnitStore, humanStore, + humanEventManagerRef, clearStores, layout } diff --git a/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx b/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx index 120058b..148c59e 100644 --- a/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx +++ b/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx @@ -1,24 +1,24 @@ import { useEffect } from 'react' -import { useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useFrame } from '@react-three/fiber' import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; // import { findConveyorSubsequence } from '../../../simulator/functions/getConveyorSequencesInProduct'; function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) { + const { materialStore, conveyorStore, productStore } = useSceneContext(); const { getProductById } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { getMaterialsByCurrentModelUuid, materials } = materialStore(); - const { isReset } = useResetButtonStore(); + const { getMaterialsByCurrentModelUuid } = materialStore(); const { setConveyorPaused } = conveyorStore(); - useEffect(() => { + useFrame(() => { const product = getProductById(selectedProduct.productUuid); if (!product) return; const conveyorMaterials = getMaterialsByCurrentModelUuid(conveyor.modelUuid); - if (conveyorMaterials && conveyorMaterials?.length > 0) { + if (conveyorMaterials && conveyorMaterials.length > 0) { const hasPausedMaterials = conveyorMaterials.some(material => material.isPaused); @@ -52,7 +52,7 @@ function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) { // } // }); - }, [materials, conveyor.modelUuid, getMaterialsByCurrentModelUuid, setConveyorPaused, isReset, selectedProduct.productUuid, getProductById]); + }); useEffect(() => { // console.log('conveyor: ', conveyor); @@ -61,7 +61,7 @@ function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) { return ( <> - ) -}; + ); +} export default ConveyorInstance \ No newline at end of file diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts index ac5a478..f023852 100644 --- a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -1,195 +1,142 @@ -import { useEffect, useRef } from 'react'; +import { useEffect } from 'react'; import { useFrame } from '@react-three/fiber'; import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../scene/sceneContext'; import { useProductContext } from '../../products/productContext'; export function useHumanEventManager() { - const { humanStore, productStore, assetStore } = useSceneContext(); - const { getHumanById, setCurrentPhase } = humanStore(); + const { humanStore, productStore, assetStore, humanEventManagerRef } = useSceneContext(); + const { getHumanById, setCurrentPhase, removeCurrentAction } = humanStore(); const { getAssetById } = assetStore(); const { getActionByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const stateRef = useRef({ - humanStates: new Map void)[], - actionQueue: { actionType: "worker" | "assembly", actionUuid: string, actionName: string }[], - isCooldown: boolean - }>(), - callbackCounts: new Map>(), - isMonitoring: false - }); - const { isPlaying } = usePlayButtonStore(); const { isPaused } = usePauseButtonStore(); const { isReset } = useResetButtonStore(); useEffect(() => { - if (isReset) { - stateRef.current.humanStates.clear(); - stateRef.current.callbackCounts.clear(); - stateRef.current.isMonitoring = false; + if ((isReset || !isPlaying) && humanEventManagerRef.current) { + humanEventManagerRef.current.humanStates = []; } - }, [isReset]); + }, [isReset, isPlaying]); const addHumanToMonitor = (humanId: string, callback: () => void, actionUuid: string) => { - const action = getActionByUuid(selectedProduct.productUuid, actionUuid || '') as HumanAction | undefined; + const human = getHumanById(humanId); + const action = getActionByUuid(selectedProduct.productUuid, actionUuid); + if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker') || !humanEventManagerRef.current) return; - if (!action) return; - - const actionType = action.actionType; - if (actionType !== "worker" && actionType !== "assembly") return; - - if (!stateRef.current.callbackCounts.has(humanId)) { - stateRef.current.callbackCounts.set(humanId, new Map()); + let state = humanEventManagerRef.current.humanStates.find(h => h.humanId === humanId); + if (!state) { + state = { humanId, actionQueue: [], isCooldown: false }; + humanEventManagerRef.current.humanStates.push(state); } - const actionCounts = stateRef.current.callbackCounts.get(humanId)!; - if (!actionCounts.has(actionUuid)) { - actionCounts.set(actionUuid, 0); - } - - const currentCount = actionCounts.get(actionUuid)!; - if (actionType === 'worker' && currentCount >= action.loadCount) { + const existingAction = state.actionQueue.find(a => a.actionUuid === actionUuid); + if (existingAction) { + const currentCount = existingAction.count ?? 0; + if (existingAction.actionType === 'worker') { + if (currentCount < existingAction.maxLoadCount) { + existingAction.callback = callback; + existingAction.isMonitored = true; + existingAction.isCompleted = false; + } + } else if (existingAction.actionType === 'assembly') { + if (currentCount < existingAction.maxAssemblyCount) { + existingAction.callback = callback; + existingAction.isMonitored = true; + existingAction.isCompleted = false; + } + } return; } - if (!stateRef.current.humanStates.has(humanId)) { - stateRef.current.humanStates.set(humanId, { - callbacks: [], - actionQueue: [], - isCooldown: false - }); - } - - const humanState = stateRef.current.humanStates.get(humanId)!; - humanState.callbacks.push(callback); - humanState.actionQueue.push({ actionType, actionUuid, actionName: action.actionName }); - - stateRef.current.isMonitoring = true; + state.actionQueue.push({ + actionType: action.actionType, + actionUuid, + actionName: action.actionName, + maxLoadCount: action.loadCount ?? 0, + maxAssemblyCount: action.assemblyCount ?? 0, + count: 0, + isMonitored: true, + isCompleted: false, + callback + }); }; - const removeHumanFromMonitor = (humanId: string) => { - // stateRef.current.humanStates.delete(humanId); + const removeHumanFromMonitor = (humanId: string, actionUuid: string) => { + if (!humanEventManagerRef.current) return; + const state = humanEventManagerRef.current.humanStates.find(h => h.humanId === humanId); + if (!state) return; - if (stateRef.current.humanStates.size === 0) { - stateRef.current.isMonitoring = false; + const action = state.actionQueue.find(a => a.actionUuid === actionUuid); + if (action) { + action.callback = undefined; + action.isMonitored = false; } }; - const getCallbackCount = (humanId: string, actionUuid: string) => { - if (!stateRef.current.callbackCounts.has(humanId)) return 0; - return stateRef.current.callbackCounts.get(humanId)!.get(actionUuid) || 0; - }; - - const incrementCallbackCount = (humanId: string, actionUuid: string) => { - if (!stateRef.current.callbackCounts.has(humanId)) { - stateRef.current.callbackCounts.set(humanId, new Map()); - } - const actionCounts = stateRef.current.callbackCounts.get(humanId)!; - const currentCount = actionCounts.get(actionUuid) || 0; - actionCounts.set(actionUuid, currentCount + 1); - }; - useFrame(() => { - if (!stateRef.current.isMonitoring || !isPlaying || isPaused) return; + if (!humanEventManagerRef.current || humanEventManagerRef.current.humanStates.length === 0 || !isPlaying || isPaused) return; - stateRef.current.humanStates.forEach((humanState, humanId) => { - if (humanState.callbacks.length === 0 || humanState.isCooldown) return; + for (const humanState of humanEventManagerRef.current.humanStates) { + if (humanState.isCooldown) continue; - const actionQueue = humanState.actionQueue; - if (!actionQueue || actionQueue.length === 0) return; + const { humanId, actionQueue } = humanState; + if (!actionQueue || actionQueue.length === 0) continue; + + const action = actionQueue.find(a => !a.isCompleted); + if (!action || !action.isMonitored || !action.callback) continue; - const { actionType: expectedActionType, actionUuid } = actionQueue[0]; const human = getHumanById(humanId); const humanAsset = getAssetById(humanId); - const action = getActionByUuid(selectedProduct.productUuid, actionUuid) as HumanAction | undefined; + const currentAction = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '') as HumanAction | undefined; - if (!humanAsset || !human || !action || action.actionType !== expectedActionType) return; - - const currentCount = getCallbackCount(humanId, actionUuid); - - const currentAction = getActionByUuid(selectedProduct.productUuid, human.currentAction?.actionUuid || '') as HumanAction | undefined; + if (!human || !humanAsset || !currentAction) continue; + if (human.isActive || human.state !== "idle" || humanAsset.animationState?.current !== 'idle') continue; let conditionMet = false; - if (expectedActionType === "worker") { - if (currentAction && currentAction.actionType === 'worker' && currentCount >= currentAction.loadCount) { - humanState.callbacks.shift(); - actionQueue.shift(); - if (humanState.callbacks.length === 0) { - removeHumanFromMonitor(humanId); - } - return; + if (currentAction.actionType === 'worker') { + if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) { + conditionMet = true; + } else if (action.actionType === 'assembly') { + conditionMet = true; } - - if (currentAction && currentAction.actionType === 'worker') { - conditionMet = ( - !human.isActive && - human.state === "idle" && - humanAsset.animationState?.current === 'idle' && - human.currentLoad < currentAction.loadCapacity - ); - - if (conditionMet && actionUuid !== human.currentAction?.actionUuid) { - setCurrentPhase(human.modelUuid, 'init'); - } - } else { - conditionMet = ( - !human.isActive && - human.state === "idle" && - humanAsset.animationState?.current === 'idle' - ); - if (conditionMet && actionUuid !== human.currentAction?.actionUuid) { - setCurrentPhase(human.modelUuid, 'init'); - } - } - - } else if (expectedActionType === "assembly") { - if (currentAction && currentAction.actionType === 'worker') { - conditionMet = ( - !human.isActive && - human.state === "idle" && - humanAsset.animationState?.current === 'idle' && - human.currentLoad < currentAction.loadCapacity - ); - if (conditionMet && actionUuid !== human.currentAction?.actionUuid) { - setCurrentPhase(human.modelUuid, 'init'); - } - } else { - conditionMet = ( - !human.isActive && - human.state === "idle" && - humanAsset.animationState?.current === 'idle' - ) + } else if (currentAction.actionType === 'assembly') { + if (action.actionType === 'assembly') { + conditionMet = true; + } else if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) { + conditionMet = true; } } if (conditionMet) { - const callback = humanState.callbacks.shift(); - actionQueue.shift(); - - if (callback) { - callback(); - incrementCallbackCount(humanId, actionUuid); + if (action.actionUuid !== human.currentAction?.actionUuid) { + setCurrentPhase(human.modelUuid, 'init'); + removeCurrentAction(human.modelUuid); } - if (humanState.callbacks.length === 0) { - removeHumanFromMonitor(humanId); - } else { - humanState.isCooldown = true; - setTimeout(() => { - humanState.isCooldown = false; - }, 1000); + action.callback(); + action.count = (action.count ?? 0) + 1; + action.isMonitored = false; + if ((action.actionType === 'worker' && action.count >= action.maxLoadCount) || + (action.actionType === 'assembly' && action.count >= action.maxAssemblyCount)) { + action.isCompleted = true; } + humanState.isCooldown = true; + setTimeout(() => { + humanState.isCooldown = false; + }, 1000); + + removeHumanFromMonitor(human.modelUuid, action.actionUuid); } - }); - }); + } + }, 0); return { addHumanToMonitor, - removeHumanFromMonitor, + removeHumanFromMonitor }; } \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/animator/assemblerAnimator.tsx b/app/src/modules/simulation/human/instances/animator/assemblerAnimator.tsx new file mode 100644 index 0000000..d891d8f --- /dev/null +++ b/app/src/modules/simulation/human/instances/animator/assemblerAnimator.tsx @@ -0,0 +1,172 @@ +import { useEffect, useRef, useState } from 'react'; +import { useFrame, useThree } from '@react-three/fiber'; +import * as THREE from 'three'; +import { Line } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../products/productContext'; + +interface AssemblerAnimatorProps { + path: [number, number, number][]; + handleCallBack: () => void; + reset: () => void; + human: HumanStatus; +} + +function AssemblerAnimator({ path, handleCallBack, human, reset }: Readonly) { + const { humanStore, assetStore, productStore } = useSceneContext(); + const { getActionByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { getHumanById } = humanStore(); + const { setCurrentAnimation } = assetStore(); + const { isPaused } = usePauseButtonStore(); + const { isPlaying } = usePlayButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const { isReset, setReset } = useResetButtonStore(); + const progressRef = useRef(0); + const completedRef = useRef(false); + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>((action as HumanAction)?.assemblyPoint?.rotation || [0, 0, 0]); + const [restRotation, setRestingRotation] = useState(true); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + const { scene } = useThree(); + + useEffect(() => { + if (!human.currentAction?.actionUuid) return; + if (human.currentPhase === 'init-assembly' && path.length > 0) { + setCurrentPath(path); + setObjectRotation((action as HumanAction)?.assemblyPoint?.rotation ?? null); + } + }, [human.currentPhase, path, objectRotation, selectedProduct, human.currentAction?.actionUuid]); + + useEffect(() => { + completedRef.current = false; + }, [currentPath]); + + useEffect(() => { + if (isReset || !isPlaying) { + reset(); + setCurrentPath([]); + completedRef.current = false; + progressRef.current = 0; + setReset(false); + setRestingRotation(true); + const object = scene.getObjectByProperty('uuid', human.modelUuid); + const humanData = getHumanById(human.modelUuid); + if (object && humanData) { + object.position.set(humanData.position[0], humanData.position[1], humanData.position[2]); + object.rotation.set(humanData.rotation[0], humanData.rotation[1], humanData.rotation[2]); + } + } + }, [isReset, isPlaying]); + + const lastTimeRef = useRef(performance.now()); + + useFrame(() => { + const now = performance.now(); + const delta = (now - lastTimeRef.current) / 1000; + lastTimeRef.current = now; + + const object = scene.getObjectByProperty('uuid', human.modelUuid); + if (!object || currentPath.length < 2) return; + if (isPaused || !isPlaying) return; + + let totalDistance = 0; + const distances = []; + let accumulatedDistance = 0; + let index = 0; + const rotationSpeed = 1.5; + + for (let i = 0; i < currentPath.length - 1; i++) { + const start = new THREE.Vector3(...currentPath[i]); + const end = new THREE.Vector3(...currentPath[i + 1]); + const segmentDistance = start.distanceTo(end); + distances.push(segmentDistance); + totalDistance += segmentDistance; + } + + while (index < distances.length && progressRef.current > accumulatedDistance + distances[index]) { + accumulatedDistance += distances[index]; + index++; + } + + if (index < distances.length) { + const start = new THREE.Vector3(...currentPath[index]); + const end = new THREE.Vector3(...currentPath[index + 1]); + const segmentDistance = distances[index]; + + 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); + + const angle = object.quaternion.angleTo(targetQuaternion); + if (angle < 0.01) { + object.quaternion.copy(targetQuaternion); + } else { + const step = rotationSpeed * delta * speed * human.speed; + object.quaternion.rotateTowards(targetQuaternion, step); + } + + const isAligned = angle < 0.01; + + if (isAligned) { + progressRef.current += delta * (speed * human.speed); + const t = (progressRef.current - accumulatedDistance) / segmentDistance; + const position = start.clone().lerp(end, t); + object.position.copy(position); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + } else { + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + } + } + + if (progressRef.current >= totalDistance) { + if (restRotation && objectRotation) { + const targetEuler = new THREE.Euler(0, objectRotation[1], 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); + setRestingRotation(false); + } else { + const step = rotationSpeed * delta * speed * human.speed; + object.quaternion.rotateTowards(targetQuaternion, step); + } + + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + return; + } + } + + if (progressRef.current >= totalDistance) { + setRestingRotation(true); + progressRef.current = 0; + setCurrentPath([]); + handleCallBack(); + } + }); + + return ( + <> + {currentPath.length > 0 && ( + + + {currentPath.map((point, index) => ( + + + + + ))} + + )} + + ); +} + +export default AssemblerAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx index 9cf0154..178cc4e 100644 --- a/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx @@ -50,7 +50,7 @@ const MaterialAnimator = ({ human }: { human: HumanStatus; }) => { return ( <> - {hasLoad && (action as HumanAction).actionType === 'worker' && human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup') && ( + {hasLoad && action && (action as HumanAction).actionType === 'worker' && human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup') && ( void; reset: () => void; @@ -14,7 +14,7 @@ interface HumanAnimatorProps { human: HumanStatus; } -function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly) { +function WorkerAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly) { const { humanStore, assetStore, productStore } = useSceneContext(); const { getActionByUuid } = productStore(); const { selectedProductStore } = useProductContext(); @@ -29,7 +29,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce const movingForward = useRef(true); const completedRef = useRef(false); const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>((action as HumanAction)?.pickUpPoint?.rotation || [0, 0, 0]) + const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>((action as HumanAction)?.pickUpPoint?.rotation || [0, 0, 0]); const [restRotation, setRestingRotation] = useState(true); const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); const { scene } = useThree(); @@ -39,15 +39,12 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); if (human.currentPhase === 'init-pickup' && path.length > 0) { setCurrentPath(path); - setObjectRotation((action as HumanAction).pickUpPoint?.rotation ?? null) - } else if (human.currentPhase === 'init-assembly' && path.length > 0) { - setObjectRotation((action as HumanAction)?.assemblyPoint?.rotation ?? null) - setCurrentPath(path); + setObjectRotation((action as HumanAction).pickUpPoint?.rotation ?? null); } else if (human.currentPhase === 'pickup-drop' && path.length > 0) { - setObjectRotation((action as HumanAction)?.dropPoint?.rotation ?? null) + setObjectRotation((action as HumanAction)?.dropPoint?.rotation ?? null); setCurrentPath(path); } else if (human.currentPhase === 'drop-pickup' && path.length > 0) { - setObjectRotation((action as HumanAction)?.pickUpPoint?.rotation ?? null) + setObjectRotation((action as HumanAction)?.pickUpPoint?.rotation ?? null); setCurrentPath(path); } }, [human.currentPhase, path, objectRotation, selectedProduct, human.currentAction?.actionUuid]); @@ -72,7 +69,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce object.rotation.set(humanData.rotation[0], humanData.rotation[1], humanData.rotation[2]); } } - }, [isReset, isPlaying]) + }, [isReset, isPlaying]); const lastTimeRef = useRef(performance.now()); @@ -109,7 +106,9 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce const end = new THREE.Vector3(...currentPath[index + 1]); const segmentDistance = distances[index]; - const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0))); + 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); @@ -118,13 +117,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce object.quaternion.copy(targetQuaternion); } else { const step = rotationSpeed * delta * speed * human.speed; - const angle = object.quaternion.angleTo(targetQuaternion); - - if (angle < step) { - object.quaternion.copy(targetQuaternion); - } else { - object.quaternion.rotateTowards(targetQuaternion, step); - } + object.quaternion.rotateTowards(targetQuaternion, step); } const isAligned = angle < 0.01; @@ -134,13 +127,13 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce const t = (progressRef.current - accumulatedDistance) / segmentDistance; const position = start.clone().lerp(end, t); object.position.copy(position); - if (human.currentMaterials.length > 0 && action?.actionType === 'worker' && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) { + if (human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) { setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true); } else { setCurrentAnimation(human.modelUuid, 'walking', true, true, true); } } else { - if (human.currentMaterials.length > 0 && action?.actionType === 'worker' && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) { + if (human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) { setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true); } else { setCurrentAnimation(human.modelUuid, 'idle', true, true, true); @@ -151,7 +144,6 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce if (progressRef.current >= totalDistance) { if (restRotation && objectRotation) { const targetEuler = new THREE.Euler(0, objectRotation[1], 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); @@ -162,21 +154,14 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce setRestingRotation(false); } else { const step = rotationSpeed * delta * speed * human.speed; - const angle = object.quaternion.angleTo(targetQuaternion); - - if (angle < step) { - object.quaternion.copy(targetQuaternion); - } else { - object.quaternion.rotateTowards(targetQuaternion, step); - } - } - - if (human.currentMaterials.length > 0) { - setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true); - } else { - setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + object.quaternion.rotateTowards(targetQuaternion, step); } + setCurrentAnimation( + human.modelUuid, + human.currentMaterials.length > 0 ? 'idle_with_box' : 'idle', + true, true, true + ); return; } } @@ -196,7 +181,6 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce return ( <> {currentPath.length > 0 && ( - // helper {currentPath.map((point, index) => ( @@ -208,7 +192,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce )} - ) + ); } -export default HumanAnimator \ No newline at end of file +export default WorkerAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/actions/assemberInstance.tsx b/app/src/modules/simulation/human/instances/instance/actions/assemberInstance.tsx new file mode 100644 index 0000000..881a2ec --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/actions/assemberInstance.tsx @@ -0,0 +1,221 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; +import { useThree } from '@react-three/fiber'; +import { NavMeshQuery } from '@recast-navigation/core'; +import { useNavMesh } from '../../../../../../store/builder/store'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../../store/usePlayButtonStore'; +import { useTriggerHandler } from '../../../../triggers/triggerHandler/useTriggerHandler'; +import { useSceneContext } from '../../../../../scene/sceneContext'; +import { useProductContext } from '../../../../products/productContext'; + +import AssemblerAnimator from '../../animator/assemblerAnimator'; + +function AssemblerInstance({ human }: { human: HumanStatus }) { + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); + const { scene } = useThree(); + const { assetStore, materialStore, humanStore, productStore } = useSceneContext(); + const { setMaterial } = materialStore(); + const { triggerPointActions } = useTriggerHandler(); + const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore(); + const { getActionByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { setHumanActive, setHumanState, decrementHumanLoad, removeLastMaterial, setCurrentPhase } = humanStore(); + + const [path, setPath] = useState<[number, number, number][]>([]); + const isPausedRef = useRef(false); + const isSpeedRef = useRef(0); + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + const humanAsset = getAssetById(human.modelUuid); + const processStartTimeRef = useRef(null); + const processTimeRef = useRef(0); + const processAnimationIdRef = useRef(null); + const accumulatedPausedTimeRef = useRef(0); + const lastPauseTimeRef = useRef(null); + const hasLoggedHalfway = useRef(false); + const hasLoggedCompleted = useRef(false); + + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); + + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + + const computePath = useCallback((start: [number, number, number], end: [number, number, number]) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + let startPoint = new THREE.Vector3(start[0], start[1], start[2]); + let endPoint = new THREE.Vector3(end[0], end[1], end[2]); + const { path: segmentPath } = navMeshQuery.computePath(startPoint, endPoint); + if ( + segmentPath.length > 0 && + Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(endPoint.x) && + Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(endPoint.z) + ) { + return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; + } else { + console.log("There is no path here...Choose valid path") + const { path: segmentPaths } = navMeshQuery.computePath(startPoint, startPoint); + return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; + } + } catch { + console.error("Failed to compute path"); + return []; + } + }, [navMesh]); + + function humanStatus(modelId: string, status: string) { + // console.log(`${modelId} , ${status}`); + } + + function reset() { + setCurrentPhase(human.modelUuid, 'init'); + setHumanActive(human.modelUuid, false); + setHumanState(human.modelUuid, 'idle'); + resetAnimation(human.modelUuid); + setPath([]); + 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]); + object.rotation.set(human.rotation[0], human.rotation[1], human.rotation[2]); + } + } + + useEffect(() => { + if (isPlaying) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + + if (!action || !(action as HumanAction).assemblyPoint || (action as HumanAction).actionType === 'worker') return; + + if (!human.isActive && human.state === 'idle' && human.currentPhase === 'init') { + const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); + if (!humanMesh) return; + + const toPickupPath = computePath(humanMesh.position.toArray(), (action as HumanAction)?.assemblyPoint?.position || [0, 0, 0]); + setPath(toPickupPath); + setHumanState(human.modelUuid, 'idle'); + setCurrentPhase(human.modelUuid, 'init-assembly'); + 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' && human.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(human.modelUuid, 'assembling'); + setHumanActive(human.modelUuid, true); + + processStartTimeRef.current = performance.now(); + processTimeRef.current = (action as HumanAction).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 ((action as HumanAction).assemblyPoint && human.currentPhase === 'assembling') { + setHumanState(human.modelUuid, 'idle'); + setCurrentPhase(human.modelUuid, 'waiting'); + 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((action as HumanAction), material.materialId); + } + } + } + } else { + reset() + } + }, [human, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); + + const trackAssemblyProcess = useCallback(() => { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + + const now = performance.now(); + + if (!processStartTimeRef.current || !(action as HumanAction).processTime || !action) { + 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 = ((action as HumanAction).processTime || 1) * 1000; + + if (elapsed >= totalProcessTimeMs / 2 && !hasLoggedHalfway.current) { + hasLoggedHalfway.current = true; + if (human.currentMaterials.length > 0) { + setMaterial(human.currentMaterials[0].materialId, (action as HumanAction).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.currentMaterials]); + + function handleCallBack() { + if (human.currentPhase === 'init-assembly') { + setCurrentPhase(human.modelUuid, 'waiting'); + setHumanState(human.modelUuid, 'idle'); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached assembly point, waiting for material'); + setPath([]); + } + } + + return ( + <> + + + ) +} + +export default AssemblerInstance; \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/actions/workerInstance.tsx b/app/src/modules/simulation/human/instances/instance/actions/workerInstance.tsx new file mode 100644 index 0000000..c5645af --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/actions/workerInstance.tsx @@ -0,0 +1,615 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; +import { useThree } from '@react-three/fiber'; +import { NavMeshQuery } from '@recast-navigation/core'; +import { useNavMesh } from '../../../../../../store/builder/store'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../../store/usePlayButtonStore'; +import { useTriggerHandler } from '../../../../triggers/triggerHandler/useTriggerHandler'; +import { useSceneContext } from '../../../../../scene/sceneContext'; +import { useProductContext } from '../../../../products/productContext'; + +import WorkerAnimator from '../../animator/workerAnimator'; + +function WorkerInstance({ human }: { human: HumanStatus }) { + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); + const { scene } = useThree(); + const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); + const { removeMaterial, setEndTime, setIsVisible } = materialStore(); + const { getStorageUnitById } = storageUnitStore(); + const { getArmBotById } = armBotStore(); + const { getConveyorById } = conveyorStore(); + const { getVehicleById } = vehicleStore(); + const { getMachineById } = machineStore(); + const { triggerPointActions } = useTriggerHandler(); + const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore(); + const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { setHumanActive, setHumanState, clearCurrentMaterials, setHumanLoad, setHumanScheduled, decrementHumanLoad, removeLastMaterial, incrementIdleTime, incrementActiveTime, resetTime, setCurrentPhase } = humanStore(); + + const [path, setPath] = useState<[number, number, number][]>([]); + const pauseTimeRef = useRef(null); + const idleTimeRef = useRef(0); + const activeTimeRef = useRef(0); + const isPausedRef = useRef(false); + const isSpeedRef = useRef(0); + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + const previousTimeRef = useRef(null); + const animationFrameIdRef = useRef(null); + const humanAsset = getAssetById(human.modelUuid); + + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); + + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + + const computePath = useCallback((start: [number, number, number], end: [number, number, number]) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + let startPoint = new THREE.Vector3(start[0], start[1], start[2]); + let endPoint = new THREE.Vector3(end[0], end[1], end[2]); + const { path: segmentPath } = navMeshQuery.computePath(startPoint, endPoint); + if ( + segmentPath.length > 0 && + Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(endPoint.x) && + Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(endPoint.z) + ) { + return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; + } else { + console.log("There is no path here...Choose valid path") + const { path: segmentPaths } = navMeshQuery.computePath(startPoint, startPoint); + return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; + } + } catch { + console.error("Failed to compute path"); + return []; + } + }, [navMesh]); + + function humanStatus(modelId: string, status: string) { + // console.log(`${modelId} , ${status}`); + } + + function reset() { + setCurrentPhase(human.modelUuid, 'init'); + setHumanActive(human.modelUuid, false); + setHumanState(human.modelUuid, 'idle'); + setHumanScheduled(human.modelUuid, false); + setHumanLoad(human.modelUuid, 0); + resetAnimation(human.modelUuid); + setPath([]); + isPausedRef.current = false; + pauseTimeRef.current = 0; + resetTime(human.modelUuid) + activeTimeRef.current = 0 + idleTimeRef.current = 0 + previousTimeRef.current = null + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current) + animationFrameIdRef.current = null + } + const object = scene.getObjectByProperty('uuid', human.modelUuid); + if (object && human) { + object.position.set(human.position[0], human.position[1], human.position[2]); + object.rotation.set(human.rotation[0], human.rotation[1], human.rotation[2]); + } + } + + useEffect(() => { + if (isPlaying) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + if (!action || action.actionType !== 'worker' || !action.pickUpPoint || !action.dropPoint) return; + + if (!human.isActive && human.state === 'idle' && human.currentPhase === 'init') { + const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); + if (!humanMesh) return; + + const toPickupPath = computePath(humanMesh.position.toArray(), action?.pickUpPoint?.position || [0, 0, 0]); + + setPath(toPickupPath); + setCurrentPhase(human.modelUuid, 'init-pickup'); + setHumanState(human.modelUuid, 'running'); + setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + humanStatus(human.modelUuid, 'Started from init, heading to pickup'); + return; + } else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'picking') { + if (humanAsset && human.currentLoad === action.loadCapacity && human.currentMaterials.length > 0 && human.currentLoad > 0 && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState?.isCompleted) { + if (action.pickUpPoint && action.dropPoint) { + const toDrop = computePath(action.pickUpPoint.position || [0, 0, 0], action.dropPoint.position || [0, 0, 0]); + setPath(toDrop); + setCurrentPhase(human.modelUuid, 'pickup-drop'); + setHumanState(human.modelUuid, 'running'); + setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true); + humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point'); + } + } else if (human.currentMaterials.length > 0 && human.currentLoad > 0 && humanAsset?.animationState?.current !== 'pickup') { + if (human.currentMaterials[0]?.materialId) { + setIsVisible(human.currentMaterials[0]?.materialId, false); + } + setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); + } + } else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'dropping' && human.currentLoad === 0) { + if (action.pickUpPoint && action.dropPoint) { + const dropToPickup = computePath(action.dropPoint.position || [0, 0, 0], action.pickUpPoint.position || [0, 0, 0]); + setPath(dropToPickup); + setCurrentPhase(human.modelUuid, 'drop-pickup'); + setHumanState(human.modelUuid, 'running'); + setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + humanStatus(human.modelUuid, 'Started from dropping point, heading to pickup point'); + } + } + } else { + reset() + } + }, [human, human.currentAction, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); + + function handleCallBack() { + if (human.currentPhase === 'init-pickup') { + setCurrentPhase(human.modelUuid, 'picking'); + setHumanState(human.modelUuid, 'idle'); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached pickup point, waiting for material'); + setPath([]); + } else if (human.currentPhase === 'pickup-drop') { + setCurrentPhase(human.modelUuid, 'dropping'); + setHumanState(human.modelUuid, 'idle'); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + humanStatus(human.modelUuid, 'Reached drop point'); + setPath([]); + } else if (human.currentPhase === 'drop-pickup') { + setCurrentPhase(human.modelUuid, 'picking'); + setHumanState(human.modelUuid, 'idle'); + setHumanActive(human.modelUuid, false); + setHumanScheduled(human.modelUuid, false); + setPath([]); + clearCurrentMaterials(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached pickup point again, cycle complete'); + } + } + + function startUnloadingProcess() { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { + if ((action as HumanAction).triggers.length > 0) { + const trigger = getTriggerByUuid(selectedProduct.productUuid, (action as HumanAction).triggers[0]?.triggerUuid); + const model = getEventByModelUuid(selectedProduct.productUuid, trigger?.triggeredAsset?.triggeredModel?.modelUuid || ''); + + if (trigger && model) { + if (model.type === 'transfer') { + if (action) { + handleMaterialDropToConveyor(model); + } + } else if (model.type === 'machine') { + if (action) { + handleMaterialDropToMachine(model); + } + } else if (model.type === 'roboticArm') { + if (action) { + handleMaterialDropToArmBot(model); + } + } else if (model.type === 'storageUnit') { + if (action) { + handleMaterialDropToStorageUnit(model); + } + } else if (model.type === 'vehicle') { + if (action) { + handleMaterialDropToVehicle(model); + } + } + } else { + const droppedMaterial = human.currentLoad; + handleMaterialDropByDefault(droppedMaterial); + } + } else { + const droppedMaterial = human.currentLoad; + handleMaterialDropByDefault(droppedMaterial); + } + } else { + requestAnimationFrame(startUnloadingProcess); + } + } + + function handleMaterialDropToStorageUnit(model: StorageEventSchema) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + const humanAsset = getAssetById(human.modelUuid); + if (model && humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.isCompleted) { + if (model.point.action.actionType === 'store') { + loopMaterialDropToStorage( + human.modelUuid, + human.currentLoad, + model.modelUuid, + model.point.action.storageCapacity, + (action as HumanAction) + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToStorage( + humanId: string, + humanCurrentLoad: number, + storageUnitId: string, + storageMaxCapacity: number, + action: HumanAction + ) { + const storageUnit = getStorageUnitById(storageUnitId); + const humanAsset = getAssetById(human.modelUuid); + + if (!storageUnit || humanCurrentLoad <= 0 || storageUnit.currentLoad >= storageMaxCapacity) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0 && storageUnit.currentLoad < storageMaxCapacity) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextDrop = () => { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { + loopMaterialDropToStorage( + humanId, + humanCurrentLoad, + storageUnitId, + storageMaxCapacity, + action + ); + } else { + requestAnimationFrame(waitForNextDrop); + } + }; + waitForNextDrop(); + } + } + + function handleMaterialDropToConveyor(model: ConveyorEventSchema) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.isCompleted) { + const conveyor = getConveyorById(model.modelUuid); + if (conveyor) { + loopMaterialDropToConveyor( + human.modelUuid, + human.currentLoad, + conveyor.modelUuid, + (action as HumanAction) + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToConveyor( + humanId: string, + humanCurrentLoad: number, + conveyorId: string, + action: HumanAction + ) { + const conveyor = getConveyorById(conveyorId); + const humanAsset = getAssetById(human.modelUuid); + + if (!conveyor || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextDrop = () => { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToConveyor( + humanId, + humanCurrentLoad, + conveyorId, + action + ); + } else { + requestAnimationFrame(waitForNextDrop); + } + }; + waitForNextDrop(); + } + } + + function handleMaterialDropToArmBot(model: RoboticArmEventSchema) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { + const armBot = getArmBotById(model.modelUuid); + if (armBot && armBot.state === 'idle' && !armBot.isActive) { + loopMaterialDropToArmBot( + human.modelUuid, + human.currentLoad, + model.modelUuid, + (action as HumanAction) + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToArmBot( + humanId: string, + humanCurrentLoad: number, + armBotId: string, + action: HumanAction + ) { + const armBot = getArmBotById(armBotId); + const humanAsset = getAssetById(human.modelUuid); + + if (!armBot || armBot.state !== 'idle' || armBot.isActive || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextTransfer = () => { + const currentArmBot = getArmBotById(armBotId); + if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToArmBot( + humanId, + humanCurrentLoad, + armBotId, + action + ); + } else { + requestAnimationFrame(waitForNextTransfer); + } + } else { + requestAnimationFrame(waitForNextTransfer); + } + }; + waitForNextTransfer(); + } + } + + function handleMaterialDropToVehicle(model: VehicleEventSchema) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { + const vehicle = getVehicleById(model.modelUuid); + if (vehicle && vehicle.state === 'idle' && !vehicle.isActive) { + loopMaterialDropToVehicle( + human.modelUuid, + human.currentLoad, + model.modelUuid, + (action as HumanAction) + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToVehicle( + humanId: string, + humanCurrentLoad: number, + vehicleId: string, + action: HumanAction + ) { + const vehicle = getVehicleById(vehicleId); + const humanAsset = getAssetById(human.modelUuid); + + if (!vehicle || vehicle.state !== 'idle' || vehicle.isActive || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextTransfer = () => { + const currentVehicle = getVehicleById(vehicleId); + if (currentVehicle && currentVehicle.state === 'idle' && !currentVehicle.isActive) { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToVehicle( + humanId, + humanCurrentLoad, + vehicleId, + action + ); + } else { + requestAnimationFrame(waitForNextTransfer); + } + } else { + requestAnimationFrame(waitForNextTransfer); + } + }; + waitForNextTransfer(); + } + } + + function handleMaterialDropToMachine(model: MachineEventSchema) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { + const machine = getMachineById(model.modelUuid); + if (machine && machine.state === 'idle' && !machine.isActive) { + loopMaterialDropToMachine( + human.modelUuid, + human.currentLoad, + model.modelUuid, + (action as HumanAction) + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToMachine( + humanId: string, + humanCurrentLoad: number, + machineId: string, + action: HumanAction + ) { + const machine = getMachineById(machineId); + const humanAsset = getAssetById(human.modelUuid); + + if (!machine || machine.state !== 'idle' || machine.isActive || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextTransfer = () => { + const currentMachine = getMachineById(machineId); + if (currentMachine && currentMachine.state === 'idle' && !currentMachine.isActive) { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToMachine( + humanId, + humanCurrentLoad, + machineId, + action + ); + } else { + requestAnimationFrame(waitForNextTransfer); + } + } else { + requestAnimationFrame(waitForNextTransfer); + } + }; + waitForNextTransfer(); + } + } + + function handleMaterialDropByDefault(droppedMaterial: number) { + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + if (humanAsset?.animationState?.isCompleted) { + const remainingMaterials = droppedMaterial - 1; + decrementHumanLoad(human.modelUuid, 1); + const material = removeLastMaterial(human.modelUuid); + + if (material) { + setEndTime(material.materialId, performance.now()); + removeMaterial(material.materialId); + } + + if (remainingMaterials > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + requestAnimationFrame(() => handleMaterialDropByDefault(remainingMaterials)); + } + return; + } + + requestAnimationFrame(() => handleMaterialDropByDefault(droppedMaterial)); + } + + return ( + <> + + + ) +} + +export default WorkerInstance; \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index 71e11ef..8d1c51f 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -1,36 +1,20 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; -import * as THREE from 'three'; -import { useThree } from '@react-three/fiber'; -import { NavMeshQuery } from '@recast-navigation/core'; -import { useNavMesh } from '../../../../../store/builder/store'; +import { useEffect, useRef } from 'react'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; -import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; -import HumanAnimator from '../animator/humanAnimator'; import MaterialAnimator from '../animator/materialAnimator'; +import AssemblerInstance from './actions/assemberInstance'; +import WorkerInstance from './actions/workerInstance'; function HumanInstance({ human }: { human: HumanStatus }) { - const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); - const { scene } = useThree(); - const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); - const { removeMaterial, setEndTime, setMaterial, setIsVisible } = materialStore(); - const { getStorageUnitById } = storageUnitStore(); - const { getArmBotById } = armBotStore(); - const { getConveyorById } = conveyorStore(); - const { getVehicleById } = vehicleStore(); - const { getMachineById } = machineStore(); - const { triggerPointActions } = useTriggerHandler(); - const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore(); - const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); + const { humanStore, productStore } = useSceneContext(); + const { getActionByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { setHumanActive, setHumanState, clearCurrentMaterials, setHumanLoad, setHumanScheduled, decrementHumanLoad, removeLastMaterial, incrementIdleTime, incrementActiveTime, resetTime, setCurrentPhase } = humanStore(); + const { incrementIdleTime, incrementActiveTime } = humanStore(); - const [path, setPath] = useState<[number, number, number][]>([]); - const pauseTimeRef = useRef(null); const idleTimeRef = useRef(0); const activeTimeRef = useRef(0); const isPausedRef = useRef(false); @@ -39,14 +23,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { const { isPaused } = usePauseButtonStore(); const previousTimeRef = useRef(null); const animationFrameIdRef = useRef(null); - const humanAsset = getAssetById(human.modelUuid); - const processStartTimeRef = useRef(null); - const processTimeRef = useRef(0); - const processAnimationIdRef = useRef(null); - const accumulatedPausedTimeRef = useRef(0); - const lastPauseTimeRef = useRef(null); - const hasLoggedHalfway = useRef(false); - const hasLoggedCompleted = useRef(false); + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); useEffect(() => { isPausedRef.current = isPaused; @@ -56,259 +33,6 @@ function HumanInstance({ human }: { human: HumanStatus }) { isSpeedRef.current = speed; }, [speed]); - const computePath = useCallback((start: [number, number, number], end: [number, number, number]) => { - try { - const navMeshQuery = new NavMeshQuery(navMesh); - let startPoint = new THREE.Vector3(start[0], start[1], start[2]); - let endPoint = new THREE.Vector3(end[0], end[1], end[2]); - const { path: segmentPath } = navMeshQuery.computePath(startPoint, endPoint); - if ( - segmentPath.length > 0 && - Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(endPoint.x) && - Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(endPoint.z) - ) { - return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; - } else { - console.log("There is no path here...Choose valid path") - const { path: segmentPaths } = navMeshQuery.computePath(startPoint, startPoint); - return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; - } - } catch { - console.error("Failed to compute path"); - return []; - } - }, [navMesh]); - - function humanStatus(modelId: string, status: string) { - // console.log(`${modelId} , ${status}`); - } - - function reset() { - setCurrentPhase(human.modelUuid, 'init'); - setHumanActive(human.modelUuid, false); - setHumanState(human.modelUuid, 'idle'); - setHumanScheduled(human.modelUuid, false); - setHumanLoad(human.modelUuid, 0); - resetAnimation(human.modelUuid); - setPath([]); - isPausedRef.current = false; - pauseTimeRef.current = 0; - resetTime(human.modelUuid) - activeTimeRef.current = 0 - idleTimeRef.current = 0 - previousTimeRef.current = null - if (animationFrameIdRef.current !== null) { - 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]); - object.rotation.set(human.rotation[0], human.rotation[1], human.rotation[2]); - } - } - - useEffect(() => { - if (isPlaying) { - const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - - if (!action || !(action as HumanAction).assemblyPoint || (action as HumanAction).actionType === 'worker') return; - - if (!human.isActive && human.state === 'idle' && human.currentPhase === 'init') { - - const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); - if (!humanMesh) return; - - const toPickupPath = computePath(humanMesh.position.toArray(), (action as HumanAction)?.assemblyPoint?.position || [0, 0, 0]); - setPath(toPickupPath); - setHumanState(human.modelUuid, 'idle'); - setCurrentPhase(human.modelUuid, 'init-assembly'); - 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' && human.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(human.modelUuid, 'assembling'); - setHumanActive(human.modelUuid, true); - - processStartTimeRef.current = performance.now(); - processTimeRef.current = (action as HumanAction).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 ((action as HumanAction).assemblyPoint && human.currentPhase === 'assembling') { - setHumanState(human.modelUuid, 'idle'); - setCurrentPhase(human.modelUuid, 'waiting'); - setHumanActive(human.modelUuid, false); - setHumanScheduled(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((action as HumanAction), material.materialId); - } - } - } - - } else { - reset() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [human, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); - - const trackAssemblyProcess = useCallback(() => { - const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - - const now = performance.now(); - - if (!processStartTimeRef.current || !(action as HumanAction).processTime || !action) { - 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 = ((action as HumanAction).processTime || 1) * 1000; - - if (elapsed >= totalProcessTimeMs / 2 && !hasLoggedHalfway.current) { - hasLoggedHalfway.current = true; - if (human.currentMaterials.length > 0) { - setMaterial(human.currentMaterials[0].materialId, (action as HumanAction).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.currentMaterials]); - - useEffect(() => { - if (isPlaying) { - const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - if (!action || action.actionType !== 'worker' || !action.pickUpPoint || !action.dropPoint) return; - - if (!human.isActive && human.state === 'idle' && human.currentPhase === 'init') { - - const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); - if (!humanMesh) return; - - const toPickupPath = computePath(humanMesh.position.toArray(), action?.pickUpPoint?.position || [0, 0, 0]); - setPath(toPickupPath); - setCurrentPhase(human.modelUuid, 'init-pickup'); - setHumanState(human.modelUuid, 'running'); - setHumanActive(human.modelUuid, true); - setCurrentAnimation(human.modelUuid, 'walking', true, true, true); - humanStatus(human.modelUuid, 'Started from init, heading to pickup'); - return; - } else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'picking') { - if (humanAsset && human.currentLoad === action.loadCapacity && human.currentMaterials.length > 0 && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState?.isCompleted) { - if (action.pickUpPoint && action.dropPoint) { - const toDrop = computePath(action.pickUpPoint.position || [0, 0, 0], action.dropPoint.position || [0, 0, 0]); - setPath(toDrop); - setCurrentPhase(human.modelUuid, 'pickup-drop'); - setHumanState(human.modelUuid, 'running'); - setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true); - humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point'); - } - } else if (human.currentMaterials.length > 0 && humanAsset?.animationState?.current !== 'pickup') { - if (human.currentMaterials[0]?.materialId) { - setIsVisible(human.currentMaterials[0]?.materialId, false); - } - setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); - } - } else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'dropping' && human.currentLoad === 0) { - if (action.pickUpPoint && action.dropPoint) { - const dropToPickup = computePath(action.dropPoint.position || [0, 0, 0], action.pickUpPoint.position || [0, 0, 0]); - setPath(dropToPickup); - setCurrentPhase(human.modelUuid, 'drop-pickup'); - setHumanState(human.modelUuid, 'running'); - setHumanActive(human.modelUuid, true); - setCurrentAnimation(human.modelUuid, 'walking', true, true, true); - humanStatus(human.modelUuid, 'Started from dropping point, heading to pickup point'); - } - } - - } else { - reset() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [human, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); - - function handleCallBack() { - if (human.currentPhase === 'init-pickup') { - setCurrentPhase(human.modelUuid, 'picking'); - setHumanState(human.modelUuid, 'idle'); - setHumanActive(human.modelUuid, false); - setCurrentAnimation(human.modelUuid, 'idle', true, true, true); - humanStatus(human.modelUuid, 'Reached pickup point, waiting for material'); - setPath([]); - } if (human.currentPhase === 'init-assembly') { - setCurrentPhase(human.modelUuid, 'waiting'); - setHumanState(human.modelUuid, 'idle'); - setHumanActive(human.modelUuid, false); - setCurrentAnimation(human.modelUuid, 'idle', true, true, true); - humanStatus(human.modelUuid, 'Reached assembly point, waiting for material'); - setPath([]); - } else if (human.currentPhase === 'pickup-drop') { - setCurrentPhase(human.modelUuid, 'dropping'); - setHumanState(human.modelUuid, 'idle'); - setHumanActive(human.modelUuid, false); - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - humanStatus(human.modelUuid, 'Reached drop point'); - setPath([]); - } else if (human.currentPhase === 'drop-pickup') { - setCurrentPhase(human.modelUuid, 'picking'); - setHumanState(human.modelUuid, 'idle'); - setHumanActive(human.modelUuid, false); - setHumanScheduled(human.modelUuid, false); - setPath([]); - clearCurrentMaterials(human.modelUuid); - setCurrentAnimation(human.modelUuid, 'idle', true, true, true); - humanStatus(human.modelUuid, 'Reached pickup point again, cycle complete'); - } - } - function animate(currentTime: number) { if (previousTimeRef.current === null) { previousTimeRef.current = currentTime; @@ -353,438 +77,15 @@ function HumanInstance({ human }: { human: HumanStatus }) { }; }, [human, isPlaying]); - function startUnloadingProcess() { - const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - - const humanAsset = getAssetById(human.modelUuid); - if (humanAsset?.animationState?.current !== 'drop') { - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - } - if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { - if ((action as HumanAction).triggers.length > 0) { - const trigger = getTriggerByUuid(selectedProduct.productUuid, (action as HumanAction).triggers[0]?.triggerUuid); - const model = getEventByModelUuid(selectedProduct.productUuid, trigger?.triggeredAsset?.triggeredModel?.modelUuid || ''); - - if (trigger && model) { - if (model.type === 'transfer') { - if (action) { - handleMaterialDropToConveyor(model); - } - } else if (model.type === 'machine') { - if (action) { - handleMaterialDropToMachine(model); - } - } else if (model.type === 'roboticArm') { - if (action) { - handleMaterialDropToArmBot(model); - } - } else if (model.type === 'storageUnit') { - if (action) { - handleMaterialDropToStorageUnit(model); - } - } else if (model.type === 'vehicle') { - if (action) { - handleMaterialDropToVehicle(model); - } - } - } else { - const droppedMaterial = human.currentLoad; - handleMaterialDropByDefault(droppedMaterial); - } - } else { - const droppedMaterial = human.currentLoad; - handleMaterialDropByDefault(droppedMaterial); - } - } else { - requestAnimationFrame(startUnloadingProcess); - } - } - - function handleMaterialDropToStorageUnit(model: StorageEventSchema) { - const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - const humanAsset = getAssetById(human.modelUuid); - if (model && humanAsset?.animationState?.current !== 'drop') { - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - } - - const checkAnimation = () => { - if (humanAsset?.animationState?.isCompleted) { - if (model.point.action.actionType === 'store') { - loopMaterialDropToStorage( - human.modelUuid, - human.currentLoad, - model.modelUuid, - model.point.action.storageCapacity, - (action as HumanAction) - ); - } - } else { - requestAnimationFrame(checkAnimation); - } - }; - checkAnimation(); - } - - function loopMaterialDropToStorage( - humanId: string, - humanCurrentLoad: number, - storageUnitId: string, - storageMaxCapacity: number, - action: HumanAction - ) { - const storageUnit = getStorageUnitById(storageUnitId); - const humanAsset = getAssetById(human.modelUuid); - - if (!storageUnit || humanCurrentLoad <= 0 || storageUnit.currentLoad >= storageMaxCapacity) { - return; - } - - decrementHumanLoad(humanId, 1); - humanCurrentLoad -= 1; - - const material = removeLastMaterial(humanId); - if (material) { - triggerPointActions(action, material.materialId); - } - - if (humanCurrentLoad > 0 && storageUnit.currentLoad < storageMaxCapacity) { - resetAnimation(human.modelUuid); - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - - const waitForNextDrop = () => { - if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { - loopMaterialDropToStorage( - humanId, - humanCurrentLoad, - storageUnitId, - storageMaxCapacity, - action - ); - } else { - requestAnimationFrame(waitForNextDrop); - } - }; - waitForNextDrop(); - } - } - - function handleMaterialDropToConveyor(model: ConveyorEventSchema) { - const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - const humanAsset = getAssetById(human.modelUuid); - if (humanAsset?.animationState?.current !== 'drop') { - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - } - - const checkAnimation = () => { - if (humanAsset?.animationState?.isCompleted) { - const conveyor = getConveyorById(model.modelUuid); - if (conveyor) { - loopMaterialDropToConveyor( - human.modelUuid, - human.currentLoad, - conveyor.modelUuid, - (action as HumanAction) - ); - } - } else { - requestAnimationFrame(checkAnimation); - } - }; - checkAnimation(); - } - - function loopMaterialDropToConveyor( - humanId: string, - humanCurrentLoad: number, - conveyorId: string, - action: HumanAction - ) { - const conveyor = getConveyorById(conveyorId); - const humanAsset = getAssetById(human.modelUuid); - - if (!conveyor || humanCurrentLoad <= 0) { - return; - } - - decrementHumanLoad(humanId, 1); - humanCurrentLoad -= 1; - - const material = removeLastMaterial(humanId); - if (material) { - triggerPointActions(action, material.materialId); - } - - if (humanCurrentLoad > 0) { - resetAnimation(human.modelUuid); - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - - const waitForNextDrop = () => { - if (humanAsset?.animationState?.isCompleted) { - loopMaterialDropToConveyor( - humanId, - humanCurrentLoad, - conveyorId, - action - ); - } else { - requestAnimationFrame(waitForNextDrop); - } - }; - waitForNextDrop(); - } - } - - function handleMaterialDropToArmBot(model: RoboticArmEventSchema) { - const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - const humanAsset = getAssetById(human.modelUuid); - if (humanAsset?.animationState?.current !== 'drop') { - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - } - - const checkAnimation = () => { - if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { - const armBot = getArmBotById(model.modelUuid); - if (armBot && armBot.state === 'idle' && !armBot.isActive) { - loopMaterialDropToArmBot( - human.modelUuid, - human.currentLoad, - model.modelUuid, - (action as HumanAction) - ); - } - } else { - requestAnimationFrame(checkAnimation); - } - }; - checkAnimation(); - } - - function loopMaterialDropToArmBot( - humanId: string, - humanCurrentLoad: number, - armBotId: string, - action: HumanAction - ) { - const armBot = getArmBotById(armBotId); - const humanAsset = getAssetById(human.modelUuid); - - if (!armBot || armBot.state !== 'idle' || armBot.isActive || humanCurrentLoad <= 0) { - return; - } - - decrementHumanLoad(humanId, 1); - humanCurrentLoad -= 1; - - const material = removeLastMaterial(humanId); - if (material) { - triggerPointActions(action, material.materialId); - } - - if (humanCurrentLoad > 0) { - resetAnimation(human.modelUuid); - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - - const waitForNextTransfer = () => { - const currentArmBot = getArmBotById(armBotId); - if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) { - if (humanAsset?.animationState?.isCompleted) { - loopMaterialDropToArmBot( - humanId, - humanCurrentLoad, - armBotId, - action - ); - } else { - requestAnimationFrame(waitForNextTransfer); - } - } else { - requestAnimationFrame(waitForNextTransfer); - } - }; - waitForNextTransfer(); - } - } - - function handleMaterialDropToVehicle(model: VehicleEventSchema) { - const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - const humanAsset = getAssetById(human.modelUuid); - if (humanAsset?.animationState?.current !== 'drop') { - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - } - - const checkAnimation = () => { - if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { - const vehicle = getVehicleById(model.modelUuid); - if (vehicle && vehicle.state === 'idle' && !vehicle.isActive) { - loopMaterialDropToVehicle( - human.modelUuid, - human.currentLoad, - model.modelUuid, - (action as HumanAction) - ); - } - } else { - requestAnimationFrame(checkAnimation); - } - }; - checkAnimation(); - } - - function loopMaterialDropToVehicle( - humanId: string, - humanCurrentLoad: number, - vehicleId: string, - action: HumanAction - ) { - const vehicle = getVehicleById(vehicleId); - const humanAsset = getAssetById(human.modelUuid); - - if (!vehicle || vehicle.state !== 'idle' || vehicle.isActive || humanCurrentLoad <= 0) { - return; - } - - decrementHumanLoad(humanId, 1); - humanCurrentLoad -= 1; - - const material = removeLastMaterial(humanId); - if (material) { - triggerPointActions(action, material.materialId); - } - - if (humanCurrentLoad > 0) { - resetAnimation(human.modelUuid); - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - - const waitForNextTransfer = () => { - const currentVehicle = getVehicleById(vehicleId); - if (currentVehicle && currentVehicle.state === 'idle' && !currentVehicle.isActive) { - if (humanAsset?.animationState?.isCompleted) { - loopMaterialDropToVehicle( - humanId, - humanCurrentLoad, - vehicleId, - action - ); - } else { - requestAnimationFrame(waitForNextTransfer); - } - } else { - requestAnimationFrame(waitForNextTransfer); - } - }; - waitForNextTransfer(); - } - } - - function handleMaterialDropToMachine(model: MachineEventSchema) { - const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - const humanAsset = getAssetById(human.modelUuid); - if (humanAsset?.animationState?.current !== 'drop') { - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - } - - const checkAnimation = () => { - if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { - const machine = getMachineById(model.modelUuid); - if (machine && machine.state === 'idle' && !machine.isActive) { - loopMaterialDropToMachine( - human.modelUuid, - human.currentLoad, - model.modelUuid, - (action as HumanAction) - ); - } - } else { - requestAnimationFrame(checkAnimation); - } - }; - checkAnimation(); - } - - function loopMaterialDropToMachine( - humanId: string, - humanCurrentLoad: number, - machineId: string, - action: HumanAction - ) { - const machine = getMachineById(machineId); - const humanAsset = getAssetById(human.modelUuid); - - if (!machine || machine.state !== 'idle' || machine.isActive || humanCurrentLoad <= 0) { - return; - } - - decrementHumanLoad(humanId, 1); - humanCurrentLoad -= 1; - - const material = removeLastMaterial(humanId); - if (material) { - triggerPointActions(action, material.materialId); - } - - if (humanCurrentLoad > 0) { - resetAnimation(human.modelUuid); - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - - const waitForNextTransfer = () => { - const currentMachine = getMachineById(machineId); - if (currentMachine && currentMachine.state === 'idle' && !currentMachine.isActive) { - if (humanAsset?.animationState?.isCompleted) { - loopMaterialDropToMachine( - humanId, - humanCurrentLoad, - machineId, - action - ); - } else { - requestAnimationFrame(waitForNextTransfer); - } - } else { - requestAnimationFrame(waitForNextTransfer); - } - }; - waitForNextTransfer(); - } - } - - function handleMaterialDropByDefault(droppedMaterial: number) { - const humanAsset = getAssetById(human.modelUuid); - if (humanAsset?.animationState?.current !== 'drop') { - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - } - - if (humanAsset?.animationState?.isCompleted) { - const remainingMaterials = droppedMaterial - 1; - decrementHumanLoad(human.modelUuid, 1); - const material = removeLastMaterial(human.modelUuid); - - if (material) { - setEndTime(material.materialId, performance.now()); - removeMaterial(material.materialId); - } - - if (remainingMaterials > 0) { - resetAnimation(human.modelUuid); - setCurrentAnimation(human.modelUuid, 'drop', true, false, false); - - requestAnimationFrame(() => handleMaterialDropByDefault(remainingMaterials)); - } - return; - } - - requestAnimationFrame(() => handleMaterialDropByDefault(droppedMaterial)); - } - return ( <> - + {action && action.actionType === 'worker' && + + } + {action && action.actionType === 'assembly' && + + } diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 62582af..15ed931 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -325,48 +325,17 @@ export function useTriggerHandler() { if (model?.type === 'vehicle') { const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); if (human) { - if (human.isActive === false && human.state === 'idle') { - const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); - if (vehicle) { - if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { - // Handle current action from vehicle + setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (vehicle) { + addVehicleToMonitor(vehicle.modelUuid, () => { + addHumanToMonitor(human.modelUuid, () => { setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); + handleAction(action, materialId); - - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); - - addVehicleToMonitor(vehicle.modelUuid, () => { - handleAction(action, materialId); - }) - } - } - } else { - setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); - addHumanToMonitor(human.modelUuid, () => { - const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (vehicle) { - if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { - // Handle current action from vehicle - setIsPaused(materialId, true); - handleAction(action, materialId); - - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - - addVehicleToMonitor(vehicle.modelUuid, () => { - handleAction(action, materialId); - }) - } - } - }, action.actionUuid) + }, action.actionUuid) + }) } } } else if (model?.type === 'transfer') { @@ -374,61 +343,30 @@ export function useTriggerHandler() { if (human) { setIsPaused(materialId, true); setHumanScheduled(human.modelUuid, true); - addHumanToMonitor(human.modelUuid, () => { - const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (conveyor) { - // Handle current action using Event Manager - setIsPaused(materialId, true); - - addConveyorToMonitor(conveyor.modelUuid, () => { - addHumanToMonitor(human.modelUuid, () => { - handleAction(action, materialId); - }, action.actionUuid) - }, [materialId]) - } - }, action.actionUuid) + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (conveyor) { + addConveyorToMonitor(conveyor.modelUuid, () => { + addHumanToMonitor(human.modelUuid, () => { + setIsPaused(materialId, true); + handleAction(action, materialId); + }, action.actionUuid) + }, [materialId]) + } } } else if (model?.type === 'machine') { const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); if (human) { - if (human.isActive === false && human.state === 'idle') { - const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); - if (machine) { - if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { + setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); + const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (machine) { + addMachineToMonitor(machine.modelUuid, () => { + addHumanToMonitor(human.modelUuid, () => { setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); + handleAction(action, materialId); - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); - - addMachineToMonitor(machine.modelUuid, () => { - handleAction(action, materialId); - }) - } - } - } else { - setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); - addHumanToMonitor(human.modelUuid, () => { - const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (machine) { - if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { - setIsPaused(materialId, true); - handleAction(action, materialId); - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - - addMachineToMonitor(machine.modelUuid, () => { - handleAction(action, materialId); - }) - } - } - }, action.actionUuid); + }, action.actionUuid); + }) } } } else { @@ -465,7 +403,6 @@ export function useTriggerHandler() { addHumanToMonitor(human.modelUuid, () => { handleAction(action, materialId) }, action.actionUuid); - } } } diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index dcb9f49..01a1603 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -102,6 +102,7 @@ interface HumanAction { pickUpPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } dropPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } loadCount: number; + assemblyCount: number; loadCapacity: number; triggers: TriggerSchema[]; } @@ -261,6 +262,26 @@ interface HumanStatus extends HumanEventSchema { }; } +type HumanEventState = { + humanId: string; + actionQueue: { + actionType: 'worker' | 'assembly'; + actionUuid: string; + actionName: string; + maxLoadCount: number; + maxAssemblyCount: number; + count?: number; + isMonitored: boolean; + isCompleted: boolean; + callback?: () => void; + }[]; + isCooldown: boolean; +}; + +type HumanEventManagerState = { + humanStates: HumanEventState[]; +}; + // Materials interface MaterialSchema { materialId: string; From 06e4d516bad04492421b9be039f651865104968a Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 22 Jul 2025 17:12:09 +0530 Subject: [PATCH 07/18] bug fix --- .../machine/instances/animator/machineAnimator.tsx | 1 - .../instances/machineInstance/machineInstance.tsx | 3 +++ app/src/pages/Project.tsx | 10 ---------- app/src/utils/shortcutkeys/handleShortcutKeys.ts | 7 ++++++- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx b/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx index d510784..a04119d 100644 --- a/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx +++ b/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx @@ -84,7 +84,6 @@ const MachineAnimator = ({ currentPhase, handleCallBack, processingTime, machine animationFrameId.current = null; handleCallBack(); } - } } diff --git a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx index 5715016..f45f909 100644 --- a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx +++ b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx @@ -23,6 +23,7 @@ function MachineInstance({ machineDetail }: { readonly machineDetail: MachineSta const { triggerPointActions } = useTriggerHandler(); const { speed } = useAnimationPlaySpeed(); const { isPaused } = usePauseButtonStore(); + useEffect(() => { isPausedRef.current = isPaused; }, [isPaused]); @@ -50,6 +51,7 @@ function MachineInstance({ machineDetail }: { readonly machineDetail: MachineSta function machineStatus(modelId: string, status: string) { // console.log(`${modelId} , ${status}`); } + function animate(currentTime: number) { if (previousTimeRef.current === null) { previousTimeRef.current = currentTime; @@ -130,6 +132,7 @@ function MachineInstance({ machineDetail }: { readonly machineDetail: MachineSta return ( <> + diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 1df03f7..96c529f 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -14,7 +14,6 @@ import FollowPerson from "../components/templates/FollowPerson"; import { useLogger } from "../components/ui/log/LoggerContext"; import RenderOverlay from "../components/templates/Overlay"; import LogList from "../components/ui/log/LogList"; -import { useToggleStore } from "../store/useUIToggleStore"; import { getAllProjects } from "../services/dashboard/getAllProjects"; import { viewProject } from "../services/dashboard/viewProject"; import ComparisonSceneProvider from "../components/layout/scenes/ComparisonSceneProvider"; @@ -30,11 +29,9 @@ import { handleCanvasCursors } from "../utils/mouseUtils/handleCanvasCursors"; const Project: React.FC = () => { let navigate = useNavigate(); const echo = useLogger(); - const { setToggleUI } = useToggleStore(); const { setActiveModule } = useModuleStore(); const { setUserName } = useUserName(); const { setOrganization } = useOrganization(); - const { isVersionSaved } = useSaveVersion(); const { projectId } = useParams(); const { setProjectName } = useProjectName(); const { userId, email, organization, userName } = getUserData(); @@ -98,13 +95,6 @@ const Project: React.FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [projectId]); - useEffect(() => { - if (!isVersionSaved) { - setToggleUI(true, true); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isVersionSaved]); - useEffect(() => { setActiveModule("builder"); if (email) { diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index 9cc81e4..0764fd2 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -95,10 +95,15 @@ const KeyPressListener: React.FC = () => { const toggleTo2D = toggleView; setToggleView(!toggleTo2D); setToggleThreeD(toggleTo2D); - setToggleUI(toggleTo2D, toggleTo2D); if (toggleTo2D) { setSelectedWallItem(null); setAddAction(null); + setToggleUI( + localStorage.getItem("navBarUiLeft") !== "false", + localStorage.getItem("navBarUiRight") !== "false" + ); + } else { + setToggleUI(false, false); } setActiveTool("cursor"); setActiveSubTool("cursor"); From 10e7f2f8c4a893fed64d5fdf4e7578c18bae7ea3 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 28 Jul 2025 12:23:22 +0530 Subject: [PATCH 08/18] Refactor action handling in simulation components for improved clarity and efficiency --- .../mechanics/humanMechanics.tsx | 8 +- .../selection3D/copyPasteControls3D.tsx | 17 ++-- .../selection3D/duplicationControls3D.tsx | 19 +++-- .../selection3D/moveControls3D.tsx | 14 +++- .../selection3D/rotateControls3D.tsx | 10 ++- .../actionHandler/useRetrieveHandler.ts | 61 +++++++++----- .../triggerHandler/useTriggerHandler.ts | 81 ++----------------- 7 files changed, 92 insertions(+), 118 deletions(-) diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index 6109361..fc73e58 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -330,15 +330,15 @@ function HumanMechanics() { setSelectedAction(newAction.actionUuid, newAction.actionName); }; - const handleDeleteAction = () => { - if (!selectedPointData || !selectedAction.actionId) return; + const handleDeleteAction = (actionUuid: string) => { + if (!selectedPointData || !actionUuid) return; - const updatedActions = selectedPointData.actions.filter(action => action.actionUuid !== selectedAction.actionId); + const updatedActions = selectedPointData.actions.filter(action => action.actionUuid !== actionUuid); const updatedPoint = { ...selectedPointData, actions: updatedActions }; const event = removeAction( selectedProduct.productUuid, - selectedAction.actionId + actionUuid ); if (event) { diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index 8fd29e5..24a5f58 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -137,10 +137,11 @@ const CopyPasteControls3D = ({ if (hit && centerOffset) { pastedObjects.forEach((pastedObject: THREE.Object3D, index: number) => { + const model = scene.getObjectByProperty("uuid", pastedObject.userData.modelUuid); + if (!model) return; const newPos = new THREE.Vector3().addVectors(hit, relativePositions[index]); - setPosition(pastedObject.userData.modelUuid, [newPos.x, 0, newPos.z]); + model.position.set(newPos.x, 0, newPos.z); }); - } }); @@ -204,7 +205,9 @@ const CopyPasteControls3D = ({ if (pastedAsset) { const assetUuid = pastedAsset.userData.modelUuid; const asset = getAssetById(assetUuid); - if (!asset) return; + const model = scene.getObjectByProperty("uuid", pastedAsset.userData.modelUuid); + if (!asset || !model) return; + const position = new THREE.Vector3().copy(model.position); const newFloorItem: Types.FloorItemType = { modelUuid: pastedAsset.userData.modelUuid, @@ -427,7 +430,7 @@ const CopyPasteControls3D = ({ modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, - position: newFloorItem.position, + position: [position.x, 0, position.z], rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z }, isLocked: false, isVisible: true, @@ -450,7 +453,7 @@ const CopyPasteControls3D = ({ modelUuid: data.modelUuid, modelName: data.modelName, assetId: data.assetId, - position: data.position, + position: [position.x, 0, position.z], rotation: [data.rotation.x, data.rotation.y, data.rotation.z], isLocked: data.isLocked, isCollidable: false, @@ -466,7 +469,7 @@ const CopyPasteControls3D = ({ modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, - position: newFloorItem.position, + position: [position.x, 0, position.z], rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z }, isLocked: false, isVisible: true, @@ -482,7 +485,7 @@ const CopyPasteControls3D = ({ modelUuid: data.modelUuid, modelName: data.modelName, assetId: data.assetId, - position: data.position, + position: [position.x, 0, position.z], rotation: [data.rotation.x, data.rotation.y, data.rotation.z], isLocked: data.isLocked, isCollidable: false, diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index d32468a..66858a4 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -29,7 +29,7 @@ const DuplicationControls3D = ({ const { assetStore, eventStore } = useSceneContext(); const { addEvent } = eventStore(); const { projectId } = useParams(); - const { assets, addAsset, setPosition, updateAsset, removeAsset, getAssetById } = assetStore(); + const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); @@ -143,11 +143,14 @@ const DuplicationControls3D = ({ initialPosition, initialPositions[duplicatedObjects[0].userData.modelUuid] ); + const model = scene.getObjectByProperty("uuid", duplicatedObject.userData.modelUuid); const newPosition = new THREE.Vector3().addVectors(adjustedHit, relativeOffset); const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; - setPosition(duplicatedObject.userData.modelUuid, positionArray); + if (model) { + model.position.set(...positionArray); + } } } }); @@ -205,7 +208,9 @@ const DuplicationControls3D = ({ if (duplicatedAsset) { const assetUuid = duplicatedAsset.userData.modelUuid; const asset = getAssetById(assetUuid); - if (!asset) return; + const model = scene.getObjectByProperty("uuid", duplicatedAsset.userData.modelUuid); + if (!asset || !model) return; + const position = new THREE.Vector3().copy(model.position); const newFloorItem: Types.FloorItemType = { modelUuid: duplicatedAsset.userData.modelUuid, @@ -428,7 +433,7 @@ const DuplicationControls3D = ({ modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, - position: newFloorItem.position, + position: [position.x, position.y, position.z], rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z }, isLocked: false, isVisible: true, @@ -451,7 +456,7 @@ const DuplicationControls3D = ({ modelUuid: data.modelUuid, modelName: data.modelName, assetId: data.assetId, - position: data.position, + position: [position.x, position.y, position.z], rotation: [data.rotation.x, data.rotation.y, data.rotation.z], isLocked: data.isLocked, isCollidable: false, @@ -467,7 +472,7 @@ const DuplicationControls3D = ({ modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, - position: newFloorItem.position, + position: [position.x, position.y, position.z], rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z }, isLocked: false, isVisible: true, @@ -483,7 +488,7 @@ const DuplicationControls3D = ({ modelUuid: data.modelUuid, modelName: data.modelName, assetId: data.assetId, - position: data.position, + position: [position.x, position.y, position.z], rotation: [data.rotation.x, data.rotation.y, data.rotation.z], isLocked: data.isLocked, isCollidable: false, diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 88b8bf6..9ce9b43 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -38,7 +38,7 @@ function MoveControls3D({ const { userId, organization } = getUserData(); const { projectId } = useParams(); const { assetStore, eventStore, productStore } = useSceneContext(); - const { updateAsset, setPosition, getAssetById } = assetStore(); + const { updateAsset, getAssetById } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -196,6 +196,7 @@ function MoveControls3D({ raycaster.setFromCamera(pointer, camera); const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (hit) { if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) { if (movedObjects[0]) { @@ -231,11 +232,14 @@ function MoveControls3D({ initialPosition, initialPositions[movedObjects[0].uuid] ); + const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid); const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset); const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; - setPosition(movedAsset.userData.modelUuid, positionArray); + if (model) { + model.position.set(...positionArray); + } } } }); @@ -288,13 +292,15 @@ function MoveControls3D({ if (movedAsset) { const assetUuid = movedAsset.userData.modelUuid; const asset = getAssetById(assetUuid); - if (!asset) return; + const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid); + if (!asset || !model) return; + const position = new THREE.Vector3().copy(model.position); const newFloorItem: Types.FloorItemType = { modelUuid: movedAsset.userData.modelUuid, modelName: movedAsset.userData.modelName, assetId: movedAsset.userData.assetId, - position: asset.position, + position: [position.x, position.y, position.z], rotation: { x: movedAsset.rotation.x, y: movedAsset.rotation.y, z: movedAsset.rotation.z }, isLocked: false, isVisible: true, diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index 83266fd..0ca114d 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -174,12 +174,14 @@ function RotateControls3D({ ); const angleDelta = prevAngle - currentAngle; + const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta); + rotatedObjects.forEach((obj: THREE.Object3D) => { if (obj.userData.modelUuid) { - const relativePos = new THREE.Vector3().subVectors(obj.position, center); - relativePos.applyAxisAngle(new THREE.Vector3(0, 1, 0), angleDelta); - obj.position.copy(center).add(relativePos); - obj.rotation.y += angleDelta; + const relativePosition = new THREE.Vector3().subVectors(obj.position, center); + relativePosition.applyMatrix4(rotationMatrix); + obj.position.copy(center).add(relativePosition); + obj.rotateOnWorldAxis(new THREE.Vector3(0, 1, 0), angleDelta); } }); diff --git a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts index 03c7138..02f33e1 100644 --- a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts +++ b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts @@ -3,15 +3,17 @@ import { useFrame } from "@react-three/fiber"; import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore, useAnimationPlaySpeed } from "../../../../../store/usePlayButtonStore"; import { useSceneContext } from "../../../../scene/sceneContext"; import { useProductContext } from "../../../products/productContext"; +import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager"; export function useRetrieveHandler() { - const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext(); + const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore, humanEventManagerRef } = useSceneContext(); const { selectedProductStore } = useProductContext(); const { addMaterial } = materialStore(); const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore(); const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore(); const { getVehicleById, incrementVehicleLoad, addCurrentMaterial } = vehicleStore(); const { getHumanById, incrementHumanLoad, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore(); + const { addHumanToMonitor } = useHumanEventManager(); const { getAssetById, setCurrentAnimation } = assetStore(); const { selectedProduct } = selectedProductStore(); const { getArmBotById, addCurrentAction } = armBotStore(); @@ -22,6 +24,7 @@ export function useRetrieveHandler() { const [activeRetrievals, setActiveRetrievals] = useState>(new Map()); const retrievalTimeRef = useRef>(new Map()); + const retrievalCountRef = useRef>(new Map()); const [initialDelayComplete, setInitialDelayComplete] = useState(false); const delayTimerRef = useRef(null); @@ -300,27 +303,46 @@ export function useRetrieveHandler() { const humanAsset = getAssetById(triggeredModel.modelUuid); const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - if (human && !human.isScheduled && human.state === 'idle' && human.currentLoad < (action as HumanAction).loadCapacity) { - if (humanAsset && humanAsset.animationState?.current === 'idle') { + if (!action || action.actionType !== 'worker' || !humanEventManagerRef.current) return; + + let state = humanEventManagerRef.current.humanStates.find(h => h.humanId === triggeredModel.modelUuid); + console.log('state: ', state); + console.log('human: ', human); + const currentCount = retrievalCountRef.current.get(actionUuid) ?? 0; + + let conditionMet = false; + if (state) { + console.log('state.actionQueue: ', state.actionQueue); + // state.actionQueue[0].count + } + + if (currentCount >= action.loadCount) { + completedActions.push(actionUuid); + hasChanges = true; + return; + } + + if (human && !human.isScheduled && human.state === 'idle' && human.currentLoad < action.loadCapacity) { + if (humanAsset?.animationState?.current === 'idle') { setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); - } else if (humanAsset && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState.isCompleted) { + } else if (humanAsset?.animationState?.current === 'pickup' && humanAsset.animationState.isCompleted) { const lastMaterial = getLastMaterial(storageUnit.modelUuid); if (lastMaterial) { - if (action && human.currentLoad < (action as HumanAction).loadCapacity) { - const material = createNewMaterial( - lastMaterial.materialId, - lastMaterial.materialType, - storageUnit.point.action - ); - if (material) { - removeLastMaterial(storageUnit.modelUuid); - updateCurrentLoad(storageUnit.modelUuid, -1); - incrementHumanLoad(human.modelUuid, 1); + const material = createNewMaterial( + lastMaterial.materialId, + lastMaterial.materialType, + storageUnit.point.action + ); + if (material) { + removeLastMaterial(storageUnit.modelUuid); + updateCurrentLoad(storageUnit.modelUuid, -1); + incrementHumanLoad(human.modelUuid, 1); + addHumanToMonitor(human.modelUuid, () => { addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId); retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`); - } - if (human.currentLoad + 1 < (action as HumanAction).loadCapacity) { - } + + retrievalCountRef.current.set(actionUuid, currentCount + 1); + }, actionUuid) } } } @@ -352,11 +374,12 @@ export function useRetrieveHandler() { }, []); useEffect(() => { - if (isReset) { + if (isReset || !isPlaying) { setActiveRetrievals(new Map()); + retrievalCountRef.current.clear(); setInitialDelayComplete(false); } - }, [isReset]); + }, [isReset, isPlaying]); return { handleRetrieve, diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 15ed931..4dc5ae2 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -714,10 +714,7 @@ export function useTriggerHandler() { setIsVisible(materialId, false); if (action && human) { - - if (human.isActive === false && human.state === 'idle') { - - // Handle current action from arm bot + addHumanToMonitor(human.modelUuid, () => { const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); if (model?.type === 'transfer') { const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); @@ -729,17 +726,14 @@ export function useTriggerHandler() { handleAction(action, materialId) } else { setHumanScheduled(human.modelUuid, true); - addConveyorToMonitor(conveyor.modelUuid, - () => { - handleAction(action, materialId) - } - ) + addConveyorToMonitor(conveyor.modelUuid, () => { + handleAction(action, materialId) + }) } } else { setHumanScheduled(human.modelUuid, true); handleAction(action, materialId) } - // handleAction(action, materialId) } } else if (model?.type === 'vehicle') { const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); @@ -751,79 +745,20 @@ export function useTriggerHandler() { handleAction(action, materialId); } else { - // Handle current action using Event Manager setIsPaused(materialId, true); setHumanScheduled(human.modelUuid, true); - addVehicleToMonitor(vehicle.modelUuid, - () => { - handleAction(action, materialId); - } - ) + addVehicleToMonitor(vehicle.modelUuid, () => { + handleAction(action, materialId); + }) } } } else { setHumanScheduled(human.modelUuid, true); handleAction(action, materialId) } - - } else { - - // Handle current action using Event Manager - - addHumanToMonitor(human.modelUuid, () => { - const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (model?.type === 'transfer') { - const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (conveyor) { - const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || ''); - if (previousModel) { - if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) { - - setHumanScheduled(human.modelUuid, true); - handleAction(action, materialId) - } else { - setHumanScheduled(human.modelUuid, true); - addConveyorToMonitor(conveyor.modelUuid, - () => { - handleAction(action, materialId) - } - ) - } - } else { - setHumanScheduled(human.modelUuid, true); - handleAction(action, materialId) - } - } - } else if (model?.type === 'vehicle') { - const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (vehicle) { - if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { - // Handle current action from vehicle - setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); - handleAction(action, materialId); - - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); - - addVehicleToMonitor(vehicle.modelUuid, - () => { - handleAction(action, materialId); - } - ) - } - } - } else { - setHumanScheduled(human.modelUuid, true); - handleAction(action, materialId) - } - }, action.actionUuid); - } + }, action.actionUuid); } } } From 795c69a3d447552293896eddf506aa4d1a510efd Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 28 Jul 2025 13:05:32 +0530 Subject: [PATCH 09/18] Enhance shadow handling and visibility settings across components --- .../sidebarRight/properties/GlobalProperties.tsx | 5 +++-- .../modules/builder/asset/models/model/model.tsx | 13 +++++++++++++ .../builder/wall/Instances/instance/wall.tsx | 14 +++++++++----- .../builder/wall/Instances/wallInstances.tsx | 1 + app/src/modules/scene/environment/shadow.tsx | 16 ++++++---------- app/src/types/world/worldConstants.ts | 6 ++++-- 6 files changed, 36 insertions(+), 19 deletions(-) diff --git a/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx b/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx index c075f52..15d32ce 100644 --- a/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx @@ -216,6 +216,7 @@ const GlobalProperties: React.FC = () => { // setRenderDistance(parseInt(e.target.value)); // } // } + return (
@@ -239,12 +240,12 @@ const GlobalProperties: React.FC = () => { label="Wall Visibility" onClick={changeWallVisibility} /> - {/* */} + /> { + if (gltfScene) { + gltfScene.traverse((child: any) => { + if (child.isMesh) { + child.castShadow = true; + child.receiveShadow = true; + } + }) + } + }, [gltfScene]); + useEffect(() => { setDeletableFloorItem(null); if (selectedFloorItem === null || selectedFloorItem.userData.modelUuid !== asset.modelUuid) { @@ -481,6 +492,8 @@ function Model({ asset }: { readonly asset: Asset }) { rotation={asset.rotation} visible={asset.isVisible} userData={{ ...asset, iks: ikData }} + castShadow + receiveShadow onDoubleClick={(e) => { e.stopPropagation(); if (!toggleView) { diff --git a/app/src/modules/builder/wall/Instances/instance/wall.tsx b/app/src/modules/builder/wall/Instances/instance/wall.tsx index a441ea8..1a73bfe 100644 --- a/app/src/modules/builder/wall/Instances/instance/wall.tsx +++ b/app/src/modules/builder/wall/Instances/instance/wall.tsx @@ -10,7 +10,7 @@ import { useToggleView, useWallVisibility } from '../../../../../store/builder/s import { useBuilderStore } from '../../../../../store/builder/useBuilderStore'; import * as Constants from '../../../../../types/world/worldConstants'; -import DecalInstance from '../../../Decal/decalInstance'; +// import DecalInstance from '../../../Decal/decalInstance'; import defaultMaterial from '../../../../../assets/textures/floor/wall-tex.png'; import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg'; @@ -63,10 +63,10 @@ function Wall({ wall }: { readonly wall: Wall }) { const materials = useMemo(() => { return [ - new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Left - new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Right - new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Top - new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Bottom + new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Left + new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Right + new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Top + new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Bottom new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, @@ -99,11 +99,15 @@ function Wall({ wall }: { readonly wall: Wall }) { return ( (null); const targetRef = useRef(null); const { controls, gl } = useThree(); - const { elevation, setElevation } = useElevation(); - const { azimuth, setAzimuth } = useAzimuth(); + const { elevation } = useElevation(); + const { azimuth } = useAzimuth(); const { planeValue } = useTileDistance(); useEffect(() => { diff --git a/app/src/types/world/worldConstants.ts b/app/src/types/world/worldConstants.ts index 83933b7..0fe8f72 100644 --- a/app/src/types/world/worldConstants.ts +++ b/app/src/types/world/worldConstants.ts @@ -265,8 +265,10 @@ export const planeConfig: PlaneConfig = { export const shadowConfig: ShadowConfig = { shadowOffset: 50, // Offset of the shadow - shadowmapSizewidth: 1024, // Width of the shadow map - shadowmapSizeheight: 1024, // Height of the shadow map + // shadowmapSizewidth: 1024, // Width of the shadow map + // shadowmapSizeheight: 1024, // Height of the shadow map + shadowmapSizewidth: 2048, // Width of the shadow map + shadowmapSizeheight: 2048, // Height of the shadow map // shadowmapSizewidth: 8192, // Width of the shadow map // shadowmapSizeheight: 8192, // Height of the shadow map shadowcamerafar: 70, // Far plane of the shadow camera From f86d36c59f4fc17db47848350d37e8e3b5f68ea9 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 28 Jul 2025 13:09:08 +0530 Subject: [PATCH 10/18] Fix position format in event update for moved assets --- .../controls/selectionControls/selection3D/moveControls3D.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 9ce9b43..bb3a782 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -312,7 +312,7 @@ function MoveControls3D({ if (eventData) { eventStore.getState().updateEvent(movedAsset.userData.modelUuid, { - position: asset.position, + position: [position.x, position.y, position.z], rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], }); } @@ -324,7 +324,7 @@ function MoveControls3D({ selectedProduct.productUuid, movedAsset.userData.modelUuid, { - position: asset.position, + position: [position.x, position.y, position.z], rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], } ); From 2ac6bbeb9d117a63e45f2de81033b2015e0f2d9a Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 28 Jul 2025 14:13:27 +0530 Subject: [PATCH 11/18] Refactor mouse button handling and key event logic in MoveControls3D for improved clarity --- .../selection3D/moveControls3D.tsx | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index bb3a782..26ebe57 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -46,10 +46,7 @@ function MoveControls3D({ const [initialPositions, setInitialPositions] = useState>({}); const [initialStates, setInitialStates] = useState>({}); const [isMoving, setIsMoving] = useState(false); - const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ - left: false, - right: false, - }); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); const updateBackend = ( productName: string, @@ -79,9 +76,9 @@ function MoveControls3D({ }; const onKeyUp = (event: KeyboardEvent) => { - const isModifierKey = event.key === "Control" || event.key === "Shift"; + const isModifierKey = (!event.shiftKey && !event.ctrlKey); - if (isModifierKey) { + if (isModifierKey && keyEvent !== "") { setKeyEvent(""); } }; @@ -196,7 +193,6 @@ function MoveControls3D({ raycaster.setFromCamera(pointer, camera); const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (hit) { if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) { if (movedObjects[0]) { @@ -209,18 +205,18 @@ function MoveControls3D({ if (dragOffset) { const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); - let moveSpeed = keyEvent.includes("Shift") ? 0.05 : 1; + let moveDistance = keyEvent.includes("Shift") ? 0.05 : 1; const initialBasePosition = initialPositions[movedObjects[0].uuid]; const positionDifference = new THREE.Vector3().subVectors(rawBasePosition, initialBasePosition); - const adjustedDifference = positionDifference.multiplyScalar(moveSpeed); + const adjustedDifference = positionDifference.multiplyScalar(moveDistance); const baseNewPosition = new THREE.Vector3().addVectors(initialBasePosition, adjustedDifference); if (keyEvent.includes("Ctrl")) { - baseNewPosition.x = snapControls(baseNewPosition.x, "Ctrl"); - baseNewPosition.z = snapControls(baseNewPosition.z, "Ctrl"); + baseNewPosition.x = snapControls(baseNewPosition.x, keyEvent); + baseNewPosition.z = snapControls(baseNewPosition.z, keyEvent); } movedObjects.forEach((movedAsset: THREE.Object3D) => { From 11ace1977a122ecab1c12fd5f63a8490a62d00ef Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 28 Jul 2025 15:00:24 +0530 Subject: [PATCH 12/18] Refactor asset loading and distance calculation logic for improved performance and clarity --- app/src/modules/builder/asset/assetsGroup.tsx | 7 +---- .../builder/asset/models/model/model.tsx | 26 +++++++------------ .../selection3D/duplicationControls3D.tsx | 14 +++++----- .../selection3D/moveControls3D.tsx | 2 +- .../webWorkers/distanceWorker.js | 21 +++++++++++++++ .../webWorkers/gltfLoaderWorker.js | 13 +++------- 6 files changed, 42 insertions(+), 41 deletions(-) create mode 100644 app/src/services/factoryBuilder/webWorkers/distanceWorker.js diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index deda3d8..89f7471 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -16,12 +16,7 @@ import { getUserData } from "../../../functions/getUserData"; import { useSceneContext } from "../../scene/sceneContext"; import { useVersionContext } from "../version/versionContext"; -const gltfLoaderWorker = new Worker( - new URL( - "../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", - import.meta.url - ) -); +const gltfLoaderWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", import.meta.url)); function AssetsGroup({ plane }: { readonly plane: RefMesh }) { const { activeModule } = useModuleStore(); diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 92f6d73..384c564 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -17,13 +17,16 @@ import { useSceneContext } from '../../../../scene/sceneContext'; import { useVersionContext } from '../../../version/versionContext'; import { SkeletonUtils } from 'three-stdlib'; import { useAnimationPlaySpeed, usePauseButtonStore } from '../../../../../store/usePlayButtonStore'; + import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi'; import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs'; +const distanceWorker = new Worker(new URL("../../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url)); + function Model({ asset }: { readonly asset: Asset }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const savedTheme: string = localStorage.getItem("theme") || "light"; - const { camera, controls, gl } = useThree(); + const { camera, controls, gl, scene } = useThree(); const { activeTool } = useActiveTool(); const { toolMode } = useToolMode(); const { toggleView } = useToggleView(); @@ -116,6 +119,12 @@ function Model({ asset }: { readonly asset: Asset }) { } }, [isRendered, selectedFloorItem]) + useEffect(() => { + distanceWorker.onmessage = (e) => { + setIsRendered(e.data.shouldRender); + }; + }, []); + useEffect(() => { const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); @@ -202,21 +211,6 @@ function Model({ asset }: { readonly asset: Asset }) { }, []); - useFrame(() => { - const assetPosition = new THREE.Vector3(...asset.position); - if (limitDistance) { - if (!isRendered && assetPosition.distanceTo(camera.position) <= renderDistance) { - setIsRendered(true); - } else if (isRendered && assetPosition.distanceTo(camera.position) > renderDistance) { - setIsRendered(false); - } - } else { - if (!isRendered) { - setIsRendered(true); - } - } - }) - const handleDblClick = (asset: Asset) => { if (asset) { if (activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') { diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index 66858a4..5ab6b75 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -42,8 +42,8 @@ const DuplicationControls3D = ({ right: false, }); - const calculateDragOffset = useCallback((point: THREE.Vector3, hitPoint: THREE.Vector3) => { - const pointPosition = new THREE.Vector3().copy(point); + const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { + const pointPosition = new THREE.Vector3().copy(point.position); return new THREE.Vector3().subVectors(pointPosition, hitPoint); }, []); @@ -120,11 +120,9 @@ const DuplicationControls3D = ({ if (hit) { if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) { - const assetUuid = duplicatedObjects[0].userData.modelUuid; - const asset = getAssetById(assetUuid); - if (!asset) return; - if (duplicatedObjects[0]) { - const newOffset = calculateDragOffset(new THREE.Vector3(...asset.position), intersectionPoint); + const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); + if (model) { + const newOffset = calculateDragOffset(model, intersectionPoint); setDragOffset(newOffset); } return; @@ -177,7 +175,7 @@ const DuplicationControls3D = ({ const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); if (hit) { - const offset = calculateDragOffset(selectedAssets[0].position, hit); + const offset = calculateDragOffset(selectedAssets[0], hit); setDragOffset(offset); } diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 26ebe57..18f4abf 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -210,7 +210,7 @@ function MoveControls3D({ const initialBasePosition = initialPositions[movedObjects[0].uuid]; const positionDifference = new THREE.Vector3().subVectors(rawBasePosition, initialBasePosition); - const adjustedDifference = positionDifference.multiplyScalar(moveDistance); + let adjustedDifference = positionDifference.multiplyScalar(moveDistance); const baseNewPosition = new THREE.Vector3().addVectors(initialBasePosition, adjustedDifference); diff --git a/app/src/services/factoryBuilder/webWorkers/distanceWorker.js b/app/src/services/factoryBuilder/webWorkers/distanceWorker.js new file mode 100644 index 0000000..f17ff76 --- /dev/null +++ b/app/src/services/factoryBuilder/webWorkers/distanceWorker.js @@ -0,0 +1,21 @@ +onmessage = function (e) { + const { assetPosition, cameraPosition, limitDistance, renderDistance, isRendered } = e.data; + + if (limitDistance && assetPosition) { + const distance = Math.sqrt( + Math.pow(assetPosition.x - cameraPosition.x, 2) + + Math.pow(assetPosition.y - cameraPosition.y, 2) + + Math.pow(assetPosition.z - cameraPosition.z, 2) + ); + + if (!isRendered && distance <= renderDistance) { + postMessage({ shouldRender: true }); + } else if (isRendered && distance > renderDistance) { + postMessage({ shouldRender: false }); + } + } else { + if (!isRendered) { + postMessage({ shouldRender: true }); + } + } +}; \ No newline at end of file diff --git a/app/src/services/factoryBuilder/webWorkers/gltfLoaderWorker.js b/app/src/services/factoryBuilder/webWorkers/gltfLoaderWorker.js index 66c9c81..e6d38ad 100644 --- a/app/src/services/factoryBuilder/webWorkers/gltfLoaderWorker.js +++ b/app/src/services/factoryBuilder/webWorkers/gltfLoaderWorker.js @@ -1,12 +1,5 @@ -import * as THREE from 'three'; -import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; -import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; import { retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils'; -const loader = new GLTFLoader(); -const dracoLoader = new DRACOLoader(); -dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/'); -loader.setDRACOLoader(dracoLoader); let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; onmessage = async (event) => { @@ -17,8 +10,8 @@ onmessage = async (event) => { ); for (const item of uniqueItems) { - if(item.assetId === null || item.assetId === undefined) { - continue; // Skip items without a valid assetId + if (item.assetId === null || item.assetId === undefined) { + continue; } const modelID = item.assetId; const indexedDBModel = await retrieveGLTF(modelID); @@ -37,5 +30,5 @@ onmessage = async (event) => { } } - postMessage({ message: 'done' }) + postMessage({ message: 'done' }); }; From 2aab047b6dc4a62d81b6baaadb3d8b0bfa9f0b98 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 28 Jul 2025 15:05:28 +0530 Subject: [PATCH 13/18] Refactor useRetrieveHandler to remove unused human event manager and streamline action handling --- .../builder/asset/models/model/model.tsx | 73 +++---------------- .../actionHandler/useRetrieveHandler.ts | 23 ++---- 2 files changed, 14 insertions(+), 82 deletions(-) diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 429a9c3..f92d34f 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -16,9 +16,9 @@ import { getUserData } from '../../../../../functions/getUserData'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useVersionContext } from '../../../version/versionContext'; import { SkeletonUtils } from 'three-stdlib'; -import { useAnimationPlaySpeed, usePauseButtonStore } from '../../../../../store/usePlayButtonStore'; import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi'; import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs'; +import { ModelAnimator } from './animator/modelAnimator'; function Model({ asset }: { readonly asset: Asset }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; @@ -29,10 +29,8 @@ function Model({ asset }: { readonly asset: Asset }) { const { toggleView } = useToggleView(); const { subModule } = useSubModuleStore(); const { activeModule } = useModuleStore(); - const { speed } = useAnimationPlaySpeed(); - const { isPaused } = usePauseButtonStore(); const { assetStore, eventStore, productStore } = useSceneContext(); - const { removeAsset, setAnimations, resetAnimation, setAnimationComplete } = assetStore(); + const { removeAsset, resetAnimation } = assetStore(); const { setTop } = useTopData(); const { setLeft } = useLeftData(); const { getIsEventInProduct, addPoint } = productStore(); @@ -54,12 +52,7 @@ function Model({ asset }: { readonly asset: Asset }) { const [boundingBox, setBoundingBox] = useState(null); const [isSelected, setIsSelected] = useState(false); const groupRef = useRef(null); - const mixerRef = useRef(); - const actions = useRef<{ [name: string]: THREE.AnimationAction }>({}); - const [previousAnimation, setPreviousAnimation] = useState(null); const [ikData, setIkData] = useState(); - const blendFactor = useRef(0); - const blendDuration = 0.5; const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); @@ -122,16 +115,6 @@ function Model({ asset }: { readonly asset: Asset }) { clone.animations = cachedModel.animations || []; setGltfScene(clone); calculateBoundingBox(clone); - if (cachedModel.animations && clone.animations.length > 0) { - const animationName = clone.animations.map((clip: any) => clip.name); - setAnimations(asset.modelUuid, animationName) - mixerRef.current = new THREE.AnimationMixer(clone); - - clone.animations.forEach((animation: any) => { - const action = mixerRef.current!.clipAction(animation); - actions.current[animation.name] = action; - }); - } return; } @@ -371,50 +354,6 @@ function Model({ asset }: { readonly asset: Asset }) { } } - const handleAnimationComplete = useCallback(() => { - if (asset.animationState) { - setAnimationComplete(asset.modelUuid, true); - } - }, [asset.animationState]); - - useFrame((_, delta) => { - if (mixerRef.current) { - mixerRef.current.update(delta * (activeModule === 'simulation' ? speed : 1)); - } - }); - - useEffect(() => { - if (!asset.animationState || !mixerRef.current) return; - - const { current, loopAnimation, isPlaying } = asset.animationState; - const currentAction = actions.current[current]; - const previousAction = previousAnimation ? actions.current[previousAnimation] : null; - - if (isPlaying && currentAction && activeModule === 'simulation' && !isPaused) { - blendFactor.current = 0; - - currentAction.reset(); - currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1); - currentAction.clampWhenFinished = true; - - if (previousAction && previousAction !== currentAction) { - previousAction.crossFadeTo(currentAction, blendDuration, false); - } - - currentAction.play(); - mixerRef.current.addEventListener('finished', handleAnimationComplete); - setPreviousAnimation(current); - } else { - Object.values(actions.current).forEach((action) => action.stop()); - } - - return () => { - if (mixerRef.current) { - mixerRef.current.removeEventListener('finished', handleAnimationComplete); - } - }; - }, [asset.animationState?.current, asset.animationState?.isCompleted, asset.animationState?.isPlaying, isPaused, activeModule]); - useEffect(() => { const canvasElement = gl.domElement; @@ -513,7 +452,13 @@ function Model({ asset }: { readonly asset: Asset }) { {gltfScene && ( <> {isRendered ? ( - + <> + + + + + + ) : ( )} diff --git a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts index 02f33e1..e951289 100644 --- a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts +++ b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts @@ -3,17 +3,15 @@ import { useFrame } from "@react-three/fiber"; import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore, useAnimationPlaySpeed } from "../../../../../store/usePlayButtonStore"; import { useSceneContext } from "../../../../scene/sceneContext"; import { useProductContext } from "../../../products/productContext"; -import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager"; export function useRetrieveHandler() { - const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore, humanEventManagerRef } = useSceneContext(); + const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext(); const { selectedProductStore } = useProductContext(); const { addMaterial } = materialStore(); const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore(); const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore(); const { getVehicleById, incrementVehicleLoad, addCurrentMaterial } = vehicleStore(); const { getHumanById, incrementHumanLoad, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore(); - const { addHumanToMonitor } = useHumanEventManager(); const { getAssetById, setCurrentAnimation } = assetStore(); const { selectedProduct } = selectedProductStore(); const { getArmBotById, addCurrentAction } = armBotStore(); @@ -303,19 +301,10 @@ export function useRetrieveHandler() { const humanAsset = getAssetById(triggeredModel.modelUuid); const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - if (!action || action.actionType !== 'worker' || !humanEventManagerRef.current) return; + if (!action || action.actionType !== 'worker') return; - let state = humanEventManagerRef.current.humanStates.find(h => h.humanId === triggeredModel.modelUuid); - console.log('state: ', state); - console.log('human: ', human); const currentCount = retrievalCountRef.current.get(actionUuid) ?? 0; - let conditionMet = false; - if (state) { - console.log('state.actionQueue: ', state.actionQueue); - // state.actionQueue[0].count - } - if (currentCount >= action.loadCount) { completedActions.push(actionUuid); hasChanges = true; @@ -337,12 +326,10 @@ export function useRetrieveHandler() { removeLastMaterial(storageUnit.modelUuid); updateCurrentLoad(storageUnit.modelUuid, -1); incrementHumanLoad(human.modelUuid, 1); - addHumanToMonitor(human.modelUuid, () => { - addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId); - retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`); + addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId); + retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`); - retrievalCountRef.current.set(actionUuid, currentCount + 1); - }, actionUuid) + retrievalCountRef.current.set(actionUuid, currentCount + 1); } } } From c224d9bb3c19e28f27a5c19bc1481170891cd540 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 28 Jul 2025 15:26:49 +0530 Subject: [PATCH 14/18] Refactor Model component to remove unused distance worker and implement asset rendering logic based on camera distance --- .../builder/asset/models/model/model.tsx | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 2d7b312..b8aa773 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -16,12 +16,11 @@ import { getUserData } from '../../../../../functions/getUserData'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useVersionContext } from '../../../version/versionContext'; import { SkeletonUtils } from 'three-stdlib'; + import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi'; import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs'; import { ModelAnimator } from './animator/modelAnimator'; -const distanceWorker = new Worker(new URL("../../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url)); - function Model({ asset }: { readonly asset: Asset }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const savedTheme: string = localStorage.getItem("theme") || "light"; @@ -111,12 +110,6 @@ function Model({ asset }: { readonly asset: Asset }) { } }, [isRendered, selectedFloorItem]) - useEffect(() => { - distanceWorker.onmessage = (e) => { - setIsRendered(e.data.shouldRender); - }; - }, []); - useEffect(() => { const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); @@ -193,6 +186,21 @@ function Model({ asset }: { readonly asset: Asset }) { }, []); + useFrame(() => { + const assetPosition = scene.getObjectByProperty("uuid", asset.modelUuid)?.position; + if (limitDistance && assetPosition) { + if (!isRendered && assetPosition.distanceTo(camera.position) <= renderDistance) { + setIsRendered(true); + } else if (isRendered && assetPosition.distanceTo(camera.position) > renderDistance) { + setIsRendered(false); + } + } else { + if (!isRendered) { + setIsRendered(true); + } + } + }) + const handleDblClick = (asset: Asset) => { if (asset) { if (activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') { From 1231bedbb13fc9b8c98004358266d768432f01a6 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 28 Jul 2025 17:21:07 +0530 Subject: [PATCH 15/18] Refactor AssetBoundingBox to use cylinders for edges and improve bounding box rendering logic; update Model component to adjust line width for better visibility; enhance distanceWorker to utilize Vector3 for distance calculations. --- .../asset/functions/assetBoundingBox.tsx | 78 ++++++++++++------- .../builder/asset/models/model/model.tsx | 5 +- .../selection2D/moveControls2D.tsx | 8 +- .../webWorkers/distanceWorker.js | 20 ++--- 4 files changed, 65 insertions(+), 46 deletions(-) diff --git a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx index 5923321..da08edf 100644 --- a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx +++ b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx @@ -1,47 +1,67 @@ -import { Line } from "@react-three/drei"; -import { Box3, Vector3 } from "three"; +import { Box3, Vector3, Quaternion } from "three"; import { useMemo } from "react"; +import { Cylinder } from "@react-three/drei"; -export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => { - const { points, size, center } = useMemo(() => { - if (!boundingBox) { return { points: [], center: new Vector3(), size: new Vector3(), }; } +export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => { + const { edgeCylinders, center, size } = useMemo(() => { + if (!boundingBox) return { edgeCylinders: [], center: new Vector3(), size: new Vector3() }; const min = boundingBox.min; const max = boundingBox.max; const center = boundingBox.getCenter(new Vector3()); const size = boundingBox.getSize(new Vector3()); - const edges: Array<[number, number, number]> = [ - [min.x, min.y, min.z], [max.x, min.y, min.z], - [max.x, min.y, min.z], [max.x, max.y, min.z], - [max.x, max.y, min.z], [min.x, max.y, min.z], - [min.x, max.y, min.z], [min.x, min.y, min.z], - - [min.x, min.y, max.z], [max.x, min.y, max.z], - [max.x, min.y, max.z], [max.x, max.y, max.z], - [max.x, max.y, max.z], [min.x, max.y, max.z], - [min.x, max.y, max.z], [min.x, min.y, max.z], - - [min.x, min.y, min.z], [min.x, min.y, max.z], - [max.x, min.y, min.z], [max.x, min.y, max.z], - [max.x, max.y, min.z], [max.x, max.y, max.z], - [min.x, max.y, min.z], [min.x, max.y, max.z], + const corners = [ + new Vector3(min.x, min.y, min.z), + new Vector3(max.x, min.y, min.z), + new Vector3(max.x, max.y, min.z), + new Vector3(min.x, max.y, min.z), + new Vector3(min.x, min.y, max.z), + new Vector3(max.x, min.y, max.z), + new Vector3(max.x, max.y, max.z), + new Vector3(min.x, max.y, max.z), ]; - return { points: edges, center, size }; - }, [boundingBox]); + const edgeIndices: [number, number][] = [ + [0, 1], [1, 2], [2, 3], [3, 0], + [4, 5], [5, 6], [6, 7], [7, 4], + [0, 4], [1, 5], [2, 6], [3, 7], + ]; + + const radius = 0.005 * lineWidth; + + const edgeCylinders = edgeIndices.map(([startIdx, endIdx], i) => { + const start = corners[startIdx]; + const end = corners[endIdx]; + const direction = new Vector3().subVectors(end, start); + const length = direction.length(); + const midPoint = new Vector3().addVectors(start, end).multiplyScalar(0.5); + const quaternion = new Quaternion().setFromUnitVectors( + new Vector3(0, 1, 0), + direction.clone().normalize() + ); + + return { + key: `edge-cylinder-${i}`, + position: midPoint, + rotation: quaternion, + length, + radius, + }; + }); + + return { edgeCylinders, center, size }; + }, [boundingBox, lineWidth]); if (!boundingBox) return null; return ( - + {edgeCylinders.map(({ key, position, rotation, length, radius }) => ( + + + + ))} diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index b8aa773..ffeffe0 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -21,6 +21,8 @@ import { upsertProductOrEventApi } from '../../../../../services/simulation/prod import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs'; import { ModelAnimator } from './animator/modelAnimator'; +const distanceWorker = new Worker(new URL("../../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url)); + function Model({ asset }: { readonly asset: Asset }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const savedTheme: string = localStorage.getItem("theme") || "light"; @@ -59,6 +61,7 @@ function Model({ asset }: { readonly asset: Asset }) { const { userId, organization } = getUserData(); const { projectId } = useParams(); const { selectedAssets } = useSelectedAssets(); + const intervalRef = useRef(null); const updateBackend = ( productName: string, @@ -474,7 +477,7 @@ function Model({ asset }: { readonly asset: Asset }) { ) : ( - + )} {isSelected && diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx index d91cebb..fa12f61 100644 --- a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx @@ -164,7 +164,7 @@ function MoveControls2D({ return new THREE.Vector3().subVectors(pointPosition, hitPoint); }, []); - const movePoints = useCallback(() => { + const movePoints = (() => { if (selectedPoints.length === 0) return; const states: Record = {}; @@ -192,9 +192,9 @@ function MoveControls2D({ setMovedObjects(selectedPoints); setIsMoving(true); - }, [selectedPoints, camera, pointer, plane, raycaster, calculateDragOffset]); + }); - const resetToInitialPositions = useCallback(() => { + const resetToInitialPositions = () => { setTimeout(() => { movedObjects.forEach((movedPoint: THREE.Object3D) => { if (movedPoint.userData.pointUuid && initialStates[movedPoint.uuid]) { @@ -218,7 +218,7 @@ function MoveControls2D({ } }); }, 0) - }, [movedObjects, initialStates, setAislePosition, setWallPosition, setFloorPosition, setZonePosition]); + }; const placeMovedAssets = () => { if (movedObjects.length === 0) return; diff --git a/app/src/services/factoryBuilder/webWorkers/distanceWorker.js b/app/src/services/factoryBuilder/webWorkers/distanceWorker.js index f17ff76..84ba747 100644 --- a/app/src/services/factoryBuilder/webWorkers/distanceWorker.js +++ b/app/src/services/factoryBuilder/webWorkers/distanceWorker.js @@ -1,21 +1,17 @@ +import { Vector3 } from "three"; + onmessage = function (e) { - const { assetPosition, cameraPosition, limitDistance, renderDistance, isRendered } = e.data; + const { modelUuid, assetPosition, cameraPosition, limitDistance, renderDistance, isRendered } = e.data; if (limitDistance && assetPosition) { - const distance = Math.sqrt( - Math.pow(assetPosition.x - cameraPosition.x, 2) + - Math.pow(assetPosition.y - cameraPosition.y, 2) + - Math.pow(assetPosition.z - cameraPosition.z, 2) - ); - - if (!isRendered && distance <= renderDistance) { - postMessage({ shouldRender: true }); - } else if (isRendered && distance > renderDistance) { - postMessage({ shouldRender: false }); + if (!isRendered && new Vector3(assetPosition.x, assetPosition.y, assetPosition.z).distanceTo(cameraPosition) <= renderDistance) { + postMessage({ shouldRender: true, modelUuid }); + } else if (isRendered && new Vector3(assetPosition.x, assetPosition.y, assetPosition.z).distanceTo(cameraPosition) > renderDistance) { + postMessage({ shouldRender: false, modelUuid }); } } else { if (!isRendered) { - postMessage({ shouldRender: true }); + postMessage({ shouldRender: true, modelUuid }); } } }; \ No newline at end of file From 7d29228541b5e4a407c1839f26c34122aba9b6e7 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 28 Jul 2025 17:34:36 +0530 Subject: [PATCH 16/18] Refactor Model and Models components to streamline rendering logic; update distanceWorker for improved distance calculations and rendering conditions. --- .../builder/asset/models/model/model.tsx | 28 ++--------- .../modules/builder/asset/models/models.tsx | 48 +++++++++++++++---- .../webWorkers/distanceWorker.js | 14 ++++-- 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index ffeffe0..1679b10 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -3,8 +3,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils'; import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; -import { ThreeEvent, useFrame, useThree } from '@react-three/fiber'; -import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; +import { ThreeEvent, useThree } from '@react-three/fiber'; +import { useActiveTool, useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; import { AssetBoundingBox } from '../../functions/assetBoundingBox'; import { CameraControls } from '@react-three/drei'; import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore'; @@ -21,12 +21,11 @@ import { upsertProductOrEventApi } from '../../../../../services/simulation/prod import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs'; import { ModelAnimator } from './animator/modelAnimator'; -const distanceWorker = new Worker(new URL("../../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url)); -function Model({ asset }: { readonly asset: Asset }) { +function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boolean }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const savedTheme: string = localStorage.getItem("theme") || "light"; - const { camera, controls, gl, scene } = useThree(); + const { controls, gl } = useThree(); const { activeTool } = useActiveTool(); const { toolMode } = useToolMode(); const { toggleView } = useToggleView(); @@ -44,13 +43,10 @@ function Model({ asset }: { readonly asset: Asset }) { const { socket } = useSocketStore(); const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); - const { limitDistance } = useLimitDistance(); - const { renderDistance } = useRenderDistance(); const leftDrag = useRef(false); const isLeftMouseDown = useRef(false); const rightDrag = useRef(false); const isRightMouseDown = useRef(false); - const [isRendered, setIsRendered] = useState(false); const [gltfScene, setGltfScene] = useState(null); const [boundingBox, setBoundingBox] = useState(null); const [isSelected, setIsSelected] = useState(false); @@ -61,7 +57,6 @@ function Model({ asset }: { readonly asset: Asset }) { const { userId, organization } = getUserData(); const { projectId } = useParams(); const { selectedAssets } = useSelectedAssets(); - const intervalRef = useRef(null); const updateBackend = ( productName: string, @@ -189,21 +184,6 @@ function Model({ asset }: { readonly asset: Asset }) { }, []); - useFrame(() => { - const assetPosition = scene.getObjectByProperty("uuid", asset.modelUuid)?.position; - if (limitDistance && assetPosition) { - if (!isRendered && assetPosition.distanceTo(camera.position) <= renderDistance) { - setIsRendered(true); - } else if (isRendered && assetPosition.distanceTo(camera.position) > renderDistance) { - setIsRendered(false); - } - } else { - if (!isRendered) { - setIsRendered(true); - } - } - }) - const handleDblClick = (asset: Asset) => { if (asset) { if (activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') { diff --git a/app/src/modules/builder/asset/models/models.tsx b/app/src/modules/builder/asset/models/models.tsx index a7bd752..cdfa21b 100644 --- a/app/src/modules/builder/asset/models/models.tsx +++ b/app/src/modules/builder/asset/models/models.tsx @@ -1,17 +1,45 @@ -import Model from './model/model'; -import { useThree } from '@react-three/fiber'; +import { useEffect, useRef, useState } from "react"; +import { useThree, useFrame } from "@react-three/fiber"; +import { Vector3 } from "three"; import { CameraControls } from '@react-three/drei'; -import { Vector3 } from 'three'; -import { useSelectedFloorItem } from '../../../../store/builder/store'; +import { useLimitDistance, useRenderDistance, useSelectedFloorItem } from '../../../../store/builder/store'; import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore'; import { useSceneContext } from '../../../scene/sceneContext'; +import Model from './model/model'; + +const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url)); + function Models() { - const { controls } = useThree(); + const { controls, camera } = useThree(); const { assetStore } = useSceneContext(); const { assets } = assetStore(); const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); + const { limitDistance } = useLimitDistance(); + const { renderDistance } = useRenderDistance(); + + const [renderMap, setRenderMap] = useState>({}); + + const cameraPos = useRef(new Vector3()); + + useEffect(() => { + distanceWorker.onmessage = (e) => { + const { shouldRender, modelUuid } = e.data; + setRenderMap((prev) => { + if (prev[modelUuid] === shouldRender) return prev; + return { ...prev, [modelUuid]: shouldRender }; + }); + }; + }, []); + + useFrame(() => { + camera.getWorldPosition(cameraPos.current); + for (const asset of assets) { + const isRendered = renderMap[asset.modelUuid] ?? false; + distanceWorker.postMessage({ modelUuid: asset.modelUuid, assetPosition: { x: asset.position[0], y: asset.position[1], z: asset.position[2], }, cameraPosition: cameraPos.current, limitDistance, renderDistance, isRendered, }); + } + }); return ( - {assets.map((asset) => - - )} + {assets.map((asset) => ( + + ))} - ) + ); } -export default Models \ No newline at end of file +export default Models; \ No newline at end of file diff --git a/app/src/services/factoryBuilder/webWorkers/distanceWorker.js b/app/src/services/factoryBuilder/webWorkers/distanceWorker.js index 84ba747..44111c5 100644 --- a/app/src/services/factoryBuilder/webWorkers/distanceWorker.js +++ b/app/src/services/factoryBuilder/webWorkers/distanceWorker.js @@ -1,12 +1,16 @@ import { Vector3 } from "three"; onmessage = function (e) { - const { modelUuid, assetPosition, cameraPosition, limitDistance, renderDistance, isRendered } = e.data; + const { modelUuid, assetPosition, cameraPosition, limitDistance, renderDistance, isRendered, } = e.data; - if (limitDistance && assetPosition) { - if (!isRendered && new Vector3(assetPosition.x, assetPosition.y, assetPosition.z).distanceTo(cameraPosition) <= renderDistance) { + const assetVec = new Vector3(assetPosition.x, assetPosition.y, assetPosition.z); + const cameraVec = new Vector3(cameraPosition.x, cameraPosition.y, cameraPosition.z); + const distance = assetVec.distanceTo(cameraVec); + + if (limitDistance) { + if (!isRendered && distance <= renderDistance) { postMessage({ shouldRender: true, modelUuid }); - } else if (isRendered && new Vector3(assetPosition.x, assetPosition.y, assetPosition.z).distanceTo(cameraPosition) > renderDistance) { + } else if (isRendered && distance > renderDistance) { postMessage({ shouldRender: false, modelUuid }); } } else { @@ -14,4 +18,4 @@ onmessage = function (e) { postMessage({ shouldRender: true, modelUuid }); } } -}; \ No newline at end of file +}; From 253b3db2ed7c769978acda15cfcce038770f7a91 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 29 Jul 2025 11:24:46 +0530 Subject: [PATCH 17/18] Refactor position handling in CopyPasteControls3D and DuplicationControls3D; update useRetrieveHandler to include machine and human event management; implement event handlers for model interactions in useEventHandlers. --- .../model/eventHandlers/useEventHandlers.ts | 238 ++++++++++++++++++ .../selection3D/copyPasteControls3D.tsx | 2 +- .../selection3D/duplicationControls3D.tsx | 2 +- .../actionHandler/useRetrieveHandler.ts | 97 ++++++- .../machineInstance/machineInstance.tsx | 2 +- .../triggerHandler/useTriggerHandler.ts | 30 ++- 6 files changed, 355 insertions(+), 16 deletions(-) create mode 100644 app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts diff --git a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts new file mode 100644 index 0000000..d678382 --- /dev/null +++ b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts @@ -0,0 +1,238 @@ +import * as THREE from 'three'; +import { CameraControls } from '@react-three/drei'; +import { ThreeEvent } from '@react-three/fiber'; +import { useCallback } from 'react'; +import { ProductStoreType } from '../../../../../../store/simulation/useProductStore'; +import { EventStoreType } from '../../../../../../store/simulation/useEventsStore'; +import { Socket } from 'socket.io-client'; + +import { useActiveTool, useToolMode } from '../../../../../../store/builder/store'; +import useModuleStore, { useSubModuleStore } from '../../../../../../store/useModuleStore'; +import { useSocketStore } from '../../../../../../store/builder/store'; +import { useSceneContext } from '../../../../../scene/sceneContext'; +import { useProductContext } from '../../../../../simulation/products/productContext'; +import { useVersionContext } from '../../../../version/versionContext'; +import { useParams } from 'react-router-dom'; +import { getUserData } from '../../../../../../functions/getUserData'; + +// import { deleteFloorItem } from '../../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi'; + +export function useModelEventHandlers({ + controls, + boundingBox, + groupRef, + toggleView, + deletableFloorItem, + setDeletableFloorItem, + setSelectedFloorItem, + gl, + setTop, + setLeft, + getIsEventInProduct, + getEventByModelUuid, + setSelectedAsset, + clearSelectedAsset, + removeAsset, + updateBackend, + leftDrag, + rightDrag +}: { + controls: CameraControls | any, + boundingBox: THREE.Box3 | null, + groupRef: React.RefObject, + toggleView: boolean, + deletableFloorItem: THREE.Object3D | null, + setDeletableFloorItem: (item: THREE.Object3D | null) => void, + setSelectedFloorItem: (item: THREE.Object3D | null) => void, + gl: THREE.WebGLRenderer, + setTop: (top: number) => void, + setLeft: (left: number) => void, + getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean, + getEventByModelUuid: (modelUuid: string) => EventsSchema | undefined, + setSelectedAsset: (EventData: EventsSchema) => void, + clearSelectedAsset: () => void, + removeAsset: (modelUuid: string) => void, + updateBackend: (productName: string, productUuid: string, projectId: string, event: EventsSchema) => void, + leftDrag: React.MutableRefObject, + rightDrag: React.MutableRefObject +}) { + + const { activeTool } = useActiveTool(); + const { activeModule } = useModuleStore(); + const { subModule } = useSubModuleStore(); + const { socket } = useSocketStore(); + const { eventStore, productStore } = useSceneContext(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + const { userId, organization } = getUserData(); + + const handleDblClick = (asset: Asset) => { + if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') { + const size = boundingBox.getSize(new THREE.Vector3()); + const center = boundingBox.getCenter(new THREE.Vector3()); + + const front = new THREE.Vector3(0, 0, 1); + groupRef.current.localToWorld(front); + front.sub(groupRef.current.position).normalize(); + + const distance = Math.max(size.x, size.y, size.z) * 2; + const newPosition = center.clone().addScaledVector(front, distance); + + (controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true); + (controls as CameraControls).setTarget(center.x, center.y, center.z, true); + (controls as CameraControls).fitToBox(groupRef.current, true, { + cover: true, + paddingTop: 5, + paddingLeft: 5, + paddingBottom: 5, + paddingRight: 5, + }); + + setSelectedFloorItem(groupRef.current); + } + }; + + const handleClick = async (evt: ThreeEvent, asset: Asset) => { + if (leftDrag.current || toggleView) return; + if (activeTool === 'delete' && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) { + + //REST + + // const response = await deleteFloorItem(organization, asset.modelUuid, asset.modelName); + + //SOCKET + + const data = { + organization, + modelUuid: asset.modelUuid, + modelName: asset.modelName, + socketId: socket.id, + userId, + versionId: selectedVersion?.versionId || '', + projectId + } + + const response = socket.emit('v1:model-asset:delete', data) + + eventStore.getState().removeEvent(asset.modelUuid); + const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid); + + updatedEvents.forEach((event) => { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + }) + + if (response) { + + removeAsset(asset.modelUuid); + + echo.success("Model Removed!"); + } + + } else if (activeModule === 'simulation' && subModule === "simulations" && activeTool === 'pen') { + if (asset.eventData && asset.eventData.type === 'Conveyor') { + const intersectedPoint = evt.point; + const localPosition = groupRef.current?.worldToLocal(intersectedPoint.clone()); + if (localPosition) { + const conveyorPoint: ConveyorPointSchema = { + uuid: THREE.MathUtils.generateUUID(), + position: [localPosition?.x, localPosition?.y, localPosition?.z], + rotation: [0, 0, 0], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: `Action 1`, + actionType: 'default', + material: 'Default Material', + delay: 0, + spawnInterval: 5, + spawnCount: 1, + triggers: [] + } + } + + const event = productStore.getState().addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + } + } + + } + }; + + const handlePointerOver = useCallback((asset: Asset) => { + if (activeTool === "delete" && activeModule === 'builder') { + if (deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) { + return; + } else { + setDeletableFloorItem(groupRef.current); + } + } + }, [activeTool, activeModule, deletableFloorItem]); + + const handlePointerOut = useCallback((evt: ThreeEvent, asset: Asset) => { + if (evt.intersections.length === 0 && activeTool === "delete" && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) { + setDeletableFloorItem(null); + } + }, [activeTool, deletableFloorItem]); + + const handleContextMenu = (asset: Asset, evt: ThreeEvent) => { + if (rightDrag.current || toggleView) return; + if (activeTool === "cursor" && subModule === 'simulations') { + if (asset.modelUuid) { + const canvasElement = gl.domElement; + const isInProduct = getIsEventInProduct(selectedProduct.productUuid, asset.modelUuid); + if (isInProduct) { + const event = getEventByModelUuid(asset.modelUuid); + if (event) { + setSelectedAsset(event); + const canvasRect = canvasElement.getBoundingClientRect(); + const relativeX = evt.clientX - canvasRect.left; + const relativeY = evt.clientY - canvasRect.top; + setTop(relativeY); + setLeft(relativeX); + } else { + clearSelectedAsset(); + } + } else { + const event = getEventByModelUuid(asset.modelUuid); + if (event) { + setSelectedAsset(event) + const canvasRect = canvasElement.getBoundingClientRect(); + const relativeX = evt.clientX - canvasRect.left; + const relativeY = evt.clientY - canvasRect.top; + setTop(relativeY); + setLeft(relativeX); + } else { + clearSelectedAsset() + } + } + } else { + clearSelectedAsset() + } + } else { + clearSelectedAsset() + } + } + + return { + handleDblClick, + handleClick, + handlePointerOver, + handlePointerOut, + handleContextMenu + }; +} diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index 24a5f58..a147750 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -213,7 +213,7 @@ const CopyPasteControls3D = ({ modelUuid: pastedAsset.userData.modelUuid, modelName: pastedAsset.userData.modelName, assetId: pastedAsset.userData.assetId, - position: asset.position, + position: [position.x, position.y, position.z], rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] }, isLocked: false, isVisible: true, diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index 5ab6b75..55e88fb 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -214,7 +214,7 @@ const DuplicationControls3D = ({ modelUuid: duplicatedAsset.userData.modelUuid, modelName: duplicatedAsset.userData.modelName, assetId: duplicatedAsset.userData.assetId, - position: asset.position, + position: [position.x, position.y, position.z], rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] }, isLocked: false, isVisible: true, diff --git a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts index e951289..0373fd0 100644 --- a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts +++ b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts @@ -3,9 +3,10 @@ import { useFrame } from "@react-three/fiber"; import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore, useAnimationPlaySpeed } from "../../../../../store/usePlayButtonStore"; import { useSceneContext } from "../../../../scene/sceneContext"; import { useProductContext } from "../../../products/productContext"; +import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager"; export function useRetrieveHandler() { - const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext(); + const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext(); const { selectedProductStore } = useProductContext(); const { addMaterial } = materialStore(); const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore(); @@ -15,14 +16,17 @@ export function useRetrieveHandler() { const { getAssetById, setCurrentAnimation } = assetStore(); const { selectedProduct } = selectedProductStore(); const { getArmBotById, addCurrentAction } = armBotStore(); + const { getMachineById } = machineStore(); const { isPlaying } = usePlayButtonStore(); const { speed } = useAnimationPlaySpeed(); const { isPaused } = usePauseButtonStore(); const { isReset } = useResetButtonStore(); + const { addHumanToMonitor } = useHumanEventManager(); const [activeRetrievals, setActiveRetrievals] = useState>(new Map()); const retrievalTimeRef = useRef>(new Map()); const retrievalCountRef = useRef>(new Map()); + const monitoredHumansRef = useRef>(new Set()); const [initialDelayComplete, setInitialDelayComplete] = useState(false); const delayTimerRef = useRef(null); @@ -312,9 +316,94 @@ export function useRetrieveHandler() { } if (human && !human.isScheduled && human.state === 'idle' && human.currentLoad < action.loadCapacity) { - if (humanAsset?.animationState?.current === 'idle') { - setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); - } else if (humanAsset?.animationState?.current === 'pickup' && humanAsset.animationState.isCompleted) { + const triggeredModel = action.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid + ? getEventByModelUuid(selectedProduct.productUuid, action.triggers[0].triggeredAsset.triggeredModel.modelUuid) + : null; + if (triggeredModel?.type === 'vehicle') { + const model = getVehicleById(triggeredModel.modelUuid); + if (model && !model.isActive && model.state === 'idle' && model.isPicking && model.currentLoad < model.point.action.loadCapacity) { + if (humanAsset?.animationState?.current === 'idle') { + setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); + } else if (humanAsset?.animationState?.current === 'pickup' && humanAsset.animationState.isCompleted) { + const lastMaterial = getLastMaterial(storageUnit.modelUuid); + if (lastMaterial) { + const material = createNewMaterial( + lastMaterial.materialId, + lastMaterial.materialType, + storageUnit.point.action + ); + if (material) { + removeLastMaterial(storageUnit.modelUuid); + updateCurrentLoad(storageUnit.modelUuid, -1); + incrementHumanLoad(human.modelUuid, 1); + addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId); + retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`); + + retrievalCountRef.current.set(actionUuid, currentCount + 1); + } + } + } + return; + } + } else if (triggeredModel?.type === 'roboticArm') { + const armBot = getArmBotById(triggeredModel.modelUuid); + if (armBot && !armBot.isActive && armBot.state === 'idle' && !armBot.currentAction) { + if (humanAsset?.animationState?.current === 'idle') { + setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); + } else if (humanAsset?.animationState?.current === 'pickup' && humanAsset.animationState.isCompleted) { + const lastMaterial = getLastMaterial(storageUnit.modelUuid); + if (lastMaterial) { + const material = createNewMaterial( + lastMaterial.materialId, + lastMaterial.materialType, + storageUnit.point.action + ); + if (material) { + removeLastMaterial(storageUnit.modelUuid); + updateCurrentLoad(storageUnit.modelUuid, -1); + incrementHumanLoad(human.modelUuid, 1); + addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId); + retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`); + + retrievalCountRef.current.set(actionUuid, currentCount + 1); + } + } + } + return; + } + } else if (triggeredModel?.type === 'machine') { + const machine = getMachineById(triggeredModel.modelUuid); + if (machine && !machine.isActive && machine.state === 'idle' && !machine.currentAction) { + if (!monitoredHumansRef.current.has(human.modelUuid)) { + addHumanToMonitor(human.modelUuid, () => { + if (humanAsset?.animationState?.current === 'idle') { + setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); + } + }, action.actionUuid); + } + monitoredHumansRef.current.add(human.modelUuid); + if (humanAsset?.animationState?.current === 'pickup' && humanAsset.animationState.isCompleted) { + const lastMaterial = getLastMaterial(storageUnit.modelUuid); + if (lastMaterial) { + const material = createNewMaterial( + lastMaterial.materialId, + lastMaterial.materialType, + storageUnit.point.action + ); + if (material) { + removeLastMaterial(storageUnit.modelUuid); + updateCurrentLoad(storageUnit.modelUuid, -1); + incrementHumanLoad(human.modelUuid, 1); + addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId); + retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`); + retrievalCountRef.current.set(actionUuid, currentCount + 1); + } + } + monitoredHumansRef.current.delete(human.modelUuid); + } + return; + } + } else if (triggeredModel?.type === 'storageUnit') { const lastMaterial = getLastMaterial(storageUnit.modelUuid); if (lastMaterial) { const material = createNewMaterial( diff --git a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx index f45f909..879c985 100644 --- a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx +++ b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx @@ -116,7 +116,7 @@ function MachineInstance({ machineDetail }: { readonly machineDetail: MachineSta function handleCallBack() { if (currentPhase == "processing") { setMachineState(machineDetail.modelUuid, 'idle'); - setMachineActive(machineDetail.modelUuid, false); + setMachineActive(machineDetail.modelUuid, true); setCurrentPhase("idle") isIncrememtable.current = true; machineStatus(machineDetail.modelUuid, "Machine has completed the processing") diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 4dc5ae2..04e7fa7 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -23,7 +23,7 @@ export function useTriggerHandler() { const { addHumanToMonitor } = useHumanEventManager(); const { getVehicleById } = vehicleStore(); const { getHumanById, setHumanScheduled } = humanStore(); - const { getMachineById } = machineStore(); + const { getMachineById, setMachineActive } = machineStore(); const { getStorageUnitById } = storageUnitStore(); const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore(); @@ -630,21 +630,24 @@ export function useTriggerHandler() { const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || ''); if (previousModel) { if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) { + setMachineActive(trigger.triggerUuid, false); handleAction(action, materialId) } else { addConveyorToMonitor(conveyor.modelUuid, () => { - handleAction(action, materialId) + setMachineActive(trigger.triggerUuid, false); + handleAction(action, materialId); } ) } } else { - handleAction(action, materialId) + setMachineActive(trigger.triggerUuid, false); + handleAction(action, materialId); } - // handleAction(action, materialId) } } else { - handleAction(action, materialId) + setMachineActive(trigger.triggerUuid, false); + handleAction(action, materialId); } } else { @@ -660,21 +663,24 @@ export function useTriggerHandler() { const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || ''); if (previousModel) { if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) { + setMachineActive(trigger.triggerUuid, false); handleAction(action, materialId) } else { addConveyorToMonitor(conveyor.modelUuid, () => { - handleAction(action, materialId) + setMachineActive(trigger.triggerUuid, false); + handleAction(action, materialId); } ) } } else { - handleAction(action, materialId) + setMachineActive(trigger.triggerUuid, false); + handleAction(action, materialId); } - // handleAction(action, materialId) } } else { - handleAction(action, materialId) + setMachineActive(trigger.triggerUuid, false); + handleAction(action, materialId); } } ); @@ -723,15 +729,18 @@ export function useTriggerHandler() { if (previousModel) { if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) { setHumanScheduled(human.modelUuid, true); + setMachineActive(trigger.triggerUuid, false); handleAction(action, materialId) } else { setHumanScheduled(human.modelUuid, true); addConveyorToMonitor(conveyor.modelUuid, () => { + setMachineActive(trigger.triggerUuid, false); handleAction(action, materialId) }) } } else { setHumanScheduled(human.modelUuid, true); + setMachineActive(trigger.triggerUuid, false); handleAction(action, materialId) } } @@ -742,6 +751,7 @@ export function useTriggerHandler() { // Handle current action from vehicle setIsPaused(materialId, true); setHumanScheduled(human.modelUuid, true); + setMachineActive(trigger.triggerUuid, false); handleAction(action, materialId); } else { @@ -750,12 +760,14 @@ export function useTriggerHandler() { setHumanScheduled(human.modelUuid, true); addVehicleToMonitor(vehicle.modelUuid, () => { + setMachineActive(trigger.triggerUuid, false); handleAction(action, materialId); }) } } } else { setHumanScheduled(human.modelUuid, true); + setMachineActive(trigger.triggerUuid, false); handleAction(action, materialId) } }, action.actionUuid); From fcd924eb313e6462c28b808b1951a8eda406ce25 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 29 Jul 2025 17:20:34 +0530 Subject: [PATCH 18/18] feat: Implement undo and redo functionality for 2D scene controls - Added useRedoHandler to manage redo actions, including socket communication for wall, floor, zone, and aisle updates. - Added useUndoHandler to manage undo actions, reversing the effects of previous actions with corresponding socket updates. - Created UndoRedo2DControls component to handle keyboard shortcuts for undo (Ctrl+Z) and redo (Ctrl+Y). - Established a Zustand store (useUndoRedo2DStore) to maintain undo and redo stacks, with methods for pushing, popping, and peeking actions. --- .../floor/floorCreator/floorCreator.tsx | 52 +- app/src/modules/builder/line/line.tsx | 92 +++- app/src/modules/builder/point/point.tsx | 203 ++++++- .../builder/wall/wallCreator/wallCreator.tsx | 86 ++- .../builder/zone/zoneCreator/zoneCreator.tsx | 52 +- app/src/modules/scene/controls/controls.tsx | 3 + .../handlers/useRedoHandler.ts | 355 ++++++++++++ .../handlers/useUndoHandler.ts | 356 ++++++++++++ .../undoRedo2D/undoRedo2DControls.tsx | 49 ++ app/src/modules/scene/sceneContext.tsx | 12 +- app/src/store/builder/useFloorStore.ts | 59 +- app/src/store/builder/useUndoRedo2DStore.ts | 78 +++ app/src/store/builder/useZoneStore.ts | 59 +- app/src/types/builderTypes.d.ts | 38 ++ app/src/types/world/worldConstants.ts | 508 +++++++++--------- 15 files changed, 1701 insertions(+), 301 deletions(-) create mode 100644 app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts create mode 100644 app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts create mode 100644 app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx create mode 100644 app/src/store/builder/useUndoRedo2DStore.ts diff --git a/app/src/modules/builder/floor/floorCreator/floorCreator.tsx b/app/src/modules/builder/floor/floorCreator/floorCreator.tsx index acab425..28450aa 100644 --- a/app/src/modules/builder/floor/floorCreator/floorCreator.tsx +++ b/app/src/modules/builder/floor/floorCreator/floorCreator.tsx @@ -19,8 +19,9 @@ function FloorCreator() { const { toolMode } = useToolMode(); const { activeLayer } = useActiveLayer(); const { socket } = useSocketStore(); - const { floorStore } = useSceneContext(); - const { addFloor, getFloorPointById, getFloorByPoints } = floorStore(); + const { floorStore, undoRedo2DStore } = useSceneContext(); + const { addFloor, getFloorPointById } = floorStore(); + const { push2D } = undoRedo2DStore(); const drag = useRef(false); const isLeftMouseDown = useRef(false); const { selectedVersionStore } = useVersionContext(); @@ -103,6 +104,21 @@ function FloorCreator() { }; addFloor(floor); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Floor', + lineData: floor, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -142,6 +158,21 @@ function FloorCreator() { }; addFloor(floor); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Floor', + lineData: floor, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -191,6 +222,21 @@ function FloorCreator() { }; addFloor(floor); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Floor', + lineData: floor, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -243,7 +289,7 @@ function FloorCreator() { canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("contextmenu", onContext); }; - }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addFloor, getFloorPointById, getFloorByPoints, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]); + }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addFloor, getFloorPointById, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]); return ( <> diff --git a/app/src/modules/builder/line/line.tsx b/app/src/modules/builder/line/line.tsx index a0470aa..c9f5e9b 100644 --- a/app/src/modules/builder/line/line.tsx +++ b/app/src/modules/builder/line/line.tsx @@ -30,10 +30,11 @@ function Line({ points }: Readonly) { const [isDeletable, setIsDeletable] = useState(false); const { socket } = useSocketStore(); const { toolMode } = useToolMode(); - const { wallStore, floorStore, zoneStore } = useSceneContext(); + const { wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext(); + const { push2D } = undoRedo2DStore(); const { removeWallByPoints, setPosition: setWallPosition, getWallsByPointId } = wallStore(); - const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId } = floorStore(); - const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId } = zoneStore(); + const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId, getFloorsByPoints } = floorStore(); + const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId, getZonesByPoints } = zoneStore(); const { userId, organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -117,10 +118,26 @@ function Line({ points }: Readonly) { } socket.emit('v1:model-Wall:delete', data); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Delete', + point: { + type: 'Wall', + lineData: removedWall, + timeStamp: new Date().toISOString(), + } + } + ] + }); } + setHoveredLine(null); } if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') { + const Floors = getFloorsByPoints(points); const { removedFloors, updatedFloors } = removeFloorByPoints(points); if (removedFloors.length > 0) { removedFloors.forEach(floor => { @@ -143,6 +160,22 @@ function Line({ points }: Readonly) { socket.emit('v1:model-Floor:delete', data); } }); + + const removedFloorsData = removedFloors.map((floor) => ({ + type: "Floor" as const, + lineData: floor, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Delete', + points: removedFloorsData + } + ] + }); } if (updatedFloors.length > 0) { updatedFloors.forEach(floor => { @@ -165,11 +198,29 @@ function Line({ points }: Readonly) { socket.emit('v1:model-Floor:add', data); } }); + + const updatedFloorsData = updatedFloors.map((floor) => ({ + type: "Floor" as const, + lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor, + newData: floor, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Update', + points: updatedFloorsData + } + ] + }); } setHoveredLine(null); } if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') { + const Zones = getZonesByPoints(points); const { removedZones, updatedZones } = removeZoneByPoints(points); if (removedZones.length > 0) { removedZones.forEach(zone => { @@ -192,6 +243,22 @@ function Line({ points }: Readonly) { socket.emit('v1:zone:delete', data); } }); + + const removedZonesData = removedZones.map((zone) => ({ + type: "Zone" as const, + lineData: zone, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Delete', + points: removedZonesData + } + ] + }); } if (updatedZones.length > 0) { updatedZones.forEach(zone => { @@ -214,7 +281,26 @@ function Line({ points }: Readonly) { socket.emit('v1:zone:add', data); } }); + + const updatedZonesData = updatedZones.map((zone) => ({ + type: "Zone" as const, + lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone, + newData: zone, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Update', + points: updatedZonesData + } + ] + }); } + + setHoveredLine(null); } handleCanvasCursors('default'); } diff --git a/app/src/modules/builder/point/point.tsx b/app/src/modules/builder/point/point.tsx index 5c62dd5..b203f05 100644 --- a/app/src/modules/builder/point/point.tsx +++ b/app/src/modules/builder/point/point.tsx @@ -32,13 +32,14 @@ function Point({ point }: { readonly point: Point }) { const [dragOffset, setDragOffset] = useState(null); const { socket } = useSocketStore(); const { toolMode } = useToolMode(); - const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext(); + const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext(); + const { push2D } = undoRedo2DStore(); const { setPosition: setAislePosition, removePoint: removeAislePoint, getAislesByPointId } = aisleStore(); const { setPosition: setWallPosition, removePoint: removeWallPoint, getWallsByPointId } = wallStore(); const { setPosition: setFloorPosition, removePoint: removeFloorPoint, getFloorsByPointId } = floorStore(); const { setPosition: setZonePosition, removePoint: removeZonePoint, getZonesByPointId } = zoneStore(); const { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle, snapFloorPoint, snapFloorAngle, snapZonePoint, snapZoneAngle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position }); - const { hoveredPoint,hoveredLine, setHoveredPoint } = useBuilderStore(); + const { hoveredPoint, hoveredLine, setHoveredPoint } = useBuilderStore(); const { selectedPoints } = useSelectedPoints(); const { userId, organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); @@ -47,6 +48,13 @@ function Point({ point }: { readonly point: Point }) { const boxScale: [number, number, number] = Constants.pointConfig.boxScale; const colors = getColor(point); + const [initialPositions, setInitialPositions] = useState<{ + aisles?: Aisle[], + walls?: Wall[], + floors?: Floor[], + zones?: Zone[] + }>({}); + useEffect(() => { handleCanvasCursors('default'); }, [toolMode]) @@ -152,6 +160,20 @@ function Point({ point }: { readonly point: Point }) { const currentPosition = new THREE.Vector3(...point.position); const offset = new THREE.Vector3().subVectors(currentPosition, hit); setDragOffset(offset); + + if (point.pointType === 'Aisle') { + const aisles = getAislesByPointId(point.pointUuid); + setInitialPositions({ aisles }); + } else if (point.pointType === 'Wall') { + const walls = getWallsByPointId(point.pointUuid); + setInitialPositions({ walls }); + } else if (point.pointType === 'Floor') { + const floors = getFloorsByPointId(point.pointUuid); + setInitialPositions({ floors }); + } else if (point.pointType === 'Zone') { + const zones = getZonesByPointId(point.pointUuid); + setInitialPositions({ zones }); + } } }; @@ -159,6 +181,7 @@ function Point({ point }: { readonly point: Point }) { handleCanvasCursors('default'); setDragOffset(null); if (toolMode !== 'move') return; + if (point.pointType === 'Aisle') { const updatedAisles = getAislesByPointId(point.pointUuid); if (updatedAisles.length > 0 && projectId) { @@ -180,6 +203,23 @@ function Point({ point }: { readonly point: Point }) { type: updatedAisle.type }) }) + + if (initialPositions.aisles && initialPositions.aisles.length > 0) { + const updatedPoints = initialPositions.aisles.map((aisle) => ({ + type: "Aisle" as const, + lineData: aisle, + newData: updatedAisles.find(a => a.aisleUuid === aisle.aisleUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [{ + actionType: 'Lines-Update', + points: updatedPoints, + }] + }); + } } } else if (point.pointType === 'Wall') { const updatedWalls = getWallsByPointId(point.pointUuid); @@ -203,6 +243,23 @@ function Point({ point }: { readonly point: Point }) { socket.emit('v1:model-Wall:add', data); }); } + + if (initialPositions.walls && initialPositions.walls.length > 0) { + const updatedPoints = initialPositions.walls.map((wall) => ({ + type: "Wall" as const, + lineData: wall, + newData: updatedWalls.find(w => w.wallUuid === wall.wallUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [{ + actionType: 'Lines-Update', + points: updatedPoints, + }] + }); + } } else if (point.pointType === 'Floor') { const updatedFloors = getFloorsByPointId(point.pointUuid); if (updatedFloors && updatedFloors.length > 0 && projectId) { @@ -225,6 +282,23 @@ function Point({ point }: { readonly point: Point }) { socket.emit('v1:model-Floor:add', data); }); } + + if (initialPositions.floors && initialPositions.floors.length > 0) { + const updatedPoints = initialPositions.floors.map((floor) => ({ + type: "Floor" as const, + lineData: floor, + newData: updatedFloors.find(f => f.floorUuid === floor.floorUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [{ + actionType: 'Lines-Update', + points: updatedPoints, + }] + }); + } } else if (point.pointType === 'Zone') { const updatedZones = getZonesByPointId(point.pointUuid); if (updatedZones && updatedZones.length > 0 && projectId) { @@ -247,13 +321,33 @@ function Point({ point }: { readonly point: Point }) { socket.emit('v1:zone:add', data); }); } + + if (initialPositions.zones && initialPositions.zones.length > 0) { + const updatedPoints = initialPositions.zones.map((zone) => ({ + type: "Zone" as const, + lineData: zone, + newData: updatedZones.find(z => z.zoneUuid === zone.zoneUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [{ + actionType: 'Lines-Update', + points: updatedPoints, + }] + }); + } } + + setInitialPositions({}); } const handlePointClick = (point: Point) => { if (toolMode === '2D-Delete') { if (point.pointType === 'Aisle') { const removedAisles = removeAislePoint(point.pointUuid); + setHoveredPoint(null); if (removedAisles.length > 0) { removedAisles.forEach(aisle => { if (projectId) { @@ -273,9 +367,25 @@ function Point({ point }: { readonly point: Point }) { } socket.emit('v1:model-aisle:delete', data); + } }); - setHoveredPoint(null); + + const removedAislesData = removedAisles.map((aisle) => ({ + type: "Aisle" as const, + lineData: aisle, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Delete', + points: removedAislesData + } + ] + }); } } if (point.pointType === 'Wall') { @@ -302,9 +412,26 @@ function Point({ point }: { readonly point: Point }) { socket.emit('v1:model-Wall:delete', data); } }); + + const removedWallsData = removedWalls.map((wall) => ({ + type: "Wall" as const, + lineData: wall, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Delete', + points: removedWallsData + } + ] + }); } } if (point.pointType === 'Floor') { + const Floors = getFloorsByPointId(point.pointUuid); const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid); setHoveredPoint(null); if (removedFloors.length > 0) { @@ -328,6 +455,22 @@ function Point({ point }: { readonly point: Point }) { socket.emit('v1:model-Floor:delete', data); } }); + + const removedFloorsData = removedFloors.map((floor) => ({ + type: "Floor" as const, + lineData: floor, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Delete', + points: removedFloorsData + } + ] + }); } if (updatedFloors.length > 0) { updatedFloors.forEach(floor => { @@ -350,9 +493,27 @@ function Point({ point }: { readonly point: Point }) { socket.emit('v1:model-Floor:add', data); } }); + + const updatedFloorsData = updatedFloors.map((floor) => ({ + type: "Floor" as const, + lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor, + newData: floor, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Update', + points: updatedFloorsData + } + ] + }); } } if (point.pointType === 'Zone') { + const Zones = getZonesByPointId(point.pointUuid); const { removedZones, updatedZones } = removeZonePoint(point.pointUuid); setHoveredPoint(null); if (removedZones.length > 0) { @@ -376,6 +537,22 @@ function Point({ point }: { readonly point: Point }) { socket.emit('v1:zone:delete', data); } }); + + const removedZonesData = removedZones.map((zone) => ({ + type: "Zone" as const, + lineData: zone, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Delete', + points: removedZonesData + } + ] + }); } if (updatedZones.length > 0) { updatedZones.forEach(zone => { @@ -398,6 +575,23 @@ function Point({ point }: { readonly point: Point }) { socket.emit('v1:zone:add', data); } }); + + const updatedZonesData = updatedZones.map((zone) => ({ + type: "Zone" as const, + lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone, + newData: zone, + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Update', + points: updatedZonesData + } + ] + }); } } handleCanvasCursors('default'); @@ -422,7 +616,6 @@ function Point({ point }: { readonly point: Point }) { return null; } - return ( <> {!isSelected ? @@ -453,7 +646,7 @@ function Point({ point }: { readonly point: Point }) { onPointerOut={() => { if (hoveredPoint) { setHoveredPoint(null); - if(!hoveredLine){ + if (!hoveredLine) { handleCanvasCursors('default'); } } diff --git a/app/src/modules/builder/wall/wallCreator/wallCreator.tsx b/app/src/modules/builder/wall/wallCreator/wallCreator.tsx index a80a62d..52d5d7e 100644 --- a/app/src/modules/builder/wall/wallCreator/wallCreator.tsx +++ b/app/src/modules/builder/wall/wallCreator/wallCreator.tsx @@ -22,8 +22,9 @@ function WallCreator() { const { toolMode } = useToolMode(); const { activeLayer } = useActiveLayer(); const { socket } = useSocketStore(); - const { wallStore } = useSceneContext(); + const { wallStore, undoRedo2DStore } = useSceneContext(); const { addWall, getWallPointById, removeWall, getWallByPoints } = wallStore(); + const { push2D } = undoRedo2DStore(); const drag = useRef(false); const isLeftMouseDown = useRef(false); const { selectedVersionStore } = useVersionContext(); @@ -91,6 +92,7 @@ function WallCreator() { const closestPoint = new THREE.Vector3().lerpVectors(point1Vec, point2Vec, t); removeWall(wall.wallUuid); + if (projectId) { // API @@ -142,6 +144,7 @@ function WallCreator() { wallHeight: wallHeight, decals: [] } + addWall(wall2); // API @@ -171,8 +174,36 @@ function WallCreator() { wallHeight: wallHeight, decals: [] } + addWall(wall3); + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Create', + points: [ + { + type: 'Wall', + lineData: wall3, + timeStamp: new Date().toISOString(), + }, { + type: 'Wall', + lineData: wall2, + timeStamp: new Date().toISOString(), + } + ] + }, { + actionType: 'Line-Delete', + point: { + type: 'Wall', + lineData: wall, + timeStamp: new Date().toISOString(), + } + } + ], + }) + // API // if (projectId) { @@ -202,7 +233,8 @@ function WallCreator() { wallThickness: wallThickness, wallHeight: wallHeight, decals: [] - }; + } + addWall(wall1); // API @@ -232,6 +264,7 @@ function WallCreator() { wallHeight: wallHeight, decals: [] } + addWall(wall2); // API @@ -261,8 +294,40 @@ function WallCreator() { wallHeight: wallHeight, decals: [] } + addWall(wall3); + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Create', + points: [ + { + type: 'Wall', + lineData: wall3, + timeStamp: new Date().toISOString(), + }, { + type: 'Wall', + lineData: wall1, + timeStamp: new Date().toISOString(), + }, { + type: 'Wall', + lineData: wall2, + timeStamp: new Date().toISOString(), + } + ] + }, { + actionType: 'Line-Delete', + point: { + type: 'Wall', + lineData: wall, + timeStamp: new Date().toISOString(), + } + } + ], + }) + // API // if (projectId) { @@ -328,9 +393,24 @@ function WallCreator() { wallThickness: wallThickness, wallHeight: wallHeight, decals: [] - }; + } + addWall(wall); + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Wall', + lineData: wall, + timeStamp: new Date().toISOString(), + } + } + ], + }) + // API // if (projectId) { diff --git a/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx b/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx index f1f1b6a..bed501f 100644 --- a/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx +++ b/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx @@ -19,8 +19,9 @@ function ZoneCreator() { const { toolMode } = useToolMode(); const { activeLayer } = useActiveLayer(); const { socket } = useSocketStore(); - const { zoneStore } = useSceneContext(); - const { zones, addZone, getZonePointById, getZoneByPoints } = zoneStore(); + const { zoneStore, undoRedo2DStore } = useSceneContext(); + const { addZone, getZonePointById } = zoneStore(); + const { push2D } = undoRedo2DStore(); const drag = useRef(false); const isLeftMouseDown = useRef(false); const { selectedVersionStore } = useVersionContext(); @@ -102,6 +103,21 @@ function ZoneCreator() { }; addZone(zone); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Zone', + lineData: zone, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -139,6 +155,21 @@ function ZoneCreator() { }; addZone(zone); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Zone', + lineData: zone, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -186,6 +217,21 @@ function ZoneCreator() { }; addZone(zone); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Zone', + lineData: zone, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -238,7 +284,7 @@ function ZoneCreator() { canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("contextmenu", onContext); }; - }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, getZoneByPoints, zoneColor, zoneHeight, snappedPosition, snappedPoint]); + }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, zoneColor, zoneHeight, snappedPosition, snappedPoint]); return ( <> diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx index 975ab54..77f234c 100644 --- a/app/src/modules/scene/controls/controls.tsx +++ b/app/src/modules/scene/controls/controls.tsx @@ -14,6 +14,7 @@ import TransformControl from "./transformControls/transformControls"; import { useParams } from "react-router-dom"; import { getUserData } from "../../../functions/getUserData"; import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D"; +import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls"; export default function Controls() { const controlsRef = useRef(null); @@ -142,6 +143,8 @@ export default function Controls() { + + diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts new file mode 100644 index 0000000..e5f31a5 --- /dev/null +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts @@ -0,0 +1,355 @@ +import { useParams } from "react-router-dom"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useVersionContext } from "../../../../builder/version/versionContext"; +import { useSceneContext } from "../../../sceneContext"; +import { useSocketStore } from "../../../../../store/builder/store"; + +// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi"; +// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi"; + +// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi"; +// import { deleteZoneApi } from "../../../../../services/factoryBuilder/zone/deleteZoneApi"; + +// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi"; +// import { deleteFloorApi } from "../../../../../services/factoryBuilder/floor/deleteFloorApi"; + +// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi"; +// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi"; + +function useRedoHandler() { + const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext(); + const { redo2D, peekRedo2D } = undoRedo2DStore(); + const { addWall, removeWall, updateWall } = wallStore(); + const { addFloor, removeFloor, updateFloor } = floorStore(); + const { addZone, removeZone, updateZone } = zoneStore(); + const { addAisle, removeAisle, updateAisle } = aisleStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + const { socket } = useSocketStore(); + + const handleRedo = () => { + const redoData = peekRedo2D(); + if (!redoData) return; + + if (redoData.type === 'Draw') { + const { actions } = redoData; + + actions.forEach(action => { + const { actionType } = action; + + if ('point' in action) { + const point = action.point; + + if (actionType === 'Line-Create') { + handleCreate(point); + } else if (actionType === 'Line-Update') { + handleUpdate(point); + } else if (actionType === 'Line-Delete') { + handleRemove(point); + } + + } else if ('points' in action) { + const points = action.points; + + if (actionType === 'Lines-Create') { + points.forEach(handleCreate); + } else if (actionType === 'Lines-Update') { + points.forEach(handleUpdate); + } else if (actionType === 'Lines-Delete') { + points.forEach(handleRemove); + } + } + }); + } else if (redoData.type === 'UI') { + // Handle UI actions if needed + } + + redo2D(); + }; + + const handleCreate = (point: UndoRedo2DDataTypeSchema) => { + switch (point.type) { + case 'Wall': createWallFromBackend(point.lineData); break; + case 'Floor': createFloorFromBackend(point.lineData); break; + case 'Zone': createZoneFromBackend(point.lineData); break; + case 'Aisle': createAisleFromBackend(point.lineData); break; + } + }; + + const handleRemove = (point: UndoRedo2DDataTypeSchema) => { + switch (point.type) { + case 'Wall': removeWallFromBackend(point.lineData.wallUuid); break; + case 'Floor': removeFloorFromBackend(point.lineData.floorUuid); break; + case 'Zone': removeZoneFromBackend(point.lineData.zoneUuid); break; + case 'Aisle': removeAisleFromBackend(point.lineData.aisleUuid); break; + } + }; + + const handleUpdate = (point: UndoRedo2DDataTypeSchema) => { + if (!point.newData) return; + switch (point.type) { + case 'Wall': updateWallFromBackend(point.newData.wallUuid, point.newData); break; + case 'Floor': updateFloorFromBackend(point.newData.floorUuid, point.newData); break; + case 'Zone': updateZoneFromBackend(point.newData.zoneUuid, point.newData); break; + case 'Aisle': updateAisleFromBackend(point.newData.aisleUuid, point.newData); break; + } + }; + + const createWallFromBackend = (wallData: Wall) => { + addWall(wallData); + if (projectId) { + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', wallData); + + // SOCKET + + const data = { + wallData: wallData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + } + }; + + const removeWallFromBackend = (wallUuid: string) => { + removeWall(wallUuid); + if (projectId) { + // API + + // deleteWallApi(projectId, selectedVersion?.versionId || '', wallUuid); + + // SOCKET + + const data = { + wallUuid: wallUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:delete', data); + } + }; + + const updateWallFromBackend = (wallUuid: string, updatedData: Wall) => { + updateWall(wallUuid, updatedData); + if (projectId) { + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedData); + + // SOCKET + + const data = { + wallData: updatedData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + } + }; + + const createFloorFromBackend = (floorData: Floor) => { + addFloor(floorData); + if (projectId) { + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', floorData); + + // SOCKET + + const data = { + floorData: floorData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + } + }; + + const removeFloorFromBackend = (floorUuid: string) => { + removeFloor(floorUuid); + if (projectId) { + // API + + // deleteFloorApi(projectId, selectedVersion?.versionId || '', floorUuid); + + // SOCKET + + const data = { + floorUuid: floorUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:delete', data); + } + }; + + const updateFloorFromBackend = (floorUuid: string, updatedData: Floor) => { + updateFloor(floorUuid, updatedData); + if (projectId) { + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedData); + + // SOCKET + + const data = { + floorData: updatedData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + } + }; + + const createZoneFromBackend = (zoneData: Zone) => { + addZone(zoneData); + if (projectId) { + // API + + // upsertZoneApi(projectId, selectedVersion?.versionId || '', zoneData); + + // SOCKET + + const data = { + zoneData: zoneData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:zone:add', data); + } + }; + + const removeZoneFromBackend = (zoneUuid: string) => { + removeZone(zoneUuid); + if (projectId) { + // API + + // deleteZoneApi(projectId, selectedVersion?.versionId || '', zoneUuid); + + // SOCKET + + const data = { + zoneUuid, + projectId, + versionId: selectedVersion?.versionId || '', + userId, + organization + }; + + socket.emit('v1:zone:delete', data); + } + }; + + const updateZoneFromBackend = (zoneUuid: string, updatedData: Zone) => { + updateZone(zoneUuid, updatedData); + if (projectId) { + // API + + // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedData); + + // SOCKET + + const data = { + zoneData: updatedData, + projectId, + versionId: selectedVersion?.versionId || '', + userId, + organization + }; + + socket.emit('v1:zone:add', data); + } + }; + + const createAisleFromBackend = (aisleData: Aisle) => { + addAisle(aisleData); + if (projectId) { + // API + + // upsertAisleApi(projectId, selectedVersion?.versionId || '', aisleData); + + // SOCKET + + const data = { + aisleData, + projectId, + versionId: selectedVersion?.versionId || '', + userId, + organization + }; + + socket.emit('v1:model-aisle:add', data); + } + }; + + const removeAisleFromBackend = (aisleUuid: string) => { + removeAisle(aisleUuid); + if (projectId) { + // API + + // deleteAisleApi(projectId, selectedVersion?.versionId || '', aisleUuid); + + // SOCKET + + const data = { + aisleUuid, + projectId, + versionId: selectedVersion?.versionId || '', + userId, + organization + }; + + socket.emit('v1:model-aisle:delete', data); + } + }; + + const updateAisleFromBackend = (aisleUuid: string, updatedData: Aisle) => { + updateAisle(aisleUuid, updatedData); + if (projectId) { + // API + + // upsertAisleApi(projectId, selectedVersion?.versionId || '', updatedData); + + // SOCKET + + const data = { + aisleData: updatedData, + projectId, + versionId: selectedVersion?.versionId || '', + userId, + organization + }; + + socket.emit('v1:model-aisle:add', data); + } + }; + + return { handleRedo }; +} + +export default useRedoHandler; diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts new file mode 100644 index 0000000..357f36e --- /dev/null +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts @@ -0,0 +1,356 @@ +import { useParams } from "react-router-dom"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useVersionContext } from "../../../../builder/version/versionContext"; +import { useSceneContext } from "../../../sceneContext"; +import { useSocketStore } from "../../../../../store/builder/store"; + +// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi"; +// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi"; + +// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi"; +// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi"; + +// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi"; +// import { deleteFloorApi } from "../../../../../services/factoryBuilder/floor/deleteFloorApi"; + +// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi"; +// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi"; + +function useUndoHandler() { + const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext(); + const { undo2D, peekUndo2D } = undoRedo2DStore(); + const { addWall, removeWall, updateWall } = wallStore(); + const { addFloor, removeFloor, updateFloor } = floorStore(); + const { addZone, removeZone, updateZone } = zoneStore(); + const { addAisle, removeAisle, updateAisle } = aisleStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + const { socket } = useSocketStore(); + + const handleUndo = () => { + const unDoData = peekUndo2D(); + if (!unDoData) return; + + if (unDoData.type === 'Draw') { + const { actions } = unDoData; + + actions.forEach(action => { + const { actionType } = action; + + if ('point' in action) { + const point = action.point; + + if (actionType === 'Line-Create') { + handleRemove(point); + } else if (actionType === 'Line-Update') { + handleUpdate(point); + } else if (actionType === 'Line-Delete') { + handleCreate(point); + } + + } else if ('points' in action) { + const points = action.points; + + if (actionType === 'Lines-Create') { + points.forEach(handleRemove); + } else if (actionType === 'Lines-Update') { + points.forEach(handleUpdate); + } else if (actionType === 'Lines-Delete') { + points.forEach(handleCreate); + } + } + }); + } else if (unDoData.type === 'UI') { + // Handle UI actions if needed + } + + undo2D(); + + }; + + const handleCreate = (point: UndoRedo2DDataTypeSchema) => { + switch (point.type) { + case 'Wall': createWallFromBackend(point.lineData); break; + case 'Floor': createFloorFromBackend(point.lineData); break; + case 'Zone': createZoneFromBackend(point.lineData); break; + case 'Aisle': createAisleFromBackend(point.lineData); break; + } + }; + + const handleRemove = (point: UndoRedo2DDataTypeSchema) => { + switch (point.type) { + case 'Wall': removeWallFromBackend(point.lineData.wallUuid); break; + case 'Floor': removeFloorFromBackend(point.lineData.floorUuid); break; + case 'Zone': removeZoneFromBackend(point.lineData.zoneUuid); break; + case 'Aisle': removeAisleFromBackend(point.lineData.aisleUuid); break; + } + }; + + const handleUpdate = (point: UndoRedo2DDataTypeSchema) => { + switch (point.type) { + case 'Wall': updateWallFromBackend(point.lineData.wallUuid, point.lineData); break; + case 'Floor': updateFloorFromBackend(point.lineData.floorUuid, point.lineData); break; + case 'Zone': updateZoneFromBackend(point.lineData.zoneUuid, point.lineData); break; + case 'Aisle': updateAisleFromBackend(point.lineData.aisleUuid, point.lineData); break; + } + }; + + + const createWallFromBackend = (wallData: Wall) => { + addWall(wallData); + if (projectId) { + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', wallData); + + // SOCKET + + const data = { + wallData: wallData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + } + }; + + const removeWallFromBackend = (wallUuid: string) => { + removeWall(wallUuid); + if (projectId) { + // API + + // deleteWallApi(projectId, selectedVersion?.versionId || '', wallUuid); + + // SOCKET + + const data = { + wallUuid: wallUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:delete', data); + } + }; + + const updateWallFromBackend = (wallUuid: string, updatedData: Wall) => { + updateWall(wallUuid, updatedData); + if (projectId) { + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedData); + + // SOCKET + + const data = { + wallData: updatedData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + } + }; + + const createFloorFromBackend = (floorData: Floor) => { + addFloor(floorData); + if (projectId) { + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', floorData); + + // SOCKET + + const data = { + floorData: floorData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + } + }; + + const removeFloorFromBackend = (floorUuid: string) => { + removeFloor(floorUuid); + if (projectId) { + // API + + // deleteFloorApi(projectId, selectedVersion?.versionId || '', floorUuid); + + // SOCKET + + const data = { + floorUuid: floorUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:delete', data); + } + }; + + const updateFloorFromBackend = (floorUuid: string, updatedData: Floor) => { + updateFloor(floorUuid, updatedData); + if (projectId) { + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedData); + + // SOCKET + + const data = { + floorData: updatedData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + } + }; + + const createZoneFromBackend = (zoneData: Zone) => { + addZone(zoneData); + if (projectId) { + // API + + // upsertZoneApi(projectId, selectedVersion?.versionId || '', zoneData); + + // SOCKET + + const data = { + zoneData: zoneData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:zone:add', data); + } + }; + + const removeZoneFromBackend = (zoneUuid: string) => { + removeZone(zoneUuid); + if (projectId) { + // API + + // deleteZoneApi(projectId, selectedVersion?.versionId || '', zoneUuid); + + // SOCKET + + const data = { + zoneUuid, + projectId, + versionId: selectedVersion?.versionId || '', + userId, + organization + }; + + socket.emit('v1:zone:delete', data); + } + }; + + const updateZoneFromBackend = (zoneUuid: string, updatedData: Zone) => { + updateZone(zoneUuid, updatedData); + if (projectId) { + // API + + // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedData); + + // SOCKET + + const data = { + zoneData: updatedData, + projectId, + versionId: selectedVersion?.versionId || '', + userId, + organization + }; + + socket.emit('v1:zone:add', data); + } + }; + + const createAisleFromBackend = (aisleData: Aisle) => { + addAisle(aisleData); + if (projectId) { + // API + + // upsertAisleApi(projectId, selectedVersion?.versionId || '', aisleData); + + // SOCKET + + const data = { + aisleData, + projectId, + versionId: selectedVersion?.versionId || '', + userId, + organization + }; + + socket.emit('v1:model-aisle:add', data); + } + }; + + const removeAisleFromBackend = (aisleUuid: string) => { + removeAisle(aisleUuid); + if (projectId) { + // API + + // deleteAisleApi(projectId, selectedVersion?.versionId || '', aisleUuid); + + // SOCKET + + const data = { + aisleUuid, + projectId, + versionId: selectedVersion?.versionId || '', + userId, + organization + }; + + socket.emit('v1:model-aisle:delete', data); + } + }; + + const updateAisleFromBackend = (aisleUuid: string, updatedData: Aisle) => { + updateAisle(aisleUuid, updatedData); + if (projectId) { + // API + + // upsertAisleApi(projectId, selectedVersion?.versionId || '', updatedData); + + // SOCKET + + const data = { + aisleData: updatedData, + projectId, + versionId: selectedVersion?.versionId || '', + userId, + organization + }; + + socket.emit('v1:model-aisle:add', data); + } + }; + + return { handleUndo }; +} + +export default useUndoHandler; \ No newline at end of file diff --git a/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx b/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx new file mode 100644 index 0000000..d45518c --- /dev/null +++ b/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx @@ -0,0 +1,49 @@ +import { useEffect } from 'react' +import { useSceneContext } from '../../../sceneContext' +import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys'; +import { useSocketStore, useToggleView } from '../../../../../store/builder/store'; +import { useVersionContext } from '../../../../builder/version/versionContext'; + +import useUndoHandler from '../handlers/useUndoHandler'; +import useRedoHandler from '../handlers/useRedoHandler'; + +function UndoRedo2DControls() { + const { undoRedo2DStore } = useSceneContext(); + const { undoStack, redoStack } = undoRedo2DStore(); + const { toggleView } = useToggleView(); + const { handleUndo } = useUndoHandler(); + const { handleRedo } = useRedoHandler(); + const { socket } = useSocketStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + + useEffect(() => { + console.log(undoStack, redoStack); + }, [undoStack, redoStack]); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (keyCombination === 'Ctrl+Z') { + handleUndo(); + } + + if (keyCombination === 'Ctrl+Y') { + handleRedo(); + } + }; + + if (toggleView) { + window.addEventListener('keydown', handleKeyDown); + } + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [toggleView, undoStack, redoStack, socket, selectedVersion]); + + return null; +} + +export default UndoRedo2DControls; diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index 187d66a..bf65c36 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -7,6 +7,8 @@ import { createAisleStore, AisleStoreType } from '../../store/builder/useAisleSt import { createZoneStore, ZoneStoreType } from '../../store/builder/useZoneStore'; import { createFloorStore, FloorStoreType } from '../../store/builder/useFloorStore'; +import { createUndoRedo2DStore, UndoRedo2DStoreType } from '../../store/builder/useUndoRedo2DStore'; + import { createEventStore, EventStoreType } from '../../store/simulation/useEventsStore'; import { createProductStore, ProductStoreType } from '../../store/simulation/useProductStore'; @@ -27,6 +29,8 @@ type SceneContextValue = { zoneStore: ZoneStoreType, floorStore: FloorStoreType, + undoRedo2DStore: UndoRedo2DStoreType, + eventStore: EventStoreType, productStore: ProductStoreType, @@ -62,6 +66,8 @@ export function SceneProvider({ const zoneStore = useMemo(() => createZoneStore(), []); const floorStore = useMemo(() => createFloorStore(), []); + const undoRedo2DStore = useMemo(() => createUndoRedo2DStore(), []); + const eventStore = useMemo(() => createEventStore(), []); const productStore = useMemo(() => createProductStore(), []); @@ -82,6 +88,7 @@ export function SceneProvider({ aisleStore.getState().clearAisles(); zoneStore.getState().clearZones(); floorStore.getState().clearFloors(); + undoRedo2DStore.getState().clearUndoRedo2D(); eventStore.getState().clearEvents(); productStore.getState().clearProducts(); materialStore.getState().clearMaterials(); @@ -92,7 +99,7 @@ export function SceneProvider({ storageUnitStore.getState().clearStorageUnits(); humanStore.getState().clearHumans(); humanEventManagerRef.current.humanStates = []; - }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]); + }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]); const contextValue = useMemo(() => ( { @@ -102,6 +109,7 @@ export function SceneProvider({ aisleStore, zoneStore, floorStore, + undoRedo2DStore, eventStore, productStore, materialStore, @@ -115,7 +123,7 @@ export function SceneProvider({ clearStores, layout } - ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, clearStores, layout]); + ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, clearStores, layout]); return ( diff --git a/app/src/store/builder/useFloorStore.ts b/app/src/store/builder/useFloorStore.ts index efb431b..a045884 100644 --- a/app/src/store/builder/useFloorStore.ts +++ b/app/src/store/builder/useFloorStore.ts @@ -28,7 +28,7 @@ interface FloorStore { getFloorById: (uuid: string) => Floor | undefined; getFloorsByPointId: (uuid: string) => Floor[] | []; - getFloorByPoints: (points: Point[]) => Floor | undefined; + getFloorsByPoints: (points: [Point, Point]) => Floor[] | []; getFloorPointById: (uuid: string) => Point | undefined; getConnectedPoints: (uuid: string) => Point[]; } @@ -74,10 +74,13 @@ export const createFloorStore = () => { const updatedFloors: Floor[] = []; set(state => { + const newFloors: Floor[] = []; + for (const floor of state.floors) { const pointIndex = floor.points.findIndex(p => p.pointUuid === pointUuid); + if (pointIndex === -1) { - updatedFloors.push(JSON.parse(JSON.stringify(floor))); + newFloors.push(floor); continue; } @@ -87,11 +90,13 @@ export const createFloorStore = () => { removedFloors.push(JSON.parse(JSON.stringify(floor))); continue; } - floor.points = remainingPoints; - updatedFloors.push(JSON.parse(JSON.stringify(floor))); + + const updatedFloor = { ...floor, points: remainingPoints }; + updatedFloors.push(JSON.parse(JSON.stringify(updatedFloor))); + newFloors.push(updatedFloor); } - state.floors = updatedFloors; + state.floors = newFloors; }); return { removedFloors, updatedFloors }; @@ -102,6 +107,7 @@ export const createFloorStore = () => { const updatedFloors: Floor[] = []; set(state => { + const newFloors: Floor[] = []; for (const floor of state.floors) { const indices = floor.points.map((p, i) => ({ uuid: p.pointUuid, index: i })); @@ -110,7 +116,7 @@ export const createFloorStore = () => { const idxB = indices.find(i => i.uuid === pointB.pointUuid)?.index ?? -1; if (idxA === -1 || idxB === -1) { - updatedFloors.push(JSON.parse(JSON.stringify(floor))); + newFloors.push(floor); continue; } @@ -120,7 +126,7 @@ export const createFloorStore = () => { (idxB === 0 && idxA === floor.points.length - 1); if (!areAdjacent) { - updatedFloors.push(JSON.parse(JSON.stringify(floor))); + newFloors.push(floor); continue; } @@ -129,14 +135,15 @@ export const createFloorStore = () => { ); if (remainingPoints.length > 2) { - floor.points = remainingPoints; - updatedFloors.push(JSON.parse(JSON.stringify(floor))); + const updatedFloor = { ...floor, points: remainingPoints }; + updatedFloors.push(JSON.parse(JSON.stringify(updatedFloor))); + newFloors.push(updatedFloor); } else { removedFloors.push(JSON.parse(JSON.stringify(floor))); } } - state.floors = updatedFloors; + state.floors = newFloors; }); return { removedFloors, updatedFloors }; @@ -253,12 +260,32 @@ export const createFloorStore = () => { }); }, - getFloorByPoints: (points) => { - return get().floors.find(floor => { - const floorPointIds = new Set(floor.points.map(p => p.pointUuid)); - const givenPointIds = new Set(points.map(p => p.pointUuid)); - return floorPointIds.size === givenPointIds.size && [...floorPointIds].every(id => givenPointIds.has(id)); - }); + getFloorsByPoints: ([pointA, pointB]) => { + const Floors: Floor[] = []; + + for (const floor of get().floors) { + const indices = floor.points.map((p, i) => ({ uuid: p.pointUuid, index: i })); + + const idxA = indices.find(i => i.uuid === pointA.pointUuid)?.index ?? -1; + const idxB = indices.find(i => i.uuid === pointB.pointUuid)?.index ?? -1; + + if (idxA === -1 || idxB === -1) { + continue; + } + + const areAdjacent = + Math.abs(idxA - idxB) === 1 || + (idxA === 0 && idxB === floor.points.length - 1) || + (idxB === 0 && idxA === floor.points.length - 1); + + if (!areAdjacent) { + continue; + } + + Floors.push(JSON.parse(JSON.stringify(floor))); + } + + return Floors; }, getFloorPointById: (pointUuid) => { diff --git a/app/src/store/builder/useUndoRedo2DStore.ts b/app/src/store/builder/useUndoRedo2DStore.ts new file mode 100644 index 0000000..9df0185 --- /dev/null +++ b/app/src/store/builder/useUndoRedo2DStore.ts @@ -0,0 +1,78 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; +import { undoRedoConfig } from '../../types/world/worldConstants'; + +type UndoRedo2DStore = { + undoStack: UndoRedo2DTypes[]; + redoStack: UndoRedo2DTypes[]; + + push2D: (entry: UndoRedo2DTypes) => void; + undo2D: () => UndoRedo2DTypes | undefined; + redo2D: () => UndoRedo2DTypes | undefined; + clearUndoRedo2D: () => void; + + peekUndo2D: () => UndoRedo2DTypes | undefined; + peekRedo2D: () => UndoRedo2DTypes | undefined; +}; + +export const createUndoRedo2DStore = () => { + return create()( + immer((set, get) => ({ + undoStack: [], + redoStack: [], + + push2D: (entry) => { + set((state) => { + state.undoStack.push(entry); + + if (state.undoStack.length > undoRedoConfig.undoRedoCount) { + state.undoStack.shift(); + } + + state.redoStack = []; + }); + }, + + undo2D: () => { + let lastAction: UndoRedo2DTypes | undefined; + set((state) => { + lastAction = state.undoStack.pop(); + if (lastAction) { + state.redoStack.unshift(lastAction); + } + }); + return lastAction; + }, + + redo2D: () => { + let redoAction: UndoRedo2DTypes | undefined; + set((state) => { + redoAction = state.redoStack.shift(); + if (redoAction) { + state.undoStack.push(redoAction); + } + }); + return redoAction; + }, + + clearUndoRedo2D: () => { + set((state) => { + state.undoStack = []; + state.redoStack = []; + }); + }, + + peekUndo2D: () => { + const stack = get().undoStack; + return stack.length > 0 ? stack[stack.length - 1] : undefined; + }, + + peekRedo2D: () => { + const stack = get().redoStack; + return stack.length > 0 ? stack[0] : undefined; + }, + })) + ) +} + +export type UndoRedo2DStoreType = ReturnType; \ No newline at end of file diff --git a/app/src/store/builder/useZoneStore.ts b/app/src/store/builder/useZoneStore.ts index 1a93e57..17134c7 100644 --- a/app/src/store/builder/useZoneStore.ts +++ b/app/src/store/builder/useZoneStore.ts @@ -21,7 +21,7 @@ interface ZoneStore { getZoneById: (uuid: string) => Zone | undefined; getZonesByPointId: (uuid: string) => Zone[] | []; - getZoneByPoints: (points: Point[]) => Zone | undefined; + getZonesByPoints: (points: Point[]) => Zone[] | []; getZonePointById: (uuid: string) => Point | undefined; getConnectedPoints: (uuid: string) => Point[]; } @@ -76,10 +76,13 @@ export const createZoneStore = () => { const updatedZones: Zone[] = []; set(state => { + const newZones: Zone[] = []; + for (const zone of state.zones) { const pointIndex = zone.points.findIndex(p => p.pointUuid === pointUuid); + if (pointIndex === -1) { - updatedZones.push(JSON.parse(JSON.stringify(zone))); + newZones.push(zone); continue; } @@ -89,11 +92,13 @@ export const createZoneStore = () => { removedZones.push(JSON.parse(JSON.stringify(zone))); continue; } - zone.points = remainingPoints; - updatedZones.push(JSON.parse(JSON.stringify(zone))); + + const updatedZone = { ...zone, points: remainingPoints }; + updatedZones.push(JSON.parse(JSON.stringify(updatedZone))); + newZones.push(updatedZone); } - state.zones = updatedZones; + state.zones = newZones; }); return { removedZones, updatedZones }; @@ -104,6 +109,7 @@ export const createZoneStore = () => { const updatedZones: Zone[] = []; set(state => { + const newZones: Zone[] = []; for (const zone of state.zones) { const indices = zone.points.map((p, i) => ({ uuid: p.pointUuid, index: i })); @@ -112,7 +118,7 @@ export const createZoneStore = () => { const idxB = indices.find(i => i.uuid === pointB.pointUuid)?.index ?? -1; if (idxA === -1 || idxB === -1) { - updatedZones.push(JSON.parse(JSON.stringify(zone))); + newZones.push(zone); continue; } @@ -122,7 +128,7 @@ export const createZoneStore = () => { (idxB === 0 && idxA === zone.points.length - 1); if (!areAdjacent) { - updatedZones.push(JSON.parse(JSON.stringify(zone))); + newZones.push(zone); continue; } @@ -131,14 +137,15 @@ export const createZoneStore = () => { ); if (remainingPoints.length > 2) { - zone.points = remainingPoints; - updatedZones.push(JSON.parse(JSON.stringify(zone))); + const updatedZone = { ...zone, points: remainingPoints }; + updatedZones.push(JSON.parse(JSON.stringify(updatedZone))); + newZones.push(updatedZone); } else { removedZones.push(JSON.parse(JSON.stringify(zone))); } } - state.zones = updatedZones; + state.zones = newZones; }); return { removedZones, updatedZones }; @@ -180,12 +187,32 @@ export const createZoneStore = () => { }); }, - getZoneByPoints: (points) => { - return get().zones.find(zone => { - const zonePointIds = new Set(zone.points.map(p => p.pointUuid)); - const givenPointIds = new Set(points.map(p => p.pointUuid)); - return zonePointIds.size === givenPointIds.size && [...zonePointIds].every(id => givenPointIds.has(id)); - }); + getZonesByPoints: ([pointA, pointB]) => { + const Zones: Zone[] = []; + + for (const zone of get().zones) { + const indices = zone.points.map((p, i) => ({ uuid: p.pointUuid, index: i })); + + const idxA = indices.find(i => i.uuid === pointA.pointUuid)?.index ?? -1; + const idxB = indices.find(i => i.uuid === pointB.pointUuid)?.index ?? -1; + + if (idxA === -1 || idxB === -1) { + continue; + } + + const areAdjacent = + Math.abs(idxA - idxB) === 1 || + (idxA === 0 && idxB === zone.points.length - 1) || + (idxB === 0 && idxA === zone.points.length - 1); + + if (!areAdjacent) { + continue; + } + + Zones.push(JSON.parse(JSON.stringify(zone))); + } + + return Zones; }, getZonePointById: (pointUuid) => { diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index b48cd12..318050a 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -215,3 +215,41 @@ interface Aisle { } type Aisles = Aisle[]; + + +// Undo/Redo 2D + +type UndoRedo2DDataTypeSchema = + | { type: 'Wall'; lineData: Wall; newData?: Wall; timeStamp: string } + | { type: 'Floor'; lineData: Floor; newData?: Floor; timeStamp: string } + | { type: 'Zone'; lineData: Zone; newData?: Zone; timeStamp: string } + | { type: 'Aisle'; lineData: Aisle; newData?: Aisle; timeStamp: string }; + +type UndoRedo2DLineActionSchema = { + actionType: 'Line-Create' | 'Line-Update' | 'Line-Delete'; + point: UndoRedo2DDataTypeSchema; +} + +type UndoRedo2DLinesActionSchema = { + actionType: 'Lines-Create' | 'Lines-Update' | 'Lines-Delete'; + points: UndoRedo2DDataTypeSchema[]; +} + +type UndoRedo2DAction = UndoRedo2DLineActionSchema | UndoRedo2DLinesActionSchema; + +type UndoRedo2DDraw = { + type: 'Draw'; + actions: UndoRedo2DAction[]; +}; + +type UndoRedo2DUi = { + type: 'UI'; + action: any; // Define UI actions as needed +} + +type UndoRedo2DTypes = UndoRedo2DDraw | UndoRedo2DUi + +type UndoRedo2D = { + undoStack: UndoRedo2DTypes[]; + redoStack: UndoRedo2DTypes[]; +}; \ No newline at end of file diff --git a/app/src/types/world/worldConstants.ts b/app/src/types/world/worldConstants.ts index 0fe8f72..71d9caf 100644 --- a/app/src/types/world/worldConstants.ts +++ b/app/src/types/world/worldConstants.ts @@ -1,375 +1,383 @@ const savedTheme: string | null = localStorage.getItem("theme"); export type Controls = { - azimuthRotateSpeed: number; - polarRotateSpeed: number; - truckSpeed: number; - minDistance: number; - maxDistance: number; - maxPolarAngle: number; - leftMouse: number; - forwardSpeed: number; - backwardSpeed: number; - leftSpeed: number; - rightSpeed: number; + azimuthRotateSpeed: number; + polarRotateSpeed: number; + truckSpeed: number; + minDistance: number; + maxDistance: number; + maxPolarAngle: number; + leftMouse: number; + forwardSpeed: number; + backwardSpeed: number; + leftSpeed: number; + rightSpeed: number; }; export type ThirdPersonControls = { - azimuthRotateSpeed: number; - polarRotateSpeed: number; - truckSpeed: number; - maxDistance: number; - maxPolarAngle: number; - minZoom: number; - maxZoom: number; - targetOffset: number; - cameraHeight: number; - leftMouse: number; - rightMouse: number; - wheelMouse: number; - middleMouse: number; + azimuthRotateSpeed: number; + polarRotateSpeed: number; + truckSpeed: number; + maxDistance: number; + maxPolarAngle: number; + minZoom: number; + maxZoom: number; + targetOffset: number; + cameraHeight: number; + leftMouse: number; + rightMouse: number; + wheelMouse: number; + middleMouse: number; }; export type ControlsTransition = { - leftMouse: number; - rightMouse: number; - wheelMouse: number; - middleMouse: number; + leftMouse: number; + rightMouse: number; + wheelMouse: number; + middleMouse: number; }; export type TwoDimension = { - defaultPosition: [x: number, y: number, z: number]; - defaultTarget: [x: number, y: number, z: number]; - defaultAzimuth: number; - minDistance: number; - leftMouse: number; - rightMouse: number; + defaultPosition: [x: number, y: number, z: number]; + defaultTarget: [x: number, y: number, z: number]; + defaultAzimuth: number; + minDistance: number; + leftMouse: number; + rightMouse: number; }; export type ThreeDimension = { - defaultPosition: [x: number, y: number, z: number]; - defaultTarget: [x: number, y: number, z: number]; - defaultRotation: [x: number, y: number, z: number]; - defaultAzimuth: number; - boundaryBottom: [x: number, y: number, z: number]; - boundaryTop: [x: number, y: number, z: number]; - minDistance: number; - leftMouse: number; - rightMouse: number; + defaultPosition: [x: number, y: number, z: number]; + defaultTarget: [x: number, y: number, z: number]; + defaultRotation: [x: number, y: number, z: number]; + defaultAzimuth: number; + boundaryBottom: [x: number, y: number, z: number]; + boundaryTop: [x: number, y: number, z: number]; + minDistance: number; + leftMouse: number; + rightMouse: number; }; export type GridConfig = { - size: number; - divisions: number; - primaryColor: string; - secondaryColor: string; - position2D: [x: number, y: number, z: number]; - position3D: [x: number, y: number, z: number]; + size: number; + divisions: number; + primaryColor: string; + secondaryColor: string; + position2D: [x: number, y: number, z: number]; + position3D: [x: number, y: number, z: number]; }; export type PlaneConfig = { - position2D: [x: number, y: number, z: number]; - position3D: [x: number, y: number, z: number]; - rotation: number; - width: number; - height: number; - color: string; + position2D: [x: number, y: number, z: number]; + position3D: [x: number, y: number, z: number]; + rotation: number; + width: number; + height: number; + color: string; }; export type ShadowConfig = { - shadowOffset: number; - shadowmapSizewidth: number; - shadowmapSizeheight: number; - shadowcamerafar: number; - shadowcameranear: number; - shadowcameratop: number; - shadowcamerabottom: number; - shadowcameraleft: number; - shadowcameraright: number; - shadowbias: number; - shadownormalBias: number; - shadowMaterialPosition: [x: number, y: number, z: number]; - shadowMaterialRotation: [x: number, y: number, z: number]; - shadowMaterialOpacity: number; + shadowOffset: number; + shadowmapSizewidth: number; + shadowmapSizeheight: number; + shadowcamerafar: number; + shadowcameranear: number; + shadowcameratop: number; + shadowcamerabottom: number; + shadowcameraleft: number; + shadowcameraright: number; + shadowbias: number; + shadownormalBias: number; + shadowMaterialPosition: [x: number, y: number, z: number]; + shadowMaterialRotation: [x: number, y: number, z: number]; + shadowMaterialOpacity: number; }; export type SkyConfig = { - defaultTurbidity: number; - maxTurbidity: number; - minTurbidity: number; - defaultRayleigh: number; - mieCoefficient: number; - mieDirectionalG: number; - skyDistance: number; + defaultTurbidity: number; + maxTurbidity: number; + minTurbidity: number; + defaultRayleigh: number; + mieCoefficient: number; + mieDirectionalG: number; + skyDistance: number; }; export type AssetConfig = { - defaultScaleBeforeGsap: [number, number, number]; - defaultScaleAfterGsap: [number, number, number]; + defaultScaleBeforeGsap: [number, number, number]; + defaultScaleAfterGsap: [number, number, number]; }; export type PointConfig = { - defaultInnerColor: string; - defaultOuterColor: string; - deleteColor: string; - boxScale: [number, number, number]; - wallOuterColor: string; - floorOuterColor: string; - aisleOuterColor: string; - zoneOuterColor: string; - snappingThreshold: number; - helperColor: string; + defaultInnerColor: string; + defaultOuterColor: string; + deleteColor: string; + boxScale: [number, number, number]; + wallOuterColor: string; + floorOuterColor: string; + aisleOuterColor: string; + zoneOuterColor: string; + snappingThreshold: number; + helperColor: string; }; export type LineConfig = { - tubularSegments: number; - radius: number; - radialSegments: number; - wallName: string; - floorName: string; - aisleName: string; - zoneName: string; - referenceName: string; - lineIntersectionPoints: number; - defaultColor: string; - wallColor: string; - floorColor: string; - aisleColor: string; - zoneColor: string; - deleteColor: string; - helperColor: string; + tubularSegments: number; + radius: number; + radialSegments: number; + wallName: string; + floorName: string; + aisleName: string; + zoneName: string; + referenceName: string; + lineIntersectionPoints: number; + defaultColor: string; + wallColor: string; + floorColor: string; + aisleColor: string; + zoneColor: string; + deleteColor: string; + helperColor: string; }; export type WallConfig = { - defaultColor: string; - height: number; - width: number; + defaultColor: string; + height: number; + width: number; }; export type FloorConfig = { - defaultColor: string; - height: number; - textureScale: number; + defaultColor: string; + height: number; + textureScale: number; }; export type RoofConfig = { - defaultColor: string; - height: number; + defaultColor: string; + height: number; }; export type AisleConfig = { - width: number; - height: number; - defaultColor: string; + width: number; + height: number; + defaultColor: string; }; export type ZoneConfig = { - defaultColor: string; - height: number; - color: string; + defaultColor: string; + height: number; + color: string; }; export type ColumnConfig = { - defaultColor: string; + defaultColor: string; }; export type OutlineConfig = { - assetSelectColor: number; - assetDeleteColor: number; + assetSelectColor: number; + assetDeleteColor: number; }; export type DistanceConfig = { - minDistance: number; - maxDistance: number; + minDistance: number; + maxDistance: number; +}; + +export type undoRedoCount = { + undoRedoCount: number; }; export const firstPersonControls: Controls = { - azimuthRotateSpeed: 0.3, // Speed of rotation around the azimuth axis - polarRotateSpeed: 0.3, // Speed of rotation around the polar axis - truckSpeed: 10, // Speed of truck movement - minDistance: 0, // Minimum distance from the target - maxDistance: 0, // Maximum distance from the target - maxPolarAngle: Math.PI, // Maximum polar angle - leftMouse: 1, // Mouse button for rotation (ROTATE) - forwardSpeed: 0.1, // Speed of forward movement - backwardSpeed: -0.1, // Speed of backward movement - leftSpeed: -0.1, // Speed of left movement - rightSpeed: 0.1, // Speed of right movement + azimuthRotateSpeed: 0.3, // Speed of rotation around the azimuth axis + polarRotateSpeed: 0.3, // Speed of rotation around the polar axis + truckSpeed: 10, // Speed of truck movement + minDistance: 0, // Minimum distance from the target + maxDistance: 0, // Maximum distance from the target + maxPolarAngle: Math.PI, // Maximum polar angle + leftMouse: 1, // Mouse button for rotation (ROTATE) + forwardSpeed: 0.1, // Speed of forward movement + backwardSpeed: -0.1, // Speed of backward movement + leftSpeed: -0.1, // Speed of left movement + rightSpeed: 0.1, // Speed of right movement }; export const thirdPersonControls: ThirdPersonControls = { - azimuthRotateSpeed: 1, // Speed of rotation around the azimuth axis - polarRotateSpeed: 1, // Speed of rotation around the polar axis - truckSpeed: 2, // Speed of truck movement - maxDistance: 100, // Maximum distance from the target - maxPolarAngle: Math.PI / 2 - 0.05, // Maximum polar angle - minZoom: 6, // Minimum zoom level - maxZoom: 100, // Maximum zoom level - targetOffset: 20, // Offset of the target from the camera - cameraHeight: 30, // Height of the camera - leftMouse: 2, // Mouse button for panning - rightMouse: 1, // Mouse button for rotation - wheelMouse: 8, // Mouse button for zooming - middleMouse: 8, // Mouse button for zooming + azimuthRotateSpeed: 1, // Speed of rotation around the azimuth axis + polarRotateSpeed: 1, // Speed of rotation around the polar axis + truckSpeed: 2, // Speed of truck movement + maxDistance: 100, // Maximum distance from the target + maxPolarAngle: Math.PI / 2 - 0.05, // Maximum polar angle + minZoom: 6, // Minimum zoom level + maxZoom: 100, // Maximum zoom level + targetOffset: 20, // Offset of the target from the camera + cameraHeight: 30, // Height of the camera + leftMouse: 2, // Mouse button for panning + rightMouse: 1, // Mouse button for rotation + wheelMouse: 8, // Mouse button for zooming + middleMouse: 8, // Mouse button for zooming }; export const controlsTransition: ControlsTransition = { - leftMouse: 0, // Mouse button for no action - rightMouse: 0, // Mouse button for no action - wheelMouse: 0, // Mouse button for no action - middleMouse: 0, // Mouse button for no action + leftMouse: 0, // Mouse button for no action + rightMouse: 0, // Mouse button for no action + wheelMouse: 0, // Mouse button for no action + middleMouse: 0, // Mouse button for no action }; export const twoDimension: TwoDimension = { - defaultPosition: [0, 100, 0], // Default position of the camera - defaultTarget: [0, 0, 0], // Default target of the camera - defaultAzimuth: 0, // Default azimuth of the camera - minDistance: 25, // Minimum distance from the target - leftMouse: 2, // Mouse button for panning - rightMouse: 0, // Mouse button for no action + defaultPosition: [0, 100, 0], // Default position of the camera + defaultTarget: [0, 0, 0], // Default target of the camera + defaultAzimuth: 0, // Default azimuth of the camera + minDistance: 25, // Minimum distance from the target + leftMouse: 2, // Mouse button for panning + rightMouse: 0, // Mouse button for no action }; export const camPositionUpdateInterval: number = 200; // Interval for updating the camera position export const gridConfig: GridConfig = { - size: 150, // Size of the grid - divisions: 75, // Number of divisions in the grid - primaryColor: savedTheme === "dark" ? "#131313" : "#d5d5d5", // Primary color of the grid - secondaryColor: savedTheme === "dark" ? "#434343" : "#e3e3e3", // Secondary color of the grid + size: 150, // Size of the grid + divisions: 75, // Number of divisions in the grid + primaryColor: savedTheme === "dark" ? "#131313" : "#d5d5d5", // Primary color of the grid + secondaryColor: savedTheme === "dark" ? "#434343" : "#e3e3e3", // Secondary color of the grid - position2D: [0, 0.1, 0], // Position of the grid in 2D view - position3D: [0, -0.5, 0], // Position of the grid in 3D view + position2D: [0, 0.1, 0], // Position of the grid in 2D view + position3D: [0, -0.5, 0], // Position of the grid in 3D view }; export const threeDimension: ThreeDimension = { - defaultPosition: [0, 40, 30], // Default position of the camera - defaultTarget: [0, 0, 0], // Default target of the camera - defaultRotation: [0, 0, 0], // Default rotation of the camera - defaultAzimuth: 0, // Default azimuth of the camera - boundaryBottom: [-gridConfig.size / 2, 0, -gridConfig.size / 2], // Bottom boundary of the camera movement - boundaryTop: [gridConfig.size / 2, 100, gridConfig.size / 2], // Top boundary of the camera movement - minDistance: 1, // Minimum distance from the target - leftMouse: 2, // Mouse button for panning - rightMouse: 1, // Mouse button for rotation + defaultPosition: [0, 40, 30], // Default position of the camera + defaultTarget: [0, 0, 0], // Default target of the camera + defaultRotation: [0, 0, 0], // Default rotation of the camera + defaultAzimuth: 0, // Default azimuth of the camera + boundaryBottom: [-gridConfig.size / 2, 0, -gridConfig.size / 2], // Bottom boundary of the camera movement + boundaryTop: [gridConfig.size / 2, 100, gridConfig.size / 2], // Top boundary of the camera movement + minDistance: 1, // Minimum distance from the target + leftMouse: 2, // Mouse button for panning + rightMouse: 1, // Mouse button for rotation }; export const planeConfig: PlaneConfig = { - position2D: [0, -0.5, 0], // Position of the plane - position3D: [0, -0.65, 0], // Position of the plane - rotation: -Math.PI / 2, // Rotation of the plane + position2D: [0, -0.5, 0], // Position of the plane + position3D: [0, -0.65, 0], // Position of the plane + rotation: -Math.PI / 2, // Rotation of the plane - width: 150, // Width of the plane - height: 150, // Height of the plane - color: savedTheme === "dark" ? "#323232" : "#f3f3f3", // Color of the plane + width: 150, // Width of the plane + height: 150, // Height of the plane + color: savedTheme === "dark" ? "#323232" : "#f3f3f3", // Color of the plane }; export const shadowConfig: ShadowConfig = { - shadowOffset: 50, // Offset of the shadow - // shadowmapSizewidth: 1024, // Width of the shadow map - // shadowmapSizeheight: 1024, // Height of the shadow map - shadowmapSizewidth: 2048, // Width of the shadow map - shadowmapSizeheight: 2048, // Height of the shadow map - // shadowmapSizewidth: 8192, // Width of the shadow map - // shadowmapSizeheight: 8192, // Height of the shadow map - shadowcamerafar: 70, // Far plane of the shadow camera - shadowcameranear: 0.1, // Near plane of the shadow camera - shadowcameratop: 30, // Top plane of the shadow camera - shadowcamerabottom: -30, // Bottom plane of the shadow camera - shadowcameraleft: -30, // Left plane of the shadow camera - shadowcameraright: 30, // Right plane of the shadow camera - shadowbias: -0.001, // Bias of the shadow - shadownormalBias: 0.02, // Normal bias of the shadow - shadowMaterialPosition: [0, 0.01, 0], // Position of the shadow material - shadowMaterialRotation: [-Math.PI / 2, 0, 0], // Rotation of the shadow material - shadowMaterialOpacity: 0.1, // Opacity of the shadow material + shadowOffset: 50, // Offset of the shadow + // shadowmapSizewidth: 1024, // Width of the shadow map + // shadowmapSizeheight: 1024, // Height of the shadow map + shadowmapSizewidth: 2048, // Width of the shadow map + shadowmapSizeheight: 2048, // Height of the shadow map + // shadowmapSizewidth: 8192, // Width of the shadow map + // shadowmapSizeheight: 8192, // Height of the shadow map + shadowcamerafar: 70, // Far plane of the shadow camera + shadowcameranear: 0.1, // Near plane of the shadow camera + shadowcameratop: 30, // Top plane of the shadow camera + shadowcamerabottom: -30, // Bottom plane of the shadow camera + shadowcameraleft: -30, // Left plane of the shadow camera + shadowcameraright: 30, // Right plane of the shadow camera + shadowbias: -0.001, // Bias of the shadow + shadownormalBias: 0.02, // Normal bias of the shadow + shadowMaterialPosition: [0, 0.01, 0], // Position of the shadow material + shadowMaterialRotation: [-Math.PI / 2, 0, 0], // Rotation of the shadow material + shadowMaterialOpacity: 0.1, // Opacity of the shadow material }; export const skyConfig: SkyConfig = { - defaultTurbidity: 10.0, // Default turbidity of the sky - maxTurbidity: 20.0, // Maximum turbidity of the sky - minTurbidity: 0.0, // Minimum turbidity of the sky - defaultRayleigh: 1.9, // Default Rayleigh scattering coefficient - mieCoefficient: 0.1, // Mie scattering coefficient - mieDirectionalG: 1.0, // Mie directional G - skyDistance: 2000, // Distance of the sky + defaultTurbidity: 10.0, // Default turbidity of the sky + maxTurbidity: 20.0, // Maximum turbidity of the sky + minTurbidity: 0.0, // Minimum turbidity of the sky + defaultRayleigh: 1.9, // Default Rayleigh scattering coefficient + mieCoefficient: 0.1, // Mie scattering coefficient + mieDirectionalG: 1.0, // Mie directional G + skyDistance: 2000, // Distance of the sky }; export const assetConfig: AssetConfig = { - defaultScaleBeforeGsap: [0.1, 0.1, 0.1], // Default scale of the assets - defaultScaleAfterGsap: [1, 1, 1], // Default scale of the assets + defaultScaleBeforeGsap: [0.1, 0.1, 0.1], // Default scale of the assets + defaultScaleAfterGsap: [1, 1, 1], // Default scale of the assets }; export const pointConfig: PointConfig = { - defaultInnerColor: "#ffffff", // Default inner color of the points - defaultOuterColor: "#ffffff", // Default outer color of the points - deleteColor: "#ff0000", // Color of the points when deleting - boxScale: [0.5, 0.5, 0.5], // Scale of the points - wallOuterColor: "#C7C7C7", // Outer color of the wall points - floorOuterColor: "#808080", // Outer color of the floor points - aisleOuterColor: "#FBBC05", // Outer color of the aisle points - zoneOuterColor: "#007BFF", // Outer color of the zone points - snappingThreshold: 1, // Threshold for snapping - helperColor: "#C164FF", // Color of the helper lines + defaultInnerColor: "#ffffff", // Default inner color of the points + defaultOuterColor: "#ffffff", // Default outer color of the points + deleteColor: "#ff0000", // Color of the points when deleting + boxScale: [0.5, 0.5, 0.5], // Scale of the points + wallOuterColor: "#C7C7C7", // Outer color of the wall points + floorOuterColor: "#808080", // Outer color of the floor points + aisleOuterColor: "#FBBC05", // Outer color of the aisle points + zoneOuterColor: "#007BFF", // Outer color of the zone points + snappingThreshold: 1, // Threshold for snapping + helperColor: "#C164FF", // Color of the helper lines }; export const lineConfig: LineConfig = { - tubularSegments: 64, // Number of tubular segments - radius: 0.15, // Radius of the lines - radialSegments: 8, // Number of radial segments - wallName: "WallLine", // Name of the wall lines - floorName: "FloorLine", // Name of the floor lines - aisleName: "AisleLine", // Name of the aisle lines - zoneName: "ZoneLine", // Name of the zone lines - referenceName: "ReferenceLine", // Name of the reference lines - lineIntersectionPoints: 300, // Number of intersection points - defaultColor: "#000000", // Default color of the lines - wallColor: "#C7C7C7", // Color of the wall lines - floorColor: "#808080", // Color of the floor lines - aisleColor: "#FBBC05", // Color of the aisle lines - zoneColor: "#007BFF", // Color of the zone lines - deleteColor: "#ff0000", // Color of the line when deleting - helperColor: "#C164FF", // Color of the helper lines + tubularSegments: 64, // Number of tubular segments + radius: 0.15, // Radius of the lines + radialSegments: 8, // Number of radial segments + wallName: "WallLine", // Name of the wall lines + floorName: "FloorLine", // Name of the floor lines + aisleName: "AisleLine", // Name of the aisle lines + zoneName: "ZoneLine", // Name of the zone lines + referenceName: "ReferenceLine", // Name of the reference lines + lineIntersectionPoints: 300, // Number of intersection points + defaultColor: "#000000", // Default color of the lines + wallColor: "#C7C7C7", // Color of the wall lines + floorColor: "#808080", // Color of the floor lines + aisleColor: "#FBBC05", // Color of the aisle lines + zoneColor: "#007BFF", // Color of the zone lines + deleteColor: "#ff0000", // Color of the line when deleting + helperColor: "#C164FF", // Color of the helper lines }; export const wallConfig: WallConfig = { - defaultColor: "#f2f2f2", // Default color of the walls - height: 7.5, // Height of the walls - width: 0.05, // Width of the walls + defaultColor: "#f2f2f2", // Default color of the walls + height: 7.5, // Height of the walls + width: 0.05, // Width of the walls }; export const floorConfig: FloorConfig = { - defaultColor: "grey", // Default color of the floors - height: 0.1, // Height of the floors - textureScale: 1, // Scale of the floor texture + defaultColor: "grey", // Default color of the floors + height: 0.1, // Height of the floors + textureScale: 1, // Scale of the floor texture }; export const roofConfig: RoofConfig = { - defaultColor: "grey", // Default color of the roofs - height: 0.1, // Height of the roofs + defaultColor: "grey", // Default color of the roofs + height: 0.1, // Height of the roofs }; export const aisleConfig: AisleConfig = { - width: 0.1, // Width of the aisles - height: 0.01, // Height of the aisles - defaultColor: '#E2AC09', // Default color of the aisles + width: 0.1, // Width of the aisles + height: 0.01, // Height of the aisles + defaultColor: '#E2AC09', // Default color of the aisles }; export const zoneConfig: ZoneConfig = { - defaultColor: "black", // Default color of the zones - height: 3, - color: "#8656DF", // Color of the zones + defaultColor: "black", // Default color of the zones + height: 3, + color: "#8656DF", // Color of the zones }; export const columnConfig: ColumnConfig = { - defaultColor: "White", // Default color of the columns + defaultColor: "White", // Default color of the columns }; export const outlineConfig: OutlineConfig = { - assetSelectColor: 0x0054fe, // Color of the selected assets - assetDeleteColor: 0xff0000, // Color of the deleted assets + assetSelectColor: 0x0054fe, // Color of the selected assets + assetDeleteColor: 0xff0000, // Color of the deleted assets }; export const distanceConfig: DistanceConfig = { - minDistance: 20, - maxDistance: 75, + minDistance: 20, + maxDistance: 75, }; + +export const undoRedoConfig: undoRedoCount = { + undoRedoCount: 50, +} \ No newline at end of file