diff --git a/app/package-lock.json b/app/package-lock.json index 8be748e..5d92f68 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -48,6 +48,7 @@ "zustand": "^5.0.0-rc.2" }, "devDependencies": { + "@types/html2canvas": "^1.0.0", "@types/node": "^22.9.1", "@types/three": "^0.169.0", "axios": "^1.8.4", @@ -2020,7 +2021,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 +2033,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 +4136,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 +4267,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", @@ -6470,6 +6491,17 @@ "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" }, + "node_modules/@types/html2canvas": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/html2canvas/-/html2canvas-1.0.0.tgz", + "integrity": "sha512-BJpVf+FIN9UERmzhbtUgpXj6XBZpG67FMgBLLoj9HZKd9XifcCpSV+UnFcwTZfEyun4U/KmCrrVOG7829L589w==", + "deprecated": "This is a stub types definition. html2canvas provides its own type definitions, so you do not need this installed.", + "dev": true, + "license": "MIT", + "dependencies": { + "html2canvas": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -9007,7 +9039,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 +9916,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 +15279,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 +20747,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 +20790,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 +20802,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 +21298,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 +22357,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/package.json b/app/package.json index ce5c7d3..e555d5f 100644 --- a/app/package.json +++ b/app/package.json @@ -70,6 +70,7 @@ ] }, "devDependencies": { + "@types/html2canvas": "^1.0.0", "@types/node": "^22.9.1", "@types/three": "^0.169.0", "axios": "^1.8.4", diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx index d0873c5..7b532b8 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx @@ -45,7 +45,10 @@ const ChartWidget: React.FC = ({ type, index, title }) => { ), title, panel: "top", - data: sampleData, + Data: { + measurements:{}, + duration:'1h' + }, }); }} onDragEnd={() => setDraggedAsset(null)} 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/modules/builder/agv/agv.tsx b/app/src/modules/builder/agv/agv.tsx index 5b8a2e4..c2e7783 100644 --- a/app/src/modules/builder/agv/agv.tsx +++ b/app/src/modules/builder/agv/agv.tsx @@ -1,111 +1,108 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, 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 { useAnimationPlaySpeed, 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(); + const { speed } = useAnimationPlaySpeed(); + const globalSpeed = useRef(1); + useEffect(() => { globalSpeed.current = speed }, [speed]) + 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..9a6fae7 100644 --- a/app/src/modules/builder/agv/pathNavigator.tsx +++ b/app/src/modules/builder/agv/pathNavigator.tsx @@ -3,15 +3,15 @@ import * as THREE from "three"; import { useFrame, useThree } from "@react-three/fiber"; import { NavMeshQuery } from "@recast-navigation/core"; import { Line } from "@react-three/drei"; -import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { useAnimationPlaySpeed, usePlayButtonStore } from "../../../store/usePlayButtonStore"; import { usePlayAgv } from "../../../store/store"; -import crate from "../../../assets/gltf-glb/crate_box.glb"; interface PathNavigatorProps { navMesh: any; pathPoints: any; id: string; speed: number; + globalSpeed: number, bufferTime: number; hitCount: number; processes: any[]; @@ -31,6 +31,7 @@ export default function PathNavigator({ pathPoints, id, speed, + globalSpeed, bufferTime, hitCount, processes, @@ -38,30 +39,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 +53,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 +97,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 +126,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 +162,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 +175,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 +232,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 +243,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 +258,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; + } - progressRef.current += delta * speed; + 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 * globalSpeed); 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/builder/groups/zoneGroup.tsx b/app/src/modules/builder/groups/zoneGroup.tsx index afb5d50..d218534 100644 --- a/app/src/modules/builder/groups/zoneGroup.tsx +++ b/app/src/modules/builder/groups/zoneGroup.tsx @@ -50,39 +50,26 @@ const ZoneGroup: React.FC = () => { new THREE.ShaderMaterial({ side: THREE.DoubleSide, vertexShader: ` - varying vec2 vUv; - void main() { - vUv = uv; + varying vec2 vUv; + void main(){ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); - } + vUv = uv; + } `, fragmentShader: ` - varying vec2 vUv; - uniform vec3 uColor; - - // Simple function for stripes - float stripePattern(vec2 uv, float thickness) { - return step(0.5, abs(sin((uv.x + uv.y) * 40.0)) - thickness); - } - - void main() { + varying vec2 vUv; + uniform vec3 uColor; + void main(){ float alpha = 1.0 - vUv.y; - - // Stripe pattern - float stripes = stripePattern(vUv, 0.3); - vec3 color = uColor * 1.5; // brighten the color - - gl_FragColor = vec4(color, alpha * stripes); - } + gl_FragColor = vec4(uColor, alpha); + } `, uniforms: { uColor: { value: new THREE.Color(CONSTANTS.zoneConfig.color) }, }, transparent: true, depthWrite: false, - }), - [] - ); + }), []); useEffect(() => { const fetchZones = async () => { 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..c1063c3 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,35 +297,87 @@ 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; } } if (!isLastPoint) { const nextPoint = path[nextPointIdx]; - const distance = - path[stateRef.currentIndex].distanceTo(nextPoint); + const distance = path[stateRef.currentIndex].distanceTo(nextPoint); const effectiveSpeed = stateRef.speed * speedRef.current; const movement = effectiveSpeed * delta; 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([]); diff --git a/app/src/modules/visualization/RealTimeVisulization.tsx b/app/src/modules/visualization/RealTimeVisulization.tsx index 0eca831..6a5145f 100644 --- a/app/src/modules/visualization/RealTimeVisulization.tsx +++ b/app/src/modules/visualization/RealTimeVisulization.tsx @@ -102,6 +102,7 @@ const RealTimeVisulization: React.FC = () => { const organization = email?.split("@")[1]?.split(".")[0]; try { const response = await getZone2dData(organization); + // console.log('response: ', response); if (!Array.isArray(response)) { return; @@ -319,7 +320,6 @@ const RealTimeVisulization: React.FC = () => { "Horizontal Move", "RotateX", "RotateY", - "RotateZ", "Delete", ]} /> diff --git a/app/src/modules/visualization/template/Templates.tsx b/app/src/modules/visualization/template/Templates.tsx index a23affc..8370112 100644 --- a/app/src/modules/visualization/template/Templates.tsx +++ b/app/src/modules/visualization/template/Templates.tsx @@ -1,11 +1,13 @@ import { useEffect } from "react"; -import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore"; import useTemplateStore from "../../../store/useTemplateStore"; import { useSelectedZoneStore } from "../../../store/useZoneStore"; -import { getTemplateData } from "../../../services/realTimeVisulization/zoneData/getTemplate"; import { useSocketStore } from "../../../store/store"; +import { getTemplateData } from "../../../services/realTimeVisulization/zoneData/getTemplate"; +import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore"; import RenameInput from "../../../components/ui/inputs/RenameInput"; + + const Templates = () => { const { templates, removeTemplate, setTemplates } = useTemplateStore(); const { setSelectedZone, selectedZone } = useSelectedZoneStore(); @@ -90,15 +92,15 @@ const Templates = () => { }; return ( -
+
{templates.map((template, index) => (
handleLoadTemplate(template)} + > {template?.snapshot && ( -
+
handleLoadTemplate(template)}> {`${template.name}({}); + const { measurements, duration, name } = useChartStore(); + const { isPlaying } = usePlayButtonStore(); + + const [canvasDimensions, setCanvasDimensions] = useState({ + width: 0, + height: 0, + }); + useEffect(() => { + console.log("changes loggggg", measurements, duration, name); + }, [measurements, duration, name]) const handlePointerDown = () => { if (selectedChartId?.id !== widget.id) { setSelectedChartId(widget); @@ -128,40 +139,50 @@ export const DraggableWidget = ({ } }; + // Calculate panel size + const panelSize = Math.max( + Math.min(canvasDimensions.width * 0.25, canvasDimensions.height * 0.25), + 170 // Min 170px + ); + const getCurrentWidgetCount = (panel: Side) => selectedZone.widgets.filter((w) => w.panel === panel).length; - + // Calculate panel capacity + const calculatePanelCapacity = (panel: Side) => { - const CHART_WIDTH = 150; - const CHART_HEIGHT = 150; - const FALLBACK_HORIZONTAL_CAPACITY = 5; - const FALLBACK_VERTICAL_CAPACITY = 3; + const CHART_WIDTH = panelSize; + const CHART_HEIGHT = panelSize; const dimensions = panelDimensions[panel]; if (!dimensions) { - return panel === "top" || panel === "bottom" - ? FALLBACK_HORIZONTAL_CAPACITY - : FALLBACK_VERTICAL_CAPACITY; + return panel === "top" || panel === "bottom" ? 5 : 3; // Fallback capacities } return panel === "top" || panel === "bottom" - ? Math.floor(dimensions.width / CHART_WIDTH) - : Math.floor(dimensions.height / CHART_HEIGHT); + ? Math.max(1, Math.floor(dimensions.width / CHART_WIDTH)) + : Math.max(1, Math.floor(dimensions.height / CHART_HEIGHT)); }; const isPanelFull = (panel: Side) => { const currentWidgetCount = getCurrentWidgetCount(panel); const panelCapacity = calculatePanelCapacity(panel); - return currentWidgetCount >= panelCapacity; + + return currentWidgetCount > panelCapacity; }; const duplicateWidget = async () => { try { const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; + console.log("widget data sent", widget); + const duplicatedWidget: Widget = { ...widget, + Data: { + duration: duration, + measurements: { ...measurements } + }, id: `${widget.id}-copy-${Date.now()}`, }; console.log("duplicatedWidget: ", duplicatedWidget); @@ -172,6 +193,8 @@ export const DraggableWidget = ({ widget: duplicatedWidget, }; if (visualizationSocket) { + console.log("duplecate widget", duplicateWidget); + visualizationSocket.emit("v2:viz-widget:add", duplicateWidget); } setSelectedZone((prevZone: any) => ({ @@ -244,12 +267,7 @@ export const DraggableWidget = ({ // useClickOutside(chartWidget, () => { // setSelectedChartId(null); // }); - const { isPlaying } = usePlayButtonStore(); - const [canvasDimensions, setCanvasDimensions] = useState({ - width: 0, - height: 0, - }); // Track canvas dimensions // Current: Two identical useEffect hooks for canvas dimensions @@ -287,9 +305,8 @@ export const DraggableWidget = ({
diff --git a/app/src/modules/visualization/widgets/2d/charts/DoughnutGraphComponent.tsx b/app/src/modules/visualization/widgets/2d/charts/DoughnutGraphComponent.tsx index 25b2e8c..4947b94 100644 --- a/app/src/modules/visualization/widgets/2d/charts/DoughnutGraphComponent.tsx +++ b/app/src/modules/visualization/widgets/2d/charts/DoughnutGraphComponent.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from "react"; -import { Doughnut } from "react-chartjs-2"; +import { Line } from "react-chartjs-2"; import io from "socket.io-client"; import axios from "axios"; @@ -16,7 +16,7 @@ interface ChartComponentProps { fontWeight?: "Light" | "Regular" | "Bold"; } -const DoughnutGraphComponent = ({ +const LineGraphComponent = ({ id, type, title, @@ -52,7 +52,7 @@ const DoughnutGraphComponent = ({ }; useEffect(() => { - + },[]) // Memoize Theme Colors @@ -97,11 +97,11 @@ const DoughnutGraphComponent = ({ }, }, scales: { - // x: { - // ticks: { - // display: true, // This hides the x-axis labels - // }, - // }, + x: { + ticks: { + display: true, // This hides the x-axis labels + }, + }, }, }), [title, chartFontStyle, name] @@ -161,6 +161,8 @@ const DoughnutGraphComponent = ({ try { const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`); if (response.status === 200) { + console.log('line chart res',response); + setmeasurements(response.data.Data.measurements) setDuration(response.data.Data.duration) setName(response.data.widgetName) @@ -184,7 +186,7 @@ const DoughnutGraphComponent = ({ } ,[chartMeasurements, chartDuration, widgetName]) - return 0 ? chartData : defaultData} options={options} />; + return 0 ? chartData : defaultData} options={options} />; }; -export default DoughnutGraphComponent; \ No newline at end of file +export default LineGraphComponent; diff --git a/app/src/modules/visualization/widgets/3d/Dropped3dWidget.tsx b/app/src/modules/visualization/widgets/3d/Dropped3dWidget.tsx index 5172d03..6acc994 100644 --- a/app/src/modules/visualization/widgets/3d/Dropped3dWidget.tsx +++ b/app/src/modules/visualization/widgets/3d/Dropped3dWidget.tsx @@ -13,6 +13,8 @@ import ProductionCapacity from "./cards/ProductionCapacity"; import ReturnOfInvestment from "./cards/ReturnOfInvestment"; import StateWorking from "./cards/StateWorking"; import Throughput from "./cards/Throughput"; +import { useWidgetStore } from "../../../../store/useWidgetStore"; +import useChartStore from "../../../../store/useChartStore"; @@ -45,10 +47,14 @@ export default function Dropped3dWidgets() { const planeIntersect = useRef(new THREE.Vector3()); const rotationStartRef = useRef<[number, number, number]>([0, 0, 0]); const mouseStartRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 }); + const { setSelectedChartId } = useWidgetStore(); + const { measurements, duration} = useChartStore(); let [floorPlanesVertical, setFloorPlanesVertical] = useState( new THREE.Plane(new THREE.Vector3(0, 1, 0)) ); - + const [intersectcontextmenu, setintersectcontextmenu] = useState(); + const [horizontalX, setHorizontalX] = useState(); + const [horizontalZ, setHorizontalZ] = useState(); const activeZoneWidgets = zoneWidgetData[selectedZone.zoneId] || []; useEffect(() => { @@ -165,26 +171,30 @@ export default function Dropped3dWidgets() { }; const onDrop = (event: any) => { - event.preventDefault(); event.stopPropagation(); - + hasEntered.current = false; - + const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; - + const newWidget = createdWidgetRef.current; if (!newWidget || !widgetSelect.startsWith("ui")) return; - + // ✅ Extract 2D drop position - const [x, , z] = newWidget.position; - + let [x, y, z] = newWidget.position; + + // ✅ Clamp Y to at least 0 + y = Math.max(y, 0); + newWidget.position = [x, y, z]; + // ✅ Prepare polygon from selectedZone.points const points3D = selectedZone.points as Array<[number, number, number]>; const zonePolygonXZ = points3D.map(([x, , z]) => [x, z] as [number, number]); - + const isInside = isPointInPolygon([x, z], zonePolygonXZ); + // ✅ Remove temp widget const prevWidgets = useZoneWidgetStore.getState().zoneWidgetData[selectedZone.zoneId] || []; const cleanedWidgets = prevWidgets.filter(w => w.id !== newWidget.id); @@ -194,26 +204,29 @@ export default function Dropped3dWidgets() { [selectedZone.zoneId]: cleanedWidgets, }, })); + + // (Optional) Prevent adding if dropped outside zone // if (!isInside) { - // createdWidgetRef.current = null; - // return; // Stop here + // return; // } - // ✅ Add widget if inside polygon + + // ✅ Add widget addWidget(selectedZone.zoneId, newWidget); - + const add3dWidget = { organization, widget: newWidget, zoneId: selectedZone.zoneId, }; - + if (visualizationSocket) { visualizationSocket.emit("v2:viz-3D-widget:add", add3dWidget); } - + createdWidgetRef.current = null; }; + canvasElement.addEventListener("dragenter", handleDragEnter); @@ -237,8 +250,10 @@ export default function Dropped3dWidgets() { const widgetToDuplicate = activeZoneWidgets.find( (w: WidgetData) => w.id === rightClickSelected ); + console.log("3d widget to duplecate", widgetToDuplicate); + if (!widgetToDuplicate) return; - const newWidget: WidgetData = { + const newWidget: any = { id: generateUniqueId(), type: widgetToDuplicate.type, position: [ @@ -247,6 +262,10 @@ export default function Dropped3dWidgets() { widgetToDuplicate.position[2] + 0.5, ], rotation: widgetToDuplicate.rotation || [0, 0, 0], + Data:{ + measurements: measurements, + duration: duration + }, }; const adding3dWidget = { organization: organization, @@ -319,28 +338,40 @@ export default function Dropped3dWidgets() { return inside; } const [prevX, setPrevX] = useState(0); - + useEffect(() => { const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; const handleMouseDown = (event: MouseEvent) => { if (!rightClickSelected || !rightSelect) return; + const selectedZoneId = Object.keys(zoneWidgetData).find( + (zoneId: string) => + zoneWidgetData[zoneId].some( + (widget: WidgetData) => widget.id === rightClickSelected + ) + ); + if (!selectedZoneId) return; + const selectedWidget = zoneWidgetData[selectedZoneId].find( + (widget: WidgetData) => widget.id === rightClickSelected + ); + if (!selectedWidget) return + // let points = []; + // points.push(new THREE.Vector3(0, 0, 0)); + // points.push(new THREE.Vector3(0, selectedWidget.position[1], 0)); + // const newgeometry = new THREE.BufferGeometry().setFromPoints(points); + // let vector = new THREE.Vector3(); + // camera.getWorldDirection(vector); + // let cameraDirection = vector; + // let newPlane = new THREE.Plane(cameraDirection); + // floorPlanesVertical = newPlane; + // setFloorPlanesVertical(newPlane); + // const intersect1 = raycaster?.ray?.intersectPlane( + // floorPlanesVertical, + // planeIntersect.current + // ); + + // setintersectcontextmenu(intersect1.y); - const cameraDirection = new THREE.Vector3(); - camera.getWorldDirection(cameraDirection); - - // Plane normal should be perpendicular to screen (XZ move), so use screen right direction - const right = new THREE.Vector3(); - camera.getWorldDirection(cameraDirection); - cameraDirection.y = 0; - cameraDirection.normalize(); - - right.crossVectors(new THREE.Vector3(0, 1, 0), cameraDirection).normalize(); - - // Create a plane that allows vertical movement - const verticalPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(right, new THREE.Vector3(0, 0, 0)); - - setFloorPlanesVertical(verticalPlane); if (rightSelect === "RotateX" || rightSelect === "RotateY") { mouseStartRef.current = { x: event.clientX, y: event.clientY }; @@ -358,6 +389,7 @@ export default function Dropped3dWidgets() { rotationStartRef.current = selectedWidget.rotation || [0, 0, 0]; } } + }; const handleMouseMove = (event: MouseEvent) => { @@ -380,90 +412,110 @@ export default function Dropped3dWidgets() { raycaster.setFromCamera(mouse, camera); - if (rightSelect === "Horizontal Move" &&raycaster.ray.intersectPlane(plane.current, planeIntersect.current)) { - const points3D = selectedZone.points as Array<[number, number, number]>; - const zonePolygonXZ = points3D.map(([x, , z]) => [x, z] as [number, number]); - const newPosition: [number, number, number] = [ - planeIntersect.current.x, - selectedWidget.position[1], - planeIntersect.current.z, - ]; - const isInside = isPointInPolygon( - [newPosition[0], newPosition[2]], - zonePolygonXZ - ); - // if (isInside) { + if (rightSelect === "Horizontal Move") { + const intersect = raycaster.ray.intersectPlane(plane.current, planeIntersect.current); + if ( + intersect && + typeof horizontalX === "number" && + typeof horizontalZ === "number" + ) { + const selectedZoneId = Object.keys(zoneWidgetData).find(zoneId => + zoneWidgetData[zoneId].some(widget => widget.id === rightClickSelected) + ); + if (!selectedZoneId) return; + + const selectedWidget = zoneWidgetData[selectedZoneId].find(widget => widget.id === rightClickSelected); + if (!selectedWidget) return; + + const newPosition: [number, number, number] = [ + intersect.x + horizontalX, + selectedWidget.position[1], + intersect.z + horizontalZ, + ]; + + updateWidgetPosition(selectedZoneId, rightClickSelected, newPosition); - // } + } } + if (rightSelect === "Vertical Move") { - if (raycaster.ray.intersectPlane(floorPlanesVertical, planeIntersect.current)) { - const currentY = selectedWidget.position[1]; - const newY = planeIntersect.current.y; - console.log('planeIntersect.current: ', planeIntersect.current); - - const deltaY = newY - currentY; - - // Reject if jump is too large (safety check) - if (Math.abs(deltaY) > 200) return; - - // Clamp jump or apply smoothing - const clampedY = currentY + THREE.MathUtils.clamp(deltaY, -10, 10); - - if (clampedY > 0) { - updateWidgetPosition(selectedZoneId, rightClickSelected, [ - selectedWidget.position[0], - clampedY, - selectedWidget.position[2], - ]); - } + const intersect = raycaster.ray.intersectPlane(floorPlanesVertical, planeIntersect.current); + + if (intersect && typeof intersectcontextmenu === "number") { + const diff = intersect.y - intersectcontextmenu; + const unclampedY = selectedWidget.position[1] + diff; + const newY = Math.max(0, unclampedY); // Prevent going below floor (y=0) + + setintersectcontextmenu(intersect.y); + + const newPosition: [number, number, number] = [ + selectedWidget.position[0], + newY, + selectedWidget.position[2], + ]; + + updateWidgetPosition(selectedZoneId, rightClickSelected, newPosition); } } + + if (rightSelect?.startsWith("Rotate")) { + const axis = rightSelect.slice(-1).toLowerCase(); // "x", "y", or "z" + const currentX = event.pageX; + const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0; + setPrevX(currentX); + if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) { + const index = axis === "x" ? 0 : axis === "y" ? 1 : 2; + const currentRotation = selectedWidget.rotation as [number, number, number]; // assert type + const newRotation: [number, number, number] = [...currentRotation]; + newRotation[index] += 0.05 * sign; + updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation); + } + } + // if (rightSelect === "RotateX") { + // + // const currentX = event.pageX; + // const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0; + // + // setPrevX(currentX); + // if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) { + // + // const newRotation: [number, number, number] = [ + // selectedWidget.rotation[0] + 0.05 * sign, + // selectedWidget.rotation[1], + // selectedWidget.rotation[2], + // ]; + // updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation); + // } + // } + // if (rightSelect === "RotateY") { + // const currentX = event.pageX; + // const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0; + // setPrevX(currentX); + // if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) { + // const newRotation: [number, number, number] = [ + // selectedWidget.rotation[0], + // selectedWidget.rotation[1] + 0.05 * sign, + // selectedWidget.rotation[2], + // ]; - if (rightSelect === "RotateX") { - const currentX = event.pageX; - const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0; - setPrevX(currentX); - if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) { - const newRotation: [number, number, number] = [ - selectedWidget.rotation[0] + 0.05 * sign, - selectedWidget.rotation[1], - selectedWidget.rotation[2], - ]; - - updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation); - } - } + // updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation); + // } + // } + // if (rightSelect === "RotateZ") { + // const currentX = event.pageX; + // const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0; + // setPrevX(currentX); + // if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) { + // const newRotation: [number, number, number] = [ + // selectedWidget.rotation[0], + // selectedWidget.rotation[1], + // selectedWidget.rotation[2] + 0.05 * sign, + // ]; - if (rightSelect === "RotateY") { - const currentX = event.pageX; - const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0; - setPrevX(currentX); - if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) { - const newRotation: [number, number, number] = [ - selectedWidget.rotation[0] , - selectedWidget.rotation[1]+ 0.05 * sign, - selectedWidget.rotation[2], - ]; - - updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation); - } - } - if (rightSelect === "RotateZ") { - const currentX = event.pageX; - const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0; - setPrevX(currentX); - if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) { - const newRotation: [number, number, number] = [ - selectedWidget.rotation[0] , - selectedWidget.rotation[1], - selectedWidget.rotation[2]+ 0.05 * sign, - ]; - - updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation); - } - } + // updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation); + // } + // } }; const handleMouseUp = () => { if (!rightClickSelected || !rightSelect) return; @@ -513,7 +565,7 @@ export default function Dropped3dWidgets() { const rotation = selectedWidget.rotation || [0, 0, 0]; let lastRotation = formatValues(rotation) as [number, number, number]; - + // (async () => { // let response = await update3dWidgetRotation(selectedZoneId, organization, rightClickSelected, lastRotation); // @@ -552,11 +604,60 @@ export default function Dropped3dWidgets() { }; }, [rightClickSelected, rightSelect, zoneWidgetData, gl]); + const handleRightClick3d = (event: React.MouseEvent, id: string) => { + event.preventDefault(); + + const canvasElement = document.getElementById("real-time-vis-canvas"); + if (!canvasElement) throw new Error("Canvas element not found"); + + const canvasRect = canvasElement.getBoundingClientRect(); + const relativeX = event.clientX - canvasRect.left; + const relativeY = event.clientY - canvasRect.top; + + setEditWidgetOptions(true); + setRightClickSelected(id); + setTop(relativeY); + setLeft(relativeX); + + const selectedZoneId = Object.keys(zoneWidgetData).find(zoneId => + zoneWidgetData[zoneId].some(widget => widget.id === id) + ); + if (!selectedZoneId) return; + + const selectedWidget = zoneWidgetData[selectedZoneId].find(widget => widget.id === id); + if (!selectedWidget) return; + + const { top, left, width, height } = canvasElement.getBoundingClientRect(); + mouse.x = ((event.clientX - left) / width) * 2 - 1; + mouse.y = -((event.clientY - top) / height) * 2 + 1; + + raycaster.setFromCamera(mouse, camera); + + const cameraDirection = new THREE.Vector3(); + camera.getWorldDirection(cameraDirection); + const verticalPlane = new THREE.Plane(cameraDirection); + setFloorPlanesVertical(verticalPlane); + + const intersectPoint = raycaster.ray.intersectPlane(verticalPlane, planeIntersect.current); + if (intersectPoint) { + setintersectcontextmenu(intersectPoint.y); + } + const intersect2 = raycaster.ray.intersectPlane(plane.current, planeIntersect.current); + if (intersect2) { + const xDiff = -intersect2.x + selectedWidget.position[0]; + const zDiff = -intersect2.z + selectedWidget.position[2]; + setHorizontalX(xDiff); + setHorizontalZ(zDiff); + } + }; + + return ( <> {activeZoneWidgets.map( - ({ id, type, position, rotation = [0, 0, 0] }: WidgetData) => { + ({ id, type, position, Data, rotation = [0, 0, 0] }: any) => { const handleRightClick = (event: React.MouseEvent, id: string) => { + setSelectedChartId({ id: id, type: type }) event.preventDefault(); const canvasElement = document.getElementById( "real-time-vis-canvas" @@ -569,6 +670,7 @@ export default function Dropped3dWidgets() { setRightClickSelected(id); setTop(relativeY); setLeft(relativeX); + handleRightClick3d(event, id) }; switch (type) { @@ -580,6 +682,7 @@ export default function Dropped3dWidgets() { type={type} position={position} rotation={rotation} + Data={Data} onContextMenu={(e) => handleRightClick(e, id)} /> ); @@ -591,6 +694,7 @@ export default function Dropped3dWidgets() { type={type} position={position} rotation={rotation} + Data={Data} onContextMenu={(e) => handleRightClick(e, id)} /> ); @@ -602,6 +706,7 @@ export default function Dropped3dWidgets() { type={type} position={position} rotation={rotation} + Data={Data} onContextMenu={(e) => handleRightClick(e, id)} /> ); @@ -613,6 +718,7 @@ export default function Dropped3dWidgets() { type={type} position={position} rotation={rotation} + Data={Data} onContextMenu={(e) => handleRightClick(e, id)} /> ); diff --git a/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx b/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx index 9387bd2..274d6dd 100644 --- a/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx @@ -31,6 +31,7 @@ interface ProductionCapacityProps { type: string; position: [number, number, number]; rotation: [number, number, number]; + Data?: any, onContextMenu?: (event: React.MouseEvent) => void; // onPointerDown:any } @@ -38,6 +39,7 @@ interface ProductionCapacityProps { const ProductionCapacity: React.FC = ({ id, type, + Data, position, rotation, onContextMenu, @@ -48,8 +50,8 @@ const ProductionCapacity: React.FC = ({ duration: chartDuration, name: widgetName, } = useChartStore(); - const [measurements, setmeasurements] = useState({}); - const [duration, setDuration] = useState("1h"); + const [measurements, setmeasurements] = useState(Data?.measurements ? Data.measurements : {}); + const [duration, setDuration] = useState(Data?.duration ? Data.duration : "1h"); const [name, setName] = useState("Widget"); const [chartData, setChartData] = useState<{ labels: string[]; @@ -173,7 +175,7 @@ const ProductionCapacity: React.FC = ({ setName(response.data.widgetName); } else { } - } catch (error) {} + } catch (error) { } } }; @@ -187,9 +189,12 @@ const ProductionCapacity: React.FC = ({ } }, [chartMeasurements, chartDuration, widgetName]); - useEffect(() => {}, [rotation]); + useEffect(() => { }, [rotation]); + + return ( + = ({ // e.stopPropagation(); }} wrapperClass="pointer-none" + >
setSelectedChartId({ id: id, type: type })} onContextMenu={onContextMenu} + style={{ width: "300px", // Original width height: "300px", // Original height // transform: transformStyle.transform, transformStyle: "preserve-3d", position: "absolute", - transform: "translate(-50%, -50%)", + transform:'translate(-50%, -50%)', }} >
@@ -258,6 +263,7 @@ const ProductionCapacity: React.FC = ({
+ ); }; diff --git a/app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx b/app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx index 80ed1f0..a4c04ca 100644 --- a/app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx @@ -12,6 +12,7 @@ import { ChartOptions, } from "chart.js"; + import axios from "axios"; import io from "socket.io-client"; import { useWidgetStore } from "../../../../../store/useWidgetStore"; @@ -45,11 +46,13 @@ interface ReturnOfInvestmentProps { type: string; position: [number, number, number]; rotation: [number, number, number]; + Data?: any; onContextMenu?: (event: React.MouseEvent) => void; } const ReturnOfInvestment: React.FC = ({ id, type, + Data, position, rotation, onContextMenu, @@ -60,8 +63,8 @@ const ReturnOfInvestment: React.FC = ({ duration: chartDuration, name: widgetName, } = useChartStore(); - const [measurements, setmeasurements] = useState({}); - const [duration, setDuration] = useState("1h"); + const [measurements, setmeasurements] = useState(Data?.measurements ? Data.measurements : {}); + const [duration, setDuration] = useState(Data?.duration ? Data.duration : "1h"); const [name, setName] = useState("Widget"); const [chartData, setChartData] = useState<{ labels: string[]; diff --git a/app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx b/app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx index cc7dba1..4d10955 100644 --- a/app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx @@ -12,11 +12,13 @@ interface StateWorkingProps { type: string; position: [number, number, number]; rotation: [number, number, number]; + Data?:any; onContextMenu?: (event: React.MouseEvent) => void; } const StateWorking: React.FC = ({ id, type, + Data, position, rotation, onContextMenu, @@ -27,8 +29,8 @@ const StateWorking: React.FC = ({ duration: chartDuration, name: widgetName, } = useChartStore(); - const [measurements, setmeasurements] = useState({}); - const [duration, setDuration] = useState("1h"); + const [measurements, setmeasurements] = useState(Data?.measurements ? Data.measurements : {}); + const [duration, setDuration] = useState(Data?.duration ? Data.duration : "1h"); const [name, setName] = useState("Widget"); const [datas, setDatas] = useState({}); const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL; @@ -114,7 +116,7 @@ const StateWorking: React.FC = ({ scale={[0.5, 0.5, 0.5]} transform zIndexRange={[1, 0]} - sprite={false} + sprite={false} // style={{ // transform: transformStyle.transform, // transformStyle: "preserve-3d", diff --git a/app/src/modules/visualization/widgets/3d/cards/Throughput.tsx b/app/src/modules/visualization/widgets/3d/cards/Throughput.tsx index 13847bc..67397bd 100644 --- a/app/src/modules/visualization/widgets/3d/cards/Throughput.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/Throughput.tsx @@ -14,6 +14,7 @@ import { ChartOptions, } from "chart.js"; + import axios from "axios"; import io from "socket.io-client"; import { useWidgetStore } from "../../../../../store/useWidgetStore"; @@ -47,12 +48,14 @@ interface ThroughputProps { type: string; position: [number, number, number]; rotation: [number, number, number]; + Data?:any; onContextMenu?: (event: React.MouseEvent) => void; } const Throughput: React.FC = ({ id, type, + Data, position, rotation, onContextMenu, @@ -63,8 +66,8 @@ const Throughput: React.FC = ({ duration: chartDuration, name: widgetName, } = useChartStore(); - const [measurements, setmeasurements] = useState({}); - const [duration, setDuration] = useState("1h"); + const [measurements, setmeasurements] = useState(Data?.measurements ? Data.measurements : {}); + const [duration, setDuration] = useState(Data?.duration ? Data.duration : "1h"); const [name, setName] = useState("Widget"); const [chartData, setChartData] = useState<{ labels: string[]; diff --git a/app/src/modules/visualization/widgets/floating/cards/FleetEfficiencyComponent.tsx b/app/src/modules/visualization/widgets/floating/cards/FleetEfficiencyComponent.tsx index 75276c1..6799dcb 100644 --- a/app/src/modules/visualization/widgets/floating/cards/FleetEfficiencyComponent.tsx +++ b/app/src/modules/visualization/widgets/floating/cards/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/visualization/widgets/panel/Panel.tsx b/app/src/modules/visualization/widgets/panel/Panel.tsx index 1c783f8..f53b0ea 100644 --- a/app/src/modules/visualization/widgets/panel/Panel.tsx +++ b/app/src/modules/visualization/widgets/panel/Panel.tsx @@ -108,9 +108,8 @@ const Panel: React.FC = ({ case "bottom": return { minWidth: "170px", - width: `calc(100% - ${ - (leftActive ? panelSize : 0) + (rightActive ? panelSize : 0) - }px)`, + width: `calc(100% - ${(leftActive ? panelSize : 0) + (rightActive ? panelSize : 0) + }px)`, minHeight: "170px", height: `${panelSize}px`, left: leftActive ? `${panelSize}px` : "0", @@ -123,9 +122,8 @@ const Panel: React.FC = ({ minWidth: "170px", width: `${panelSize}px`, minHeight: "170px", - height: `calc(100% - ${ - (topActive ? panelSize : 0) + (bottomActive ? panelSize : 0) - }px)`, + height: `calc(100% - ${(topActive ? panelSize : 0) + (bottomActive ? panelSize : 0) + }px)`, top: topActive ? `${panelSize}px` : "0", bottom: bottomActive ? `${panelSize}px` : "0", [side]: "0", @@ -151,6 +149,7 @@ const Panel: React.FC = ({ const currentWidgetsCount = getCurrentWidgetCount(panel); const maxCapacity = calculatePanelCapacity(panel); + if (currentWidgetsCount < maxCapacity) { addWidgetToPanel(draggedAsset, panel); } @@ -284,9 +283,8 @@ const Panel: React.FC = ({
handleDrop(e, side)} onDragOver={(e) => e.preventDefault()} @@ -303,7 +301,7 @@ const Panel: React.FC = ({ style={{ pointerEvents: selectedZone.lockedPanels.includes(side) || - hiddenPanels[selectedZone.zoneId]?.includes(side) + hiddenPanels[selectedZone.zoneId]?.includes(side) ? "none" : "auto", opacity: selectedZone.lockedPanels.includes(side) ? "0.8" : "1", diff --git a/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts b/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts index 9b03dbe..beaa1b3 100644 --- a/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts +++ b/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts @@ -8,7 +8,7 @@ export const addingFloatingWidgets = async ( ) => { - + try { const response = await fetch( `${url_Backend_dwinzo}/api/v2/floatwidget/save`, diff --git a/app/src/store/useDroppedObjectsStore.ts b/app/src/store/useDroppedObjectsStore.ts index 8f0067e..ca39d9f 100644 --- a/app/src/store/useDroppedObjectsStore.ts +++ b/app/src/store/useDroppedObjectsStore.ts @@ -1,6 +1,7 @@ import { create } from "zustand"; import { addingFloatingWidgets } from "../services/realTimeVisulization/zoneData/addFloatingWidgets"; import { useSocketStore } from "./store"; +import useChartStore from "./useChartStore"; type DroppedObject = { className: string; @@ -96,7 +97,10 @@ export const useDroppedObjectsStore = create((set) => ({ const state = useDroppedObjectsStore.getState(); // Get the current state const zone = state.zones[zoneName]; let socketState = useSocketStore.getState(); + let iotData = useChartStore.getState(); let visualizationSocket = socketState.visualizationSocket; + let iotMeasurements = iotData.flotingMeasurements; + let iotDuration = iotData.flotingDuration; if (!zone) return; @@ -109,6 +113,10 @@ export const useDroppedObjectsStore = create((set) => ({ // Create a shallow copy of the object with a unique ID and slightly adjusted position const duplicatedObject: DroppedObject = { ...originalObject, + Data:{ + measurements: iotMeasurements, + duration: iotDuration, + }, id: `${originalObject.id}-copy-${Date.now()}`, // Unique ID position: { ...originalObject.position, @@ -122,7 +130,8 @@ export const useDroppedObjectsStore = create((set) => ({ : originalObject.position.left, }, }; - + console.log("duplicated object",duplicatedObject); + let duplicateFloatingWidget = { organization: organization, widget: duplicatedObject, diff --git a/app/src/store/useWidgetStore.ts b/app/src/store/useWidgetStore.ts index 047b57b..2d73c77 100644 --- a/app/src/store/useWidgetStore.ts +++ b/app/src/store/useWidgetStore.ts @@ -8,22 +8,8 @@ export interface Widget { fontFamily?: string; fontSize?: string; fontWeight?: string; - data: { - // Chart data - labels?: string[]; - datasets?: Array<{ - data: number[]; - backgroundColor: string; - borderColor: string; - borderWidth: number; - }>; - // Progress card data - stocks?: Array<{ - key: string; - value: number; - description: string; - }>; - }; + data?: any; + Data?:any; } interface WidgetStore { diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index c6f980a..0d4f98d 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -285,9 +285,6 @@ } } - .chart-container.notLinked { - border-color: red; - } .close-btn { position: absolute; @@ -755,6 +752,13 @@ z-index: 2 !important; } +.chart-container.notLinked { + + outline: 1px solid red; + + +} + .connectionSuccess { outline-color: #43c06d; }