diff --git a/app/package-lock.json b/app/package-lock.json index 8be748e..2c1f714 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -2020,7 +2020,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -2032,7 +2032,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -4135,6 +4135,26 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@testing-library/jest-dom": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", @@ -4246,25 +4266,25 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "devOptional": true }, "node_modules/@turf/along": { "version": "7.2.0", @@ -9007,7 +9027,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "devOptional": true }, "node_modules/cross-env": { "version": "7.0.3", @@ -9884,7 +9904,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.3.1" } @@ -15247,7 +15267,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -20715,7 +20735,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "devOptional": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20758,7 +20778,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, + "devOptional": true, "dependencies": { "acorn": "^8.11.0" }, @@ -20770,7 +20790,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "devOptional": true }, "node_modules/tsconfig-paths": { "version": "3.15.0", @@ -21266,7 +21286,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "devOptional": true }, "node_modules/v8-to-istanbul": { "version": "8.1.1", @@ -22325,7 +22345,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } diff --git a/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx b/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx index 4ffea3b..568783c 100644 --- a/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx @@ -98,7 +98,7 @@ const GlobalProperties: React.FC = () => { // setPlaneValue({ height: value * 100, width: value * 100 }); } function updatedGrid(value: number) { - console.log(" (value * 100) / 4 : ", (value * 100) / 4); + // console.log(" (value * 100) / 4 : ", (value * 100) / 4); setGridValue({ size: value * 100, divisions: (value * 100) / 4 }); setPlaneValue({ height: value * 100, width: value * 100 }); } diff --git a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx index 6492252..c36b656 100644 --- a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx @@ -32,7 +32,7 @@ const ZoneProperties: React.FC = () => { if (response.message === "updated successfully") { setEdit(false); } else { - console.log(response); + // console.log(response); } } catch (error) { @@ -63,7 +63,7 @@ const ZoneProperties: React.FC = () => { ) ); }else{ - console.log(response?.message); + // console.log(response?.message); } } function handleVectorChange(key: "zoneViewPortTarget" | "zoneViewPortPosition", newValue: [number, number, number]) { diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index d70bfc3..d8d721f 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -85,7 +85,7 @@ const RealTimeVisulization: React.FC = () => { const organization = email?.split("@")[1]?.split(".")[0]; try { const response = await getZone2dData(organization); - console.log('response: ', response); + // console.log('response: ', response); if (!Array.isArray(response)) { return; diff --git a/app/src/components/ui/realTimeVis/floating/FleetEfficiencyComponent.tsx b/app/src/components/ui/realTimeVis/floating/FleetEfficiencyComponent.tsx index 2cf12e0..6751028 100644 --- a/app/src/components/ui/realTimeVis/floating/FleetEfficiencyComponent.tsx +++ b/app/src/components/ui/realTimeVis/floating/FleetEfficiencyComponent.tsx @@ -41,7 +41,7 @@ const FleetEfficiencyComponent = ({object}: any) => { socket.on("lastOutput", (response) => { const responseData = response.input1; - console.log(responseData); + // console.log(responseData); if (typeof responseData === "number") { console.log("It's a number!"); diff --git a/app/src/modules/builder/agv/agv.tsx b/app/src/modules/builder/agv/agv.tsx index 5b8a2e4..60e76d8 100644 --- a/app/src/modules/builder/agv/agv.tsx +++ b/app/src/modules/builder/agv/agv.tsx @@ -1,111 +1,103 @@ import { useEffect, useState } from "react"; import { Line } from "@react-three/drei"; import { - useNavMesh, - usePlayAgv, - useSimulationStates, + useNavMesh, + usePlayAgv, + useSimulationStates, } from "../../../store/store"; import PathNavigator from "./pathNavigator"; -import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { usePlayButtonStore, useResetButtonStore } from "../../../store/usePlayButtonStore"; type PathPoints = { - modelUuid: string; - modelSpeed: number; - bufferTime: number; - points: { x: number; y: number; z: number }[]; - hitCount: number; + modelUuid: string; + modelSpeed: number; + bufferTime: number; + points: { x: number; y: number; z: number }[]; + hitCount: number; }; interface ProcessContainerProps { - processes: any[]; - agvRef: any; - MaterialRef: any; + processes: any[]; + agvRef: any; + MaterialRef: any; } const Agv: React.FC = ({ - processes, - agvRef, - MaterialRef, + processes, + agvRef, + MaterialRef, }) => { - const [pathPoints, setPathPoints] = useState([]); - const { simulationStates } = useSimulationStates(); - const { navMesh } = useNavMesh(); - const { isPlaying } = usePlayButtonStore(); - const { PlayAgv, setPlayAgv } = usePlayAgv(); + const [pathPoints, setPathPoints] = useState([]); + const { simulationStates } = useSimulationStates(); + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); + const { isReset, setReset } = useResetButtonStore(); + useEffect(() => { + if (!isPlaying || isReset) { + agvRef.current = []; + } + }, [isPlaying, isReset]) + useEffect(() => { + if (simulationStates.length > 0) { + const agvModels = simulationStates.filter( + (val) => val.modelName === "agv" && val.type === "Vehicle" + ); - useEffect(() => { - if (simulationStates.length > 0) { - const agvModels = simulationStates.filter( - (val) => val.modelName === "agv" && val.type === "Vehicle" - ); + const newPathPoints = agvModels + .filter( + (model: any) => + model.points && + model.points.actions && + typeof model.points.actions.start === "object" && + typeof model.points.actions.end === "object" && + "x" in model.points.actions.start && + "y" in model.points.actions.start && + "x" in model.points.actions.end && + "y" in model.points.actions.end + ) + .map((model: any) => ({ + modelUuid: model.modeluuid, + modelSpeed: model.points.speed, + bufferTime: model.points.actions.buffer, + hitCount: model.points.actions.hitCount, + points: [ + { x: model.position[0], y: model.position[1], z: model.position[2], }, + { x: model.points.actions.start.x, y: 0, z: model.points.actions.start.y, }, + { x: model.points.actions.end.x, y: 0, z: model.points.actions.end.y, }, + ], + })); - const newPathPoints = agvModels - .filter( - (model: any) => - model.points && - model.points.actions && - typeof model.points.actions.start === "object" && - typeof model.points.actions.end === "object" && - "x" in model.points.actions.start && - "y" in model.points.actions.start && - "x" in model.points.actions.end && - "y" in model.points.actions.end - ) - .map((model: any) => ({ - modelUuid: model.modeluuid, - modelSpeed: model.points.speed, - bufferTime: model.points.actions.buffer, - hitCount: model.points.actions.hitCount, - points: [ - { - x: model.position[0], - y: model.position[1], - z: model.position[2], - }, - { - x: model.points.actions.start.x, - y: 0, - z: model.points.actions.start.y, - }, - { - x: model.points.actions.end.x, - y: 0, - z: model.points.actions.end.y, - }, - ], - })); + setPathPoints(newPathPoints); + } + }, [simulationStates]); - setPathPoints(newPathPoints); - } - }, [simulationStates]); + return ( + <> + {pathPoints.map((pair, i) => ( + + - return ( - <> - {pathPoints.map((pair, i) => ( - - - - {pair.points.slice(1).map((point, idx) => ( - - - - - ))} - - ))} - - ); + {pair.points.slice(1).map((point, idx) => ( + + + + + ))} + + ))} + + ); }; export default Agv; diff --git a/app/src/modules/builder/agv/pathNavigator.tsx b/app/src/modules/builder/agv/pathNavigator.tsx index d68da57..018cf4f 100644 --- a/app/src/modules/builder/agv/pathNavigator.tsx +++ b/app/src/modules/builder/agv/pathNavigator.tsx @@ -5,7 +5,6 @@ import { NavMeshQuery } from "@recast-navigation/core"; import { Line } from "@react-three/drei"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import { usePlayAgv } from "../../../store/store"; -import crate from "../../../assets/gltf-glb/crate_box.glb"; interface PathNavigatorProps { navMesh: any; @@ -38,30 +37,12 @@ export default function PathNavigator({ MaterialRef, }: PathNavigatorProps) { const [currentPhase, setCurrentPhase] = useState("initial"); - // - const [path, setPath] = useState<[number, number, number][]>([]); - const PickUpDrop = useRef([]); - - // const [currentPhase, setCurrentPhase] = useState<"initial" | "loop">( - // "initial" - // ); - const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>( - [] - ); - - const [pickupDropPath, setPickupDropPath] = useState< - [number, number, number][] - >([]); - const [dropPickupPath, setDropPickupPath] = useState< - [number, number, number][] - >([]); - const [initialPosition, setInitialPosition] = useState( - null - ); - const [initialRotation, setInitialRotation] = useState( - null - ); + const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>([]); + const [pickupDropPath, setPickupDropPath] = useState<[number, number, number][]>([]); + const [dropPickupPath, setDropPickupPath] = useState<[number, number, number][]>([]); + const [initialPosition, setInitialPosition] = useState(null); + const [initialRotation, setInitialRotation] = useState(null); const [boxVisible, setBoxVisible] = useState(false); const distancesRef = useRef([]); @@ -70,11 +51,20 @@ export default function PathNavigator({ const isWaiting = useRef(false); const timeoutRef = useRef(null); const hasStarted = useRef(false); + const hasReachedPickup = useRef(false); const { scene } = useThree(); const { isPlaying } = usePlayButtonStore(); const { PlayAgv, setPlayAgv } = usePlayAgv(); + const boxRef = useRef(null); + + const baseMaterials = useMemo(() => ({ + Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), + Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), + Default: new THREE.MeshStandardMaterial({ color: 0xcccccc }) + }), []); + useEffect(() => { const object = scene.getObjectByProperty("uuid", id); if (object) { @@ -105,12 +95,16 @@ export default function PathNavigator({ setPath([]); setCurrentPhase("initial"); + setToPickupPath([]); setPickupDropPath([]); setDropPickupPath([]); + setBoxVisible(false); distancesRef.current = []; totalDistanceRef.current = 0; progressRef.current = 0; isWaiting.current = false; + hasStarted.current = false; + hasReachedPickup.current = false; if (initialPosition && initialRotation) { const object = scene.getObjectByProperty("uuid", id); @@ -130,27 +124,16 @@ export default function PathNavigator({ const [pickup, drop] = pathPoints.slice(-2); - PickUpDrop.current = pathPoints.slice(-2); - const object = scene.getObjectByProperty("uuid", id); - if (!object) return; - const currentPosition = { - x: object.position.x, - y: object.position.y, - z: object.position.z, - }; + const currentPosition = object.position; const toPickupPath = computePath(currentPosition, pickup); const pickupToDropPath = computePath(pickup, drop); const dropToPickupPath = computePath(drop, pickup); - if ( - toPickupPath.length && - pickupToDropPath.length && - dropToPickupPath.length - ) { + if (toPickupPath.length && pickupToDropPath.length && dropToPickupPath.length) { setPickupDropPath(pickupToDropPath); setDropPickupPath(dropToPickupPath); setToPickupPath(toPickupPath); @@ -177,25 +160,9 @@ export default function PathNavigator({ isWaiting.current = false; }, [path]); - // Add these refs outside the useFrame if not already present: - const startPointReached = useRef(false); - - const baseMaterials = useMemo( - () => ({ - Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), - Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), - // Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), - Default: new THREE.MeshStandardMaterial(), - }), - [] - ); - - // Example usage: - const targetModelUUID = id; // Replace with your target ID - const matchedProcess = findProcessByTargetModelUUID( - processes, - targetModelUUID - ); + function logAgvStatus(id: string, status: string) { + // console.log(`AGV ${id}: ${status}`); + } function findProcessByTargetModelUUID(processes: any, targetModelUUID: any) { for (const process of processes) { @@ -206,75 +173,55 @@ export default function PathNavigator({ (target: any) => target.modelUUID === targetModelUUID ) ) { - // - return process.id; // Return the process if a match is found + return process.id; } } } } - return null; // Return null if no match is found + return null; } - useFrame((_, delta) => {}); - const boxRef = useRef(null); - useEffect(() => { - if (!scene || !boxRef || !processes) return; + if (!scene || !boxRef || !processes || !MaterialRef.current) return; - // 1. Find the existing object by UUID const existingObject = scene.getObjectByProperty("uuid", id); if (!existingObject) return; - // 2. Find the process that targets this object's modelUUID + if (boxRef.current?.parent) { + boxRef.current.parent.remove(boxRef.current); + boxRef.current = null; + } + if (boxVisible) { const matchedProcess = findProcessByTargetModelUUID(processes, id); - - // 3. Determine the material (from materialref) if a process is matched - let materialType = "Default"; + let materialType: "Box" | "Crate" | "Default" = "Default"; if (matchedProcess) { const materialEntry = MaterialRef.current.find((item: any) => item.objects.some((obj: any) => obj.processId === matchedProcess) ); - console.log("materialEntry: ", materialEntry); if (materialEntry) { - materialType = materialEntry.material; // "Box" or "Crate" + materialType = materialEntry.material; } } - // 4. Create the box mesh with the assigned material const boxGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5); - const boxMaterial = baseMaterials[materialType as MaterialType]; - const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial); + const boxMesh = new THREE.Mesh(boxGeometry, baseMaterials[materialType]); boxMesh.position.y = 1; boxMesh.name = `box-${id}`; - - // 5. Add to scene and cleanup existingObject.add(boxMesh); boxRef.current = boxMesh; } - if (!startPointReached.current && boxVisible) { - setBoxVisible(false); - } return () => { if (boxRef.current?.parent) { boxRef.current.parent.remove(boxRef.current); } - boxRef.current = null; }; - }, [ - processes, - MaterialRef, - boxVisible, - findProcessByTargetModelUUID, - startPointReached, - ]); + }, [processes, MaterialRef, boxVisible, scene, id, baseMaterials]); useFrame((_, delta) => { - const currentAgv = (agvRef.current || []).find( - (agv: AGVData) => agv.vehicleId === id - ); + const currentAgv = (agvRef.current || []).find((agv: AGVData) => agv.vehicleId === id); if (!scene || !id || !isPlaying) return; @@ -283,7 +230,6 @@ export default function PathNavigator({ if (isPlaying && !hasStarted.current) { hasStarted.current = false; - startPointReached.current = false; progressRef.current = 0; isWaiting.current = false; if (timeoutRef.current) { @@ -295,10 +241,9 @@ export default function PathNavigator({ const isAgvReady = () => { if (!agvRef.current || agvRef.current.length === 0) return false; if (!currentAgv) return false; - return hitCount === currentAgv.expectedHitCount; + return currentAgv.isActive && hitCount >= currentAgv.maxHitCount; }; - // Step 1: Snap to start point on first play if (isPlaying && !hasStarted.current && toPickupPath.length > 0) { setBoxVisible(false); const startPoint = new THREE.Vector3(...toPickupPath[0]); @@ -311,146 +256,132 @@ export default function PathNavigator({ } hasStarted.current = true; - startPointReached.current = true; progressRef.current = 0; - + hasReachedPickup.current = false; setToPickupPath(toPickupPath.slice(-1)); - + logAgvStatus(id, "Started from station, heading to pickup"); return; } - // Step 2: Wait at start point for AGV readiness - if (isPlaying && startPointReached.current && path.length === 0) { - if (!isAgvReady()) { - return; // Prevent transitioning to the next phase if AGV is not ready + if (isPlaying && currentPhase === "initial" && !hasReachedPickup.current) { + const reached = moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef); + + if (reached) { + hasReachedPickup.current = true; + if (currentAgv) { + currentAgv.status = 'picking'; + } + logAgvStatus(id, "Reached pickup point, Waiting for material"); } - - setBoxVisible(true); - - setPath([...toPickupPath]); // Start path transition once the AGV is ready - setCurrentPhase("toDrop"); - progressRef.current = 0; - console.log("startPointReached: ", startPointReached.current); - startPointReached.current = false; - return; } - if (path.length < 2) return; + if (isPlaying && currentAgv?.isActive && currentPhase === "initial") { + if (!isAgvReady()) return; + setTimeout(() => { + setBoxVisible(true); + setPath([...pickupDropPath]); + setCurrentPhase("toDrop"); + progressRef.current = 0; + logAgvStatus(id, "Started from pickup point, heading to drop point"); + if (currentAgv) { + currentAgv.status = 'toDrop'; + } + }, 0) + return; + } + + if (isPlaying && currentPhase === "toDrop") { + const reached = moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef); + + if (reached && !isWaiting.current) { + isWaiting.current = true; + logAgvStatus(id, "Reached drop point"); + if (currentAgv) { + currentAgv.status = 'droping'; + } + timeoutRef.current = setTimeout(() => { + setPath([...dropPickupPath]); + setCurrentPhase("toPickup"); + progressRef.current = 0; + isWaiting.current = false; + setBoxVisible(false); + if (currentAgv) { + currentAgv.status = 'toPickup'; + } + logAgvStatus(id, "Started from droping point, heading to pickup point"); + }, bufferTime * 1000); + } + return; + } + + if (isPlaying && currentPhase === "toPickup") { + const reached = moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef); + + if (reached) { + if (currentAgv) { + currentAgv.isActive = false; + } + setCurrentPhase("initial"); + if (currentAgv) { + currentAgv.status = 'picking'; + } + logAgvStatus(id, "Reached pickup point again, cycle complete"); + } + return; + } + + moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef); + }); + + function moveAlongPath( + object: THREE.Object3D, + path: [number, number, number][], + distances: number[], + speed: number, + delta: number, + progressRef: React.MutableRefObject + ): boolean { + if (path.length < 2) return false; progressRef.current += delta * speed; - let covered = progressRef.current; let accumulated = 0; let index = 0; - if (distancesRef.current.length !== path.length - 1) { - distancesRef.current = []; - totalDistanceRef.current = 0; + for (; index < distances.length; index++) { + const dist = distances[index]; + if (accumulated + dist >= covered) break; + accumulated += dist; + } - for (let i = 0; i < path.length - 1; i++) { - const start = new THREE.Vector3(...path[i]); - const end = new THREE.Vector3(...path[i + 1]); - const distance = start.distanceTo(end); - distancesRef.current.push(distance); - totalDistanceRef.current += distance; + if (index >= path.length - 1) { + if (path.length > 1) { + const lastDirection = new THREE.Vector3(...path[path.length - 1]) + .sub(new THREE.Vector3(...path[path.length - 2])) + .normalize(); + object.rotation.y = Math.atan2(lastDirection.x, lastDirection.z); } + return true; } - while ( - index < distancesRef.current.length && - covered > accumulated + distancesRef.current[index] - ) { - accumulated += distancesRef.current[index]; - index++; - } - - // AGV has completed its path - if (index >= distancesRef.current.length) { - progressRef.current = totalDistanceRef.current; - - timeoutRef.current = setTimeout(() => { - if (!isAgvReady()) { - isWaiting.current = false; - return; - } - - let nextPath = []; - let nextPhase = currentPhase; - - if (currentPhase === "toDrop") { - nextPath = dropPickupPath; - nextPhase = "toPickup"; - setBoxVisible(false); - } else if (currentPhase === "toPickup") { - // When returning to start point (toPickup phase completed) - // Set position to toPickupPath[1] instead of [0] - if (toPickupPath.length > 1) { - object.position.copy(new THREE.Vector3(...toPickupPath[1])); - // Also set rotation towards the next point if available - if (toPickupPath.length > 2) { - const nextPoint = new THREE.Vector3(...toPickupPath[2]); - const direction = nextPoint - .clone() - .sub(object.position) - .normalize(); - object.rotation.y = Math.atan2(direction.x, direction.z); - } - } - - // Reset the path and mark start point as reached - setPath([]); - startPointReached.current = true; - progressRef.current = 0; - distancesRef.current = []; - - // Stop the AGV by setting isplaying to false - if (currentAgv) { - currentAgv.isplaying = false; - } - - // Clear timeout and return to prevent further movement - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - timeoutRef.current = null; - } - return; - } else { - nextPath = pickupDropPath; - nextPhase = "toDrop"; - setBoxVisible(true); - } - - setPath([...nextPath]); - setCurrentPhase(nextPhase); - progressRef.current = 0; - isWaiting.current = false; - distancesRef.current = []; - - // Reset hit count for the next cycle - if (agvRef.current) { - agvRef.current = agvRef.current.map((agv: AGVData) => - agv.vehicleId === id ? { ...agv, hitCount: 0 } : agv - ); - } - }, bufferTime * 1000); - - return; - } - - // Step 4: Interpolate position and rotation const start = new THREE.Vector3(...path[index]); const end = new THREE.Vector3(...path[index + 1]); - const dist = distancesRef.current[index]; - - if (!dist || dist === 0) return; + const dist = distances[index]; const t = THREE.MathUtils.clamp((covered - accumulated) / dist, 0, 1); object.position.copy(start.clone().lerp(end, t)); - const direction = new THREE.Vector3().subVectors(end, start).normalize(); - object.rotation.y = Math.atan2(direction.x, direction.z); - }); + if (dist > 0.1) { + const targetDirection = end.clone().sub(start).normalize(); + const targetRotationY = Math.atan2(targetDirection.x, targetDirection.z); + + const rotationSpeed = Math.min(5 * delta, 1); + object.rotation.y = THREE.MathUtils.lerp(object.rotation.y, targetRotationY, rotationSpeed); + } + + return false; + } useEffect(() => { return () => { diff --git a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts index c93b660..ea5ea74 100644 --- a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts @@ -20,7 +20,6 @@ async function loadInitialFloorItems( const organization = (email!.split("@")[1]).split(".")[0]; const items = await getFloorAssets(organization); - console.log('items: ', items); localStorage.setItem("FloorItems", JSON.stringify(items)); await initializeDB(); diff --git a/app/src/modules/scene/controls/selection/selectionControls.tsx b/app/src/modules/scene/controls/selection/selectionControls.tsx index acaed3a..11c2dfb 100644 --- a/app/src/modules/scene/controls/selection/selectionControls.tsx +++ b/app/src/modules/scene/controls/selection/selectionControls.tsx @@ -14,7 +14,6 @@ import CopyPasteControls from "./copyPasteControls"; import MoveControls from "./moveControls"; import RotateControls from "./rotateControls"; import useModuleStore from "../../../../store/useModuleStore"; -import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys"; const SelectionControls: React.FC = () => { const { camera, controls, gl, scene, pointer } = useThree(); @@ -102,13 +101,12 @@ const SelectionControls: React.FC = () => { }; const onKeyDown = (event: KeyboardEvent) => { - const keyCombination = detectModifierKeys(event); if (movedObjects.length > 0 || rotatedObjects.length > 0) return; - if (keyCombination === "ESCAPE") { + if (event.key.toLowerCase() === "escape") { event.preventDefault(); clearSelection(); } - if (keyCombination === "DELETE") { + if (event.key.toLowerCase() === "delete") { event.preventDefault(); deleteSelection(); } diff --git a/app/src/modules/simulation/process/processAnimator.tsx b/app/src/modules/simulation/process/processAnimator.tsx index 7b906d2..2ba5869 100644 --- a/app/src/modules/simulation/process/processAnimator.tsx +++ b/app/src/modules/simulation/process/processAnimator.tsx @@ -26,6 +26,7 @@ const ProcessAnimator: React.FC = ({ }) => { const gltf = useLoader(GLTFLoader, crate) as GLTF; const groupRef = useRef(null); + const tempStackedObjectsRef = useRef>({}); const { animationStates, @@ -43,27 +44,19 @@ const ProcessAnimator: React.FC = ({ checkAndCountTriggers, } = useProcessAnimation(processes, setProcesses, agvRef); - const baseMaterials = useMemo( - () => ({ - Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), - Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), - // Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), - Default: new THREE.MeshStandardMaterial(), - }), - [] - ); + const baseMaterials = useMemo(() => ({ + Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), + Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), + Default: new THREE.MeshStandardMaterial(), + }), []); useEffect(() => { // Update material references for all spawned objects Object.entries(animationStates).forEach(([processId, processState]) => { Object.keys(processState.spawnedObjects).forEach((objectId) => { - const entry = { - processId, - objectId, - }; + const entry = { processId, objectId, }; - const materialType = - processState.spawnedObjects[objectId]?.currentMaterialType; + const materialType = processState.spawnedObjects[objectId]?.currentMaterialType; if (!materialType) { return; @@ -72,17 +65,13 @@ const ProcessAnimator: React.FC = ({ const matRefArray = MaterialRef.current; // Find existing material group - const existing = matRefArray.find( - (entryGroup: { material: string; objects: any[] }) => - entryGroup.material === materialType + const existing = matRefArray.find((entryGroup: { material: string; objects: any[] }) => + entryGroup.material === materialType ); if (existing) { // Check if this processId + objectId already exists - const alreadyExists = existing.objects.some( - (o: any) => - o.processId === entry.processId && o.objectId === entry.objectId - ); + const alreadyExists = existing.objects.some((o: any) => o.processId === entry.processId && o.objectId === entry.objectId); if (!alreadyExists) { existing.objects.push(entry); @@ -102,8 +91,7 @@ const ProcessAnimator: React.FC = ({ useFrame(() => { // Spawn logic frame - const currentTime = - clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current; + const currentTime = clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current; setAnimationStates((prev) => { const newStates = { ...prev }; @@ -131,10 +119,7 @@ const ProcessAnimator: React.FC = ({ : parseFloat(spawnAction.spawnInterval as string) || 0; // Check if this is a zero interval spawn and we already spawned an object - if ( - spawnInterval === 0 && - processState.hasSpawnedZeroIntervalObject === true - ) { + if (spawnInterval === 0 && processState.hasSpawnedZeroIntervalObject === true) { return; // Don't spawn more objects for zero interval } @@ -312,28 +297,81 @@ const ProcessAnimator: React.FC = ({ // } // } + // if (isLastPoint) { + // if (currentPointData?.actions) { + // const hasNonInherit = hasNonInheritActions( + // currentPointData.actions + // ); + // if (!hasNonInherit) { + // // Remove the object if all actions are inherit + // updatedObjects[objectId] = { + // ...obj, + // visible: false, + // state: { ...stateRef, isAnimating: false }, + // }; + // return; + // } + // } else { + // // No actions at last point - remove the object + // updatedObjects[objectId] = { + // ...obj, + // visible: false, + // state: { ...stateRef, isAnimating: false }, + // }; + // return; + // } + // } + if (isLastPoint) { - if (currentPointData?.actions) { - const hasNonInherit = hasNonInheritActions( - currentPointData.actions - ); - if (!hasNonInherit) { - // Remove the object if all actions are inherit + const isAgvPicking = agvRef.current.some( + (agv: any) => agv.processId === process.id && agv.status === "picking" + ); + + const shouldHide = !currentPointData?.actions || !hasNonInheritActions(currentPointData.actions); + + if (shouldHide) { + if (isAgvPicking) { updatedObjects[objectId] = { ...obj, visible: false, - state: { ...stateRef, isAnimating: false }, + state: { + ...stateRef, + isAnimating: false, + }, + }; + } else { + tempStackedObjectsRef.current[objectId] = true; + + updatedObjects[objectId] = { + ...obj, + visible: true, + state: { + ...stateRef, + isAnimating: true, + }, }; - return; } - } else { - // No actions at last point - remove the object + + return; + } + } + + if (tempStackedObjectsRef.current[objectId]) { + const isAgvPicking = agvRef.current.some( + (agv: any) => agv.processId === process.id && agv.status === "picking" + ); + + if (isAgvPicking) { + delete tempStackedObjectsRef.current[objectId]; + updatedObjects[objectId] = { ...obj, visible: false, - state: { ...stateRef, isAnimating: false }, + state: { + ...stateRef, + isAnimating: false, + }, }; - return; } } diff --git a/app/src/modules/simulation/process/processCreator.tsx b/app/src/modules/simulation/process/processCreator.tsx index cae152b..041b4ec 100644 --- a/app/src/modules/simulation/process/processCreator.tsx +++ b/app/src/modules/simulation/process/processCreator.tsx @@ -57,7 +57,7 @@ // pointActions: PointAction[][]; // pointTriggers: PointTrigger[][]; // speed: number; -// isplaying: boolean; +// isActive: boolean; // } // interface ProcessCreatorProps { @@ -147,7 +147,7 @@ // pointActions: [], // pointTriggers: [], // Added point triggers array // speed: 1, -// isplaying: false, +// isActive: false, // }); // // Enhanced connection checking function @@ -283,7 +283,7 @@ // pointActions, // pointTriggers, // speed: processSpeed, -// isplaying: false, +// isActive: false, // }; // }, // [scene] @@ -410,7 +410,7 @@ // p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID) // ) // .join(","), -// isplaying: false, +// isActive: false, // })); // }, [convertedPaths]); @@ -459,6 +459,7 @@ import { ConveyorEventsSchema, VehicleEventsSchema, } from "../../../types/world/worldTypes"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; // Type definitions export interface PointAction { @@ -495,7 +496,7 @@ export interface SimulationPath { points: PathPoint[]; pathPosition: [number, number, number]; speed?: number; - isplaying: boolean; + isActive: boolean; } export interface Process { @@ -505,7 +506,7 @@ export interface Process { pointActions: PointAction[][]; pointTriggers: PointTrigger[][]; speed: number; - isplaying: boolean; + isActive: boolean; } interface ProcessCreatorProps { @@ -538,13 +539,13 @@ function convertToSimulationPath( actions: Array.isArray(point.actions) ? point.actions.map(normalizeAction) : point.actions - ? [normalizeAction(point.actions)] - : [], + ? [normalizeAction(point.actions)] + : [], triggers: Array.isArray(point.triggers) ? point.triggers.map(normalizeTrigger) : point.triggers - ? [normalizeTrigger(point.triggers)] - : [], + ? [normalizeTrigger(point.triggers)] + : [], connections: { targets: point.connections.targets.map((target) => ({ modelUUID: target.modelUUID, @@ -556,7 +557,7 @@ function convertToSimulationPath( typeof path.speed === "string" ? parseFloat(path.speed) || 1 : path.speed || 1, - isplaying: false, // Added missing property + isActive: false, // Added missing property }; } else { // For vehicle paths, handle the case where triggers might not exist @@ -570,8 +571,8 @@ function convertToSimulationPath( actions: Array.isArray(path.points.actions) ? path.points.actions.map(normalizeAction) : path.points.actions - ? [normalizeAction(path.points.actions)] - : [], + ? [normalizeAction(path.points.actions)] + : [], triggers: [], connections: { targets: path.points.connections.targets.map((target) => ({ @@ -582,7 +583,7 @@ function convertToSimulationPath( ], pathPosition: path.position, speed: path.points.speed || 1, - isplaying: false, + isActive: false, }; } } @@ -595,7 +596,7 @@ const createEmptyProcess = (): Process => ({ pointActions: [], pointTriggers: [], // Added point triggers array speed: 1, - isplaying: false, + isActive: false, }); // Enhanced connection checking function @@ -731,7 +732,7 @@ export function useProcessCreation() { pointActions, pointTriggers, speed: processSpeed, - isplaying: false, + isActive: false, }; }, [scene] @@ -824,6 +825,7 @@ const ProcessCreator: React.FC = React.memo( const { createProcessesFromPaths } = useProcessCreation(); const prevPathsRef = useRef([]); const prevProcessesRef = useRef([]); + const { isPlaying } = usePlayButtonStore(); const convertedPaths = useMemo((): SimulationPath[] => { if (!simulationStates) return []; @@ -858,7 +860,7 @@ const ProcessCreator: React.FC = React.memo( p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID) ) .join(","), - isplaying: false, + isActive: false, })); }, [convertedPaths]); diff --git a/app/src/modules/simulation/process/types.ts b/app/src/modules/simulation/process/types.ts index 086b620..6f935fc 100644 --- a/app/src/modules/simulation/process/types.ts +++ b/app/src/modules/simulation/process/types.ts @@ -39,7 +39,7 @@ export interface ProcessPath { pathRotation: number[]; speed: number; type: "Conveyor" | "Vehicle"; - isplaying: boolean + isActive: boolean } export interface ProcessData { diff --git a/app/src/modules/simulation/process/useProcessAnimations.tsx b/app/src/modules/simulation/process/useProcessAnimations.tsx index 7525c29..032d013 100644 --- a/app/src/modules/simulation/process/useProcessAnimations.tsx +++ b/app/src/modules/simulation/process/useProcessAnimations.tsx @@ -1,540 +1,609 @@ import { useCallback, useEffect, useRef, useState } from "react"; import * as THREE from "three"; import { - ProcessData, - ProcessAnimationState, - SpawnedObject, - AnimationState, - ProcessPoint, - PointAction, - Trigger, + ProcessData, + ProcessAnimationState, + SpawnedObject, + AnimationState, + ProcessPoint, + PointAction, + Trigger, } from "./types"; import { - useAnimationPlaySpeed, - usePauseButtonStore, - usePlayButtonStore, - useResetButtonStore, + useAnimationPlaySpeed, + usePauseButtonStore, + usePlayButtonStore, + useResetButtonStore, } from "../../../store/usePlayButtonStore"; import { usePlayAgv } from "../../../store/store"; // Enhanced ProcessAnimationState with trigger tracking interface EnhancedProcessAnimationState extends ProcessAnimationState { - triggerCounts: Record; - triggerLogs: Array<{ - timestamp: number; - pointId: string; - objectId: string; - triggerId: string; - }>; + triggerCounts: Record; + triggerLogs: Array<{ + timestamp: number; + pointId: string; + objectId: string; + triggerId: string; + }>; } interface ProcessContainerProps { - processes: ProcessData[]; - setProcesses: React.Dispatch>; - agvRef: any; + processes: ProcessData[]; + setProcesses: React.Dispatch>; + agvRef: any; } interface PlayAgvState { - playAgv: Record; - setPlayAgv: (data: any) => void; + playAgv: Record; + setPlayAgv: (data: any) => void; } export const useProcessAnimation = ( - processes: ProcessData[], - setProcesses: React.Dispatch>, - agvRef: any + processes: ProcessData[], + setProcesses: React.Dispatch>, + agvRef: any ) => { - // State and refs initialization - const { isPlaying, setIsPlaying } = usePlayButtonStore(); - const { isPaused, setIsPaused } = usePauseButtonStore(); - const { isReset, setReset } = useResetButtonStore(); - const debugRef = useRef(false); - const clockRef = useRef(new THREE.Clock()); - const pauseTimeRef = useRef(0); - const elapsedBeforePauseRef = useRef(0); - const animationStatesRef = useRef< - Record - >({}); - const { speed } = useAnimationPlaySpeed(); - const prevIsPlaying = useRef(null); - const [internalResetFlag, setInternalResetFlag] = useState(false); - const [animationStates, setAnimationStates] = useState< - Record - >({}); - const speedRef = useRef(speed); - const { PlayAgv, setPlayAgv } = usePlayAgv(); + // State and refs initialization + const { isPlaying, setIsPlaying } = usePlayButtonStore(); + const { isPaused, setIsPaused } = usePauseButtonStore(); + const { isReset, setReset } = useResetButtonStore(); + const debugRef = useRef(false); + const clockRef = useRef(new THREE.Clock()); + const pauseTimeRef = useRef(0); + const elapsedBeforePauseRef = useRef(0); + const animationStatesRef = useRef>({}); + const { speed } = useAnimationPlaySpeed(); + const prevIsPlaying = useRef(null); + const [internalResetFlag, setInternalResetFlag] = useState(false); + const [animationStates, setAnimationStates] = useState>({}); + const speedRef = useRef(speed); + const { PlayAgv, setPlayAgv } = usePlayAgv(); - // Effect hooks - useEffect(() => { - speedRef.current = speed; - }, [speed]); + // Effect hooks + useEffect(() => { + speedRef.current = speed; + }, [speed]); - useEffect(() => { - if (prevIsPlaying.current !== null) { - setAnimationStates({}); - } - prevIsPlaying.current = isPlaying; - }, [isPlaying]); - - useEffect(() => { - animationStatesRef.current = animationStates; - }, [animationStates]); - - // Reset handler - useEffect(() => { - if (isReset) { - setInternalResetFlag(true); - setIsPlaying(false); - setIsPaused(false); - setAnimationStates({}); - animationStatesRef.current = {}; - clockRef.current = new THREE.Clock(); - elapsedBeforePauseRef.current = 0; - pauseTimeRef.current = 0; - setReset(false); - setTimeout(() => { - setInternalResetFlag(false); - setIsPlaying(true); - }, 0); - } - }, [isReset, setReset, setIsPlaying, setIsPaused]); - - // Pause handler - useEffect(() => { - if (isPaused) { - pauseTimeRef.current = clockRef.current.getElapsedTime(); - } else if (pauseTimeRef.current > 0) { - const pausedDuration = - clockRef.current.getElapsedTime() - pauseTimeRef.current; - elapsedBeforePauseRef.current += pausedDuration; - } - }, [isPaused]); - - // Initialize animation states with trigger tracking - useEffect(() => { - if (isPlaying && !internalResetFlag) { - const newStates: Record = {}; - - processes.forEach((process) => { - const triggerCounts: Record = {}; - - // Initialize trigger counts for all On-Hit triggers - process.paths?.forEach((path) => { - path.points?.forEach((point) => { - point.triggers?.forEach((trigger: Trigger) => { - if (trigger.type === "On-Hit" && trigger.isUsed) { - triggerCounts[`${point.uuid}-${trigger.uuid}`] = 0; - } - }); - }); - }); - - newStates[process.id] = { - spawnedObjects: {}, - nextSpawnTime: 0, - objectIdCounter: 0, - isProcessDelaying: false, - processDelayStartTime: 0, - processDelayDuration: 0, - triggerCounts, - triggerLogs: [], - }; - }); - - setAnimationStates(newStates); - animationStatesRef.current = newStates; - clockRef.current.start(); - } - }, [isPlaying, processes, internalResetFlag]); - - // Helper functions - const findSpawnPoint = (process: ProcessData): ProcessPoint | null => { - for (const path of process.paths || []) { - for (const point of path.points || []) { - const spawnAction = point.actions?.find( - (a) => a.isUsed && a.type === "Spawn" - ); - if (spawnAction) { - return point; + useEffect(() => { + if (prevIsPlaying.current !== null || !isPlaying) { + setAnimationStates({}); } - } - } - return null; - }; + prevIsPlaying.current = isPlaying; + }, [isPlaying]); - const findAnimationPathPoint = ( - process: ProcessData, - spawnPoint: ProcessPoint - ): THREE.Vector3 => { - if (process.animationPath && process.animationPath.length > 0) { - let pointIndex = 0; - for (const path of process.paths || []) { - for (let i = 0; i < (path.points?.length || 0); i++) { - const point = path.points?.[i]; - if (point && point.uuid === spawnPoint.uuid) { - if (process.animationPath[pointIndex]) { - const p = process.animationPath[pointIndex]; - return new THREE.Vector3(p.x, p.y, p.z); - } - } - pointIndex++; + useEffect(() => { + animationStatesRef.current = animationStates; + }, [animationStates]); + + // Reset handler + useEffect(() => { + if (isReset) { + setInternalResetFlag(true); + setIsPlaying(false); + setIsPaused(false); + setAnimationStates({}); + animationStatesRef.current = {}; + clockRef.current = new THREE.Clock(); + elapsedBeforePauseRef.current = 0; + pauseTimeRef.current = 0; + setReset(false); + setTimeout(() => { + setInternalResetFlag(false); + setIsPlaying(true); + }, 0); } - } - } - return new THREE.Vector3( - spawnPoint.position[0], - spawnPoint.position[1], - spawnPoint.position[2] - ); - }; + }, [isReset, setReset, setIsPlaying, setIsPaused]); - // Optimized object creation - const createSpawnedObject = useCallback( - ( - process: ProcessData, - currentTime: number, - materialType: string, - spawnPoint: ProcessPoint, - baseMaterials: Record - ): SpawnedObject => { - const processMaterials = { - ...baseMaterials, - ...(process.customMaterials || {}), - }; + // Pause handler + useEffect(() => { + if (isPaused) { + pauseTimeRef.current = clockRef.current.getElapsedTime(); + } else if (pauseTimeRef.current > 0) { + const pausedDuration = clockRef.current.getElapsedTime() - pauseTimeRef.current; + elapsedBeforePauseRef.current += pausedDuration; + } + }, [isPaused]); - const spawnPosition = findAnimationPathPoint(process, spawnPoint); - const material = - processMaterials[materialType as keyof typeof processMaterials] || - baseMaterials.Default; + // Initialize animation states with trigger tracking + useEffect(() => { + if (isPlaying && !internalResetFlag) { + const newStates: Record = {}; - return { - ref: { current: null }, - state: { - currentIndex: 0, - progress: 0, - isAnimating: true, - speed: process.speed || 1, - isDelaying: false, - delayStartTime: 0, - currentDelayDuration: 0, - delayComplete: false, - currentPathIndex: 0, - }, - visible: true, - material: material, - currentMaterialType: materialType, - spawnTime: currentTime, - position: spawnPosition, - }; - }, - [] - ); + processes.forEach((process) => { + const triggerCounts: Record = {}; - // Material handling - const handleMaterialSwap = useCallback( - ( - processId: string, - objectId: string, - materialType: string, - processes: ProcessData[], - baseMaterials: Record - ) => { - setAnimationStates((prev) => { - const processState = prev[processId]; - if (!processState || !processState.spawnedObjects[objectId]) - return prev; + // Initialize trigger counts for all On-Hit triggers + process.paths?.forEach((path) => { + path.points?.forEach((point) => { + point.triggers?.forEach((trigger: Trigger) => { + if (trigger.type === "On-Hit" && trigger.isUsed) { + triggerCounts[`${point.uuid}-${trigger.uuid}`] = 0; + } + }); + }); + }); - const process = processes.find((p) => p.id === processId); - if (!process) return prev; - - const processMaterials = { - ...baseMaterials, - ...(process.customMaterials || {}), - }; - - const newMaterial = - processMaterials[materialType as keyof typeof processMaterials]; - if (!newMaterial) return prev; - - return { - ...prev, - [processId]: { - ...processState, - spawnedObjects: { - ...processState.spawnedObjects, - [objectId]: { - ...processState.spawnedObjects[objectId], - material: newMaterial, - currentMaterialType: materialType, - }, - }, - }, - }; - }); - }, - [] - ); - - // Point action handler with trigger counting - const handlePointActions = useCallback( - ( - processId: string, - objectId: string, - actions: PointAction[] = [], - currentTime: number, - processes: ProcessData[], - baseMaterials: Record - ): boolean => { - let shouldStopAnimation = false; - - actions.forEach((action) => { - if (!action.isUsed) return; - - switch (action.type) { - case "Delay": - setAnimationStates((prev) => { - const processState = prev[processId]; - if (!processState || processState.isProcessDelaying) return prev; - - const delayDuration = - typeof action.delay === "number" - ? action.delay - : parseFloat(action.delay as string) || 0; - - if (delayDuration > 0) { - return { - ...prev, - [processId]: { - ...processState, - isProcessDelaying: true, - processDelayStartTime: currentTime, - processDelayDuration: delayDuration, - spawnedObjects: { - ...processState.spawnedObjects, - [objectId]: { - ...processState.spawnedObjects[objectId], - state: { - ...processState.spawnedObjects[objectId].state, - isAnimating: false, - isDelaying: true, - delayStartTime: currentTime, - currentDelayDuration: delayDuration, - delayComplete: false, - }, - }, - }, - }, + newStates[process.id] = { + spawnedObjects: {}, + nextSpawnTime: 0, + objectIdCounter: 0, + isProcessDelaying: false, + processDelayStartTime: 0, + processDelayDuration: 0, + triggerCounts, + triggerLogs: [], }; - } - return prev; }); - shouldStopAnimation = true; - break; - case "Despawn": - setAnimationStates((prev) => { - const processState = prev[processId]; - if (!processState) return prev; - - const newSpawnedObjects = { ...processState.spawnedObjects }; - delete newSpawnedObjects[objectId]; - - return { - ...prev, - [processId]: { - ...processState, - spawnedObjects: newSpawnedObjects, - }, - }; - }); - shouldStopAnimation = true; - break; - - case "Swap": - if (action.material) { - handleMaterialSwap( - processId, - objectId, - action.material, - processes, - baseMaterials - ); - } - break; - - default: - break; + setAnimationStates(newStates); + animationStatesRef.current = newStates; + clockRef.current.start(); } - }); + }, [isPlaying, processes, internalResetFlag]); - return shouldStopAnimation; - }, - [handleMaterialSwap] - ); + useEffect(() => { + if (isPlaying && !internalResetFlag) { + const newStates: Record = {}; - // Trigger counting system - const checkAndCountTriggers = useCallback( - ( - processId: string, - objectId: string, - currentPointIndex: number, - processes: ProcessData[], - currentTime: number - ) => { - setAnimationStates((prev) => { - const processState = prev[processId]; - if (!processState) return prev; + // Initialize AGVs for each process first + processes.forEach((process) => { + // Find all vehicle paths for this process + const vehiclePaths = process.paths?.filter( + (path) => path.type === "Vehicle" + ) || []; - const process = processes.find((p) => p.id === processId); - if (!process) return prev; + // Initialize AGVs for each vehicle path + vehiclePaths.forEach((vehiclePath) => { + if (vehiclePath.points?.length > 0) { + const vehiclePoint = vehiclePath.points[0]; + const action = vehiclePoint.actions?.[0]; + const maxHitCount = action?.hitCount; - const point = getPointDataForAnimationIndex(process, currentPointIndex); - if (!point?.triggers) return prev; + const vehicleId = vehiclePath.modeluuid; + const processId = process.id; - const onHitTriggers = point.triggers.filter( - (t: Trigger) => t.type === "On-Hit" && t.isUsed + // Check if this AGV already exists + const existingAgv = agvRef.current.find( + (v: any) => v.vehicleId === vehicleId && v.processId === processId + ); + + if (!existingAgv) { + // Initialize the AGV in a stationed state + agvRef.current.push({ + processId, + vehicleId, + maxHitCount: maxHitCount || 0, + isActive: false, + hitCount: 0, + status: 'stationed', + lastUpdated: 0 + }); + } + } + }); + + // Then initialize trigger counts as before + const triggerCounts: Record = {}; + process.paths?.forEach((path) => { + path.points?.forEach((point) => { + point.triggers?.forEach((trigger: Trigger) => { + if (trigger.type === "On-Hit" && trigger.isUsed) { + triggerCounts[`${point.uuid}-${trigger.uuid}`] = 0; + } + }); + }); + }); + + newStates[process.id] = { + spawnedObjects: {}, + nextSpawnTime: 0, + objectIdCounter: 0, + isProcessDelaying: false, + processDelayStartTime: 0, + processDelayDuration: 0, + triggerCounts, + triggerLogs: [], + }; + }); + + setAnimationStates(newStates); + animationStatesRef.current = newStates; + clockRef.current.start(); + } + }, [isPlaying, processes, internalResetFlag]); + + // Helper functions + const findSpawnPoint = (process: ProcessData): ProcessPoint | null => { + for (const path of process.paths || []) { + for (const point of path.points || []) { + const spawnAction = point.actions?.find( + (a) => a.isUsed && a.type === "Spawn" + ); + if (spawnAction) { + return point; + } + } + } + return null; + }; + + const findAnimationPathPoint = ( + process: ProcessData, + spawnPoint: ProcessPoint + ): THREE.Vector3 => { + if (process.animationPath && process.animationPath.length > 0) { + let pointIndex = 0; + for (const path of process.paths || []) { + for (let i = 0; i < (path.points?.length || 0); i++) { + const point = path.points?.[i]; + if (point && point.uuid === spawnPoint.uuid) { + if (process.animationPath[pointIndex]) { + const p = process.animationPath[pointIndex]; + return new THREE.Vector3(p.x, p.y, p.z); + } + } + pointIndex++; + } + } + } + return new THREE.Vector3( + spawnPoint.position[0], + spawnPoint.position[1], + spawnPoint.position[2] ); - if (onHitTriggers.length === 0) return prev; + }; - const newTriggerCounts = { ...processState.triggerCounts }; - const newTriggerLogs = [...processState.triggerLogs]; - let shouldLog = false; + // Optimized object creation + const createSpawnedObject = useCallback( + ( + process: ProcessData, + currentTime: number, + materialType: string, + spawnPoint: ProcessPoint, + baseMaterials: Record + ): SpawnedObject => { + const processMaterials = { + ...baseMaterials, + ...(process.customMaterials || {}), + }; - // Update counts for all valid triggers - onHitTriggers.forEach((trigger: Trigger) => { - const triggerKey = `${point.uuid}-${trigger.uuid}`; - newTriggerCounts[triggerKey] = - (newTriggerCounts[triggerKey] || 0) + 1; - shouldLog = true; - newTriggerLogs.push({ - timestamp: currentTime, - pointId: point.uuid, - objectId, - triggerId: trigger.uuid, - }); - }); + const spawnPosition = findAnimationPathPoint(process, spawnPoint); + const material = + processMaterials[materialType as keyof typeof processMaterials] || + baseMaterials.Default; - const processTotalHits = Object.values(newTriggerCounts).reduce( - (a, b) => a + b, - 0 - ); + return { + ref: { current: null }, + state: { + currentIndex: 0, + progress: 0, + isAnimating: true, + speed: process.speed || 1, + isDelaying: false, + delayStartTime: 0, + currentDelayDuration: 0, + delayComplete: false, + currentPathIndex: 0, + }, + visible: true, + material: material, + currentMaterialType: materialType, + spawnTime: currentTime, + position: spawnPosition, + }; + }, + [] + ); - if (shouldLog) { - const vehiclePaths = process.paths.filter( - (path) => path.type === "Vehicle" - ); + // Material handling + const handleMaterialSwap = useCallback( + ( + processId: string, + objectId: string, + materialType: string, + processes: ProcessData[], + baseMaterials: Record + ) => { + setAnimationStates((prev) => { + const processState = prev[processId]; + if (!processState || !processState.spawnedObjects[objectId]) + return prev; - vehiclePaths.forEach((vehiclePath) => { - if (vehiclePath.points?.length > 0) { - const vehiclePoint = vehiclePath.points[0]; - const action = vehiclePoint.actions?.[0]; - let expectedHitCount = action?.hitCount; + const process = processes.find((p) => p.id === processId); + if (!process) return prev; - if (expectedHitCount !== undefined) { - const vehicleId = vehiclePath.modeluuid; + const processMaterials = { + ...baseMaterials, + ...(process.customMaterials || {}), + }; - let vehicleEntry = agvRef.current.find( - (v: any) => - v.vehicleId === vehicleId && v.processId === processId + const newMaterial = + processMaterials[materialType as keyof typeof processMaterials]; + if (!newMaterial) return prev; + + return { + ...prev, + [processId]: { + ...processState, + spawnedObjects: { + ...processState.spawnedObjects, + [objectId]: { + ...processState.spawnedObjects[objectId], + material: newMaterial, + currentMaterialType: materialType, + }, + }, + }, + }; + }); + }, + [] + ); + + // Point action handler with trigger counting + const handlePointActions = useCallback( + ( + processId: string, + objectId: string, + actions: PointAction[] = [], + currentTime: number, + processes: ProcessData[], + baseMaterials: Record + ): boolean => { + let shouldStopAnimation = false; + + actions.forEach((action) => { + if (!action.isUsed) return; + + switch (action.type) { + case "Delay": + setAnimationStates((prev) => { + const processState = prev[processId]; + if (!processState || processState.isProcessDelaying) return prev; + + const delayDuration = + typeof action.delay === "number" + ? action.delay + : parseFloat(action.delay as string) || 0; + + if (delayDuration > 0) { + return { + ...prev, + [processId]: { + ...processState, + isProcessDelaying: true, + processDelayStartTime: currentTime, + processDelayDuration: delayDuration, + spawnedObjects: { + ...processState.spawnedObjects, + [objectId]: { + ...processState.spawnedObjects[objectId], + state: { + ...processState.spawnedObjects[objectId].state, + isAnimating: false, + isDelaying: true, + delayStartTime: currentTime, + currentDelayDuration: delayDuration, + delayComplete: false, + }, + }, + }, + }, + }; + } + return prev; + }); + shouldStopAnimation = true; + break; + + case "Despawn": + setAnimationStates((prev) => { + const processState = prev[processId]; + if (!processState) return prev; + + const newSpawnedObjects = { ...processState.spawnedObjects }; + delete newSpawnedObjects[objectId]; + + return { + ...prev, + [processId]: { + ...processState, + spawnedObjects: newSpawnedObjects, + }, + }; + }); + shouldStopAnimation = true; + break; + + case "Swap": + if (action.material) { + handleMaterialSwap( + processId, + objectId, + action.material, + processes, + baseMaterials + ); + } + break; + + default: + break; + } + }); + + return shouldStopAnimation; + }, + [handleMaterialSwap] + ); + + // Trigger counting system + const checkAndCountTriggers = useCallback( + ( + processId: string, + objectId: string, + currentPointIndex: number, + processes: ProcessData[], + currentTime: number + ) => { + setAnimationStates((prev) => { + const processState = prev[processId]; + if (!processState) return prev; + + const process = processes.find((p) => p.id === processId); + if (!process) return prev; + + const point = getPointDataForAnimationIndex(process, currentPointIndex); + if (!point?.triggers) return prev; + + const onHitTriggers = point.triggers.filter( + (t: Trigger) => t.type === "On-Hit" && t.isUsed + ); + if (onHitTriggers.length === 0) return prev; + + let newTriggerCounts = { ...processState.triggerCounts }; + const newTriggerLogs = [...processState.triggerLogs]; + let shouldLog = false; + + // Find all vehicle paths for this process + const vehiclePaths = process.paths.filter( + (path) => path.type === "Vehicle" ); - if (!vehicleEntry) { - vehicleEntry = { - processId, - vehicleId, - expectedHitCount, - isplaying: false, - hitCount: 0, // Initialize hitCount - }; - agvRef.current.push(vehicleEntry); + // Check if any vehicle is active for this process + const activeVehicles = vehiclePaths.filter(path => { + const vehicleId = path.modeluuid; + const vehicleEntry = agvRef.current.find( + (v: any) => v.vehicleId === vehicleId && v.processId === processId + ); + return vehicleEntry?.isActive; + }); + + // Only count triggers if no vehicles are active for this process + if (activeVehicles.length === 0) { + onHitTriggers.forEach((trigger: Trigger) => { + const triggerKey = `${point.uuid}-${trigger.uuid}`; + newTriggerCounts[triggerKey] = (newTriggerCounts[triggerKey] || 0) + 1; + shouldLog = true; + newTriggerLogs.push({ + timestamp: currentTime, + pointId: point.uuid, + objectId, + triggerId: trigger.uuid, + }); + }); } - // Increment expectedHitCount if not playing - if (!vehicleEntry.isplaying) { - vehicleEntry.expectedHitCount = processTotalHits; + let processTotalHits = Object.values(newTriggerCounts).reduce((a, b) => a + b, 0); + + if (shouldLog) { + vehiclePaths.forEach((vehiclePath) => { + if (vehiclePath.points?.length > 0) { + const vehiclePoint = vehiclePath.points[0]; + const action = vehiclePoint.actions?.[0]; + const maxHitCount = action?.hitCount; + + if (maxHitCount !== undefined) { + const vehicleId = vehiclePath.modeluuid; + let vehicleEntry = agvRef.current.find((v: any) => v.vehicleId === vehicleId && v.processId === processId); + + if (!vehicleEntry) { + vehicleEntry = { + processId, + vehicleId, + maxHitCount: maxHitCount, + isActive: false, + hitCount: 0, + status: 'stationed' + }; + agvRef.current.push(vehicleEntry); + } + + // if (!vehicleEntry.isActive && vehicleEntry.status === 'picking') { + if (!vehicleEntry.isActive) { + vehicleEntry.hitCount = processTotalHits; + vehicleEntry.lastUpdated = currentTime; + + if (vehicleEntry.hitCount >= vehicleEntry.maxHitCount) { + vehicleEntry.isActive = true; + newTriggerCounts = {}; + processTotalHits = 0; + } + } + } + } + }); } - // Set vehicle's hitCount to the processTotalHits - vehicleEntry.hitCount = processTotalHits; - vehicleEntry.lastUpdated = currentTime; + return { + ...prev, + [processId]: { + ...processState, + triggerCounts: newTriggerCounts, + triggerLogs: newTriggerLogs, + totalHits: processTotalHits, + }, + }; + }); + }, + [] + ); - // If hitCount matches expectedHitCount, set isplaying to true - if (vehicleEntry.hitCount >= vehicleEntry.expectedHitCount) { - vehicleEntry.isplaying = true; + // Utility functions + const hasNonInheritActions = useCallback( + (actions: PointAction[] = []): boolean => { + return actions.some( + (action) => action.isUsed && action.type !== "Inherit" + ); + }, + [] + ); + + const getPointDataForAnimationIndex = useCallback( + (process: ProcessData, index: number): ProcessPoint | null => { + if (!process.paths) return null; + + let cumulativePoints = 0; + for (const path of process.paths) { + const pointCount = path.points?.length || 0; + + if (index < cumulativePoints + pointCount) { + const pointIndex = index - cumulativePoints; + return path.points?.[pointIndex] || null; } - } + + cumulativePoints += pointCount; } - }); - } - return { - ...prev, - [processId]: { - ...processState, - triggerCounts: newTriggerCounts, - triggerLogs: newTriggerLogs, - totalHits: processTotalHits, - }, - }; - }); - }, - [] - ); + return null; + }, + [] + ); - // Utility functions - const hasNonInheritActions = useCallback( - (actions: PointAction[] = []): boolean => { - return actions.some( - (action) => action.isUsed && action.type !== "Inherit" - ); - }, - [] - ); + const getTriggerCounts = useCallback((processId: string) => { + return animationStatesRef.current[processId]?.triggerCounts || {}; + }, []); - const getPointDataForAnimationIndex = useCallback( - (process: ProcessData, index: number): ProcessPoint | null => { - if (!process.paths) return null; + const getTriggerLogs = useCallback((processId: string) => { + return animationStatesRef.current[processId]?.triggerLogs || []; + }, []); - let cumulativePoints = 0; - for (const path of process.paths) { - const pointCount = path.points?.length || 0; - - if (index < cumulativePoints + pointCount) { - const pointIndex = index - cumulativePoints; - return path.points?.[pointIndex] || null; - } - - cumulativePoints += pointCount; - } - - return null; - }, - [] - ); - - const getTriggerCounts = useCallback((processId: string) => { - return animationStatesRef.current[processId]?.triggerCounts || {}; - }, []); - - const getTriggerLogs = useCallback((processId: string) => { - return animationStatesRef.current[processId]?.triggerLogs || []; - }, []); - - return { - animationStates, - setAnimationStates, - clockRef, - elapsedBeforePauseRef, - speedRef, - debugRef, - findSpawnPoint, - createSpawnedObject, - handlePointActions, - hasNonInheritActions, - getPointDataForAnimationIndex, - checkAndCountTriggers, - getTriggerCounts, - getTriggerLogs, - processes, - }; + return { + animationStates, + setAnimationStates, + clockRef, + elapsedBeforePauseRef, + speedRef, + debugRef, + findSpawnPoint, + createSpawnedObject, + handlePointActions, + hasNonInheritActions, + getPointDataForAnimationIndex, + checkAndCountTriggers, + getTriggerCounts, + getTriggerLogs, + processes, + }; }; diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 5bcf7d1..0741b5f 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -15,7 +15,6 @@ import Agv from "../builder/agv/agv"; function Simulation() { const { activeModule } = useModuleStore(); const pathsGroupRef = useRef() as React.MutableRefObject; - const { simulationStates, setSimulationStates } = useSimulationStates(); const [processes, setProcesses] = useState([]); const agvRef = useRef([]); const MaterialRef = useRef([]);