diff --git a/app/package-lock.json b/app/package-lock.json index a820896..8be748e 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -29,6 +29,7 @@ "chartjs-plugin-annotation": "^3.1.0", "glob": "^11.0.0", "gsap": "^3.12.5", + "html2canvas": "^1.4.1", "leva": "^0.10.0", "mqtt": "^5.10.4", "postprocessing": "^6.36.4", @@ -2019,7 +2020,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -2031,7 +2032,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -4134,26 +4135,6 @@ "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", @@ -4265,25 +4246,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==", - "devOptional": true + "dev": 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==", - "devOptional": true + "dev": 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==", - "devOptional": true + "dev": 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==", - "devOptional": true + "dev": true }, "node_modules/@turf/along": { "version": "7.2.0", @@ -8047,6 +8028,15 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -9017,7 +9007,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==", - "devOptional": true + "dev": true }, "node_modules/cross-env": { "version": "7.0.3", @@ -9102,6 +9092,15 @@ "postcss": "^8.4" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-loader": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", @@ -9885,7 +9884,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.3.1" } @@ -12489,6 +12488,19 @@ } } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -15235,7 +15247,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true + "dev": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -20434,6 +20446,15 @@ "node": "*" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -20694,7 +20715,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "devOptional": true, + "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20737,7 +20758,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "devOptional": true, + "dev": true, "dependencies": { "acorn": "^8.11.0" }, @@ -20749,7 +20770,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true + "dev": true }, "node_modules/tsconfig-paths": { "version": "3.15.0", @@ -21220,6 +21241,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -21236,7 +21266,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==", - "devOptional": true + "dev": true }, "node_modules/v8-to-istanbul": { "version": "8.1.1", @@ -22295,7 +22325,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } diff --git a/app/package.json b/app/package.json index 66e3b39..ce5c7d3 100644 --- a/app/package.json +++ b/app/package.json @@ -24,6 +24,7 @@ "chartjs-plugin-annotation": "^3.1.0", "glob": "^11.0.0", "gsap": "^3.12.5", + "html2canvas": "^1.4.1", "leva": "^0.10.0", "mqtt": "^5.10.4", "postprocessing": "^6.36.4", diff --git a/app/src/components/icons/RealTimeVisulationIcons.tsx b/app/src/components/icons/RealTimeVisulationIcons.tsx index 1d144d6..84d8cca 100644 --- a/app/src/components/icons/RealTimeVisulationIcons.tsx +++ b/app/src/components/icons/RealTimeVisulationIcons.tsx @@ -8,37 +8,45 @@ export function CleanPannel() { xmlns="http://www.w3.org/2000/svg" > - + - + diff --git a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx index fb7496a..b1aad0b 100644 --- a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx +++ b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx @@ -49,7 +49,7 @@ const SideBarLeft: React.FC = () => { ) : activeModule === "market" ? ( <> - ) : ( + ) : activeModule === "builder" ? ( <> { {activeOption === "Outline" ? : } + ) : ( + <> + +
+ {activeOption === "Outline" ? : } +
+ )} )} diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx index 9f71423..c411ffd 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx @@ -14,7 +14,7 @@ const chartTypes: ChartType[] = [ ]; const sampleData = { - labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"], + labels: ["January", "February", "March", "April", "May", "June", "July"], datasets: [ { data: [65, 59, 80, 81, 56, 55, 40], @@ -102,7 +102,6 @@ const ProgressBarWidget = ({ ); }; - const Widgets2D = () => { return (
diff --git a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx index bd10613..913bdd1 100644 --- a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx @@ -10,6 +10,7 @@ import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; import LabledDropdown from "../../../ui/inputs/LabledDropdown"; import { handleResize } from "../../../../functions/handleResizePannel"; import { + useFloorItems, useSelectedActionSphere, useSelectedPath, useSimulationPaths, @@ -17,11 +18,14 @@ import { import * as THREE from "three"; import * as Types from "../../../../types/world/worldTypes"; import InputToggle from "../../../ui/inputs/InputToggle"; +import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi"; +import { setEventApi } from "../../../../services/factoryBuilder/assest/floorAsset/setEventsApt"; const ConveyorMechanics: React.FC = () => { const { selectedActionSphere } = useSelectedActionSphere(); const { selectedPath, setSelectedPath } = useSelectedPath(); const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { floorItems, setFloorItems } = useFloorItems(); const actionsContainerRef = useRef(null); const triggersContainerRef = useRef(null); @@ -36,6 +40,19 @@ const ConveyorMechanics: React.FC = () => { .find((point) => point.uuid === selectedActionSphere.point.uuid); }, [selectedActionSphere, simulationPaths]); + const updateBackend = async (updatedPath: Types.ConveyorEventsSchema | undefined) => { + if (!updatedPath) return; + // const email = localStorage.getItem("email"); + // const organization = email ? email.split("@")[1].split(".")[0] : ""; + // console.log('updatedPath: ', updatedPath); + // const a = await setEventApi( + // organization, + // updatedPath.modeluuid, + // updatedPath.points + // ); + // console.log('a: ', a); + } + const handleAddAction = () => { if (!selectedActionSphere) return; @@ -65,6 +82,15 @@ const ConveyorMechanics: React.FC = () => { return path; }); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); }; @@ -74,21 +100,30 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - actions: point.actions.filter( - (action) => action.uuid !== uuid - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.filter( + (action) => action.uuid !== uuid + ), + } + : point + ), + } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); }; @@ -98,36 +133,45 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid - ? { - ...action, - type: actionType, - material: - actionType === "Spawn" || actionType === "Swap" - ? "Inherit" - : action.material, - delay: - actionType === "Delay" ? "Inherit" : action.delay, - spawnInterval: - actionType === "Spawn" - ? "Inherit" - : action.spawnInterval, - } - : action - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid + ? { + ...action, + type: actionType, + material: + actionType === "Spawn" || actionType === "Swap" + ? "Inherit" + : action.material, + delay: + actionType === "Delay" ? "Inherit" : action.delay, + spawnInterval: + actionType === "Spawn" + ? "Inherit" + : action.spawnInterval, + } + : action + ), + } + : point + ), + } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); // Update the selected item to reflect changes @@ -156,24 +200,33 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid && - (action.type === "Spawn" || action.type === "Swap") - ? { ...action, material } - : action - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid && + (action.type === "Spawn" || action.type === "Swap") + ? { ...action, material } + : action + ), + } + : point + ), + } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); // Update selected item if it's the current action @@ -194,21 +247,30 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid ? { ...action, delay } : action - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid ? { ...action, delay } : action + ), + } + : point + ), + } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); }; @@ -221,23 +283,32 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid - ? { ...action, spawnInterval } - : action - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid + ? { ...action, spawnInterval } + : action + ), + } + : point + ), + } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); }; @@ -248,6 +319,15 @@ const ConveyorMechanics: React.FC = () => { path.modeluuid === selectedPath.path.modeluuid ? { ...path, speed } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); setSelectedPath({ ...selectedPath, path: { ...selectedPath.path, speed } }); }; @@ -258,26 +338,35 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => { - if (point.uuid === selectedActionSphere.point.uuid) { - const triggerIndex = point.triggers.length; - const newTrigger = { - uuid: THREE.MathUtils.generateUUID(), - name: `Trigger ${triggerIndex + 1}`, - type: "", - bufferTime: 0, - isUsed: false, - }; + ...path, + points: path.points.map((point) => { + if (point.uuid === selectedActionSphere.point.uuid) { + const triggerIndex = point.triggers.length; + const newTrigger = { + uuid: THREE.MathUtils.generateUUID(), + name: `Trigger ${triggerIndex + 1}`, + type: "", + bufferTime: 0, + isUsed: false, + }; - return { ...point, triggers: [...point.triggers, newTrigger] }; - } - return point; - }), - } + return { ...point, triggers: [...point.triggers, newTrigger] }; + } + return point; + }), + } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); }; @@ -287,21 +376,30 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - triggers: point.triggers.filter( - (trigger) => trigger.uuid !== uuid - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + triggers: point.triggers.filter( + (trigger) => trigger.uuid !== uuid + ), + } + : point + ), + } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); }; @@ -311,23 +409,32 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => - trigger.uuid === uuid - ? { ...trigger, type: triggerType } - : trigger - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => + trigger.uuid === uuid + ? { ...trigger, type: triggerType } + : trigger + ), + } + : point + ), + } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); // Ensure the selectedItem is updated immediately @@ -347,22 +454,31 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - actions: point.actions.map((action) => ({ - ...action, - isUsed: action.uuid === uuid ? !action.isUsed : false, - })), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + actions: point.actions.map((action) => ({ + ...action, + isUsed: action.uuid === uuid ? !action.isUsed : false, + })), + } + : point + ), + } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); // Immediately update the selected item if it's the one being toggled @@ -384,22 +500,31 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => ({ - ...trigger, - isUsed: trigger.uuid === uuid ? !trigger.isUsed : false, - })), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => ({ + ...trigger, + isUsed: trigger.uuid === uuid ? !trigger.isUsed : false, + })), + } + : point + ), + } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); // Immediately update the selected item if it's the one being toggled @@ -420,23 +545,32 @@ const ConveyorMechanics: React.FC = () => { const updatedPaths = simulationPaths.map((path) => path.type === "Conveyor" ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => - trigger.uuid === uuid - ? { ...trigger, bufferTime } - : trigger - ), - } - : point - ), - } + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.point.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => + trigger.uuid === uuid + ? { ...trigger, bufferTime } + : trigger + ), + } + : point + ), + } : path ); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.point.uuid + ) + ); + updateBackend(updatedPath); + setSimulationPaths(updatedPaths); // Immediately update selectedItem if it's the currently selected trigger @@ -493,12 +627,11 @@ const ConveyorMechanics: React.FC = () => { {selectedPoint?.actions.map((action) => (
{ setSelectedItem({ type: "action", item: action }) } > - +
{ {selectedPoint?.triggers.map((trigger) => (
{ {selectedItem.type === "action" && ( <> handleActionToggle(selectedItem.item.uuid)} /> @@ -604,19 +736,19 @@ const ConveyorMechanics: React.FC = () => { {/* Only show material dropdown for Spawn/Swap actions */} {(selectedItem.item.type === "Spawn" || selectedItem.item.type === "Swap") && ( - - handleMaterialSelect(selectedItem.item.uuid, option) - } - /> - )} + + handleMaterialSelect(selectedItem.item.uuid, option) + } + /> + )} {/* Only show delay input for Delay actions */} {selectedItem.item.type === "Delay" && ( diff --git a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx index f0908b8..e78b4dd 100644 --- a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx +++ b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx @@ -1,18 +1,21 @@ -import { useState } from "react"; +import { useState, useEffect, useRef } from "react"; import { useWidgetStore } from "../../../../../store/useWidgetStore"; import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent"; import RegularDropDown from "../../../../ui/inputs/RegularDropDown"; +import { WalletIcon } from "../../../../icons/3dChartIcons"; +import SimpleCard from "../../../../ui/realTimeVis/floating/SimpleCard"; -// Define Props Interface interface Widget { id: string; - type: string; // Chart type (e.g., "bar", "line") - panel: "top" | "bottom" | "left" | "right"; // Panel location - title: string; + type?: string; + panel: "top" | "bottom" | "left" | "right"; + title?: string; + header?: string; fontFamily?: string; fontSize?: string; fontWeight?: string; - data: { + className?: string; + data?: { labels: string[]; datasets: { data: number[]; @@ -20,162 +23,263 @@ interface Widget { borderColor: string; borderWidth: number; }[]; - }; // Data for the chart + }; + value?: string; + per?: string; +} + +interface ChartElement { + tagName: string; + className: string; + textContent: string; + selector: string; } const Design = () => { - const [selectedName, setSelectedName] = useState("drop down"); - console.log("selectedName: ", selectedName); - - const [selectedElement, setSelectedElement] = useState("drop down"); - console.log("selectedElement: ", selectedElement); - const [selectedFont, setSelectedFont] = useState("drop down"); - console.log("selectedFont: ", selectedFont); - const [selectedSize, setSelectedSize] = useState("drop down"); - console.log("selectedSize: ", selectedSize); - const [selectedWeight, setSelectedWeight] = useState("drop down"); - console.log("selectedWeight: ", selectedWeight); + const [elementColor, setElementColor] = useState("#6f42c1"); + const [showColorPicker, setShowColorPicker] = useState(false); + const [chartElements, setChartElements] = useState([]); + const [selectedElementToStyle, setSelectedElementToStyle] = useState(null); + const [nameInput, setNameInput] = useState(""); + const chartRef = useRef(null); - const [elementColor, setElementColor] = useState("#6f42c1"); // Default color for elements - const [showColorPicker, setShowColorPicker] = useState(false); // Manage visibility of the color picker + const { selectedChartId, setSelectedChartId, widgets, setWidgets } = useWidgetStore(); - // Zustand Store Hooks - const { selectedChartId, setSelectedChartId, widgets, setWidgets } = - useWidgetStore(); + // Initialize name input and extract elements when selectedChartId changes + useEffect(() => { + setNameInput(selectedChartId?.header || selectedChartId?.title || ""); + + if (!chartRef.current) return; + + const timer = setTimeout(() => { + const chartContainer = chartRef.current; + if (!chartContainer) return; + + const elements = Array.from(chartContainer.querySelectorAll("*")) + .filter((el) => { + const tagName = el.tagName.toLowerCase(); + return !["script", "style", "meta", "link", "head"].includes(tagName); + }) + .map((el, index) => { + const tagName = el.tagName.toLowerCase(); + const className = typeof el.className === "string" ? el.className : ""; + const textContent = el.textContent?.trim() || ""; + + let selector = tagName; + + if (className && typeof className === "string") { + const classList = className.split(/\s+/).filter((c) => c.length > 0); + if (classList.length > 0) { + selector += "." + classList.join("."); + } + } + + if (!className || className.trim() === "") { + const parent = el.parentElement; + if (parent) { + const siblings = Array.from(parent.children).filter( + (child) => child.tagName.toLowerCase() === tagName + ); + const position = siblings.indexOf(el) + 1; + selector += `:nth-of-type(${position})`; + } + } + + return { + tagName, + className, + textContent, + selector, + }; + }); + + setChartElements(elements); + }, 300); + + return () => clearTimeout(timer); + }, [selectedChartId]); + + const applyStyles = () => { + if (!selectedElementToStyle || !chartRef.current) return; + + const element = chartRef.current.querySelector(selectedElementToStyle); + if (!element) return; + + const elementToStyle = element as HTMLElement; + + if (selectedFont !== "drop down") { + elementToStyle.style.fontFamily = selectedFont; + } + if (selectedSize !== "drop down") { + elementToStyle.style.fontSize = selectedSize; + } + if (selectedWeight !== "drop down") { + elementToStyle.style.fontWeight = selectedWeight.toLowerCase(); + } + if (elementColor) { + elementToStyle.style.color = elementColor; + } + }; + + useEffect(() => { + applyStyles(); + }, [selectedFont, selectedSize, selectedWeight, elementColor, selectedElementToStyle]); - // Handle Widget Updates const handleUpdateWidget = (updatedProperties: Partial) => { if (!selectedChartId) return; - // Update the selectedChartId const updatedChartId = { ...selectedChartId, ...updatedProperties, }; setSelectedChartId(updatedChartId); - // Update the widgets array const updatedWidgets = widgets.map((widget) => - widget.id === selectedChartId.id - ? { ...widget, ...updatedProperties } - : widget + widget.id === selectedChartId.id ? { ...widget, ...updatedProperties } : widget ); setWidgets(updatedWidgets); }; - // Default Chart Data + const handleNameChange = (e: React.ChangeEvent) => { + const newName = e.target.value; + setNameInput(newName); + + if (selectedChartId?.title) { + handleUpdateWidget({ title: newName }); + } else if (selectedChartId?.header) { + handleUpdateWidget({ header: newName }); + } + }; + const defaultChartData = { labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"], datasets: [ { data: [65, 59, 80, 81, 56, 55, 40], - backgroundColor: elementColor, // Default background color - borderColor: "#ffffff", // Default border color + backgroundColor: elementColor, + borderColor: "#ffffff", borderWidth: 1, }, ], }; + const elementOptions = chartElements.map((el) => { + let displayName = el.tagName; + if (el.className) displayName += `.${el.className}`; + if (el.textContent) + displayName += ` (${el.textContent.substring(0, 20)}${ + el.textContent.length > 20 ? "..." : "" + })`; + return { + display: displayName, + value: el.selector, + }; + }); + return (
- {/* Title of the Selected Widget */}
- {selectedChartId?.title || "Widget 1"} + {selectedChartId?.title || selectedChartId?.header || "Widget 1"}
- {/* Chart Component */} -
- {selectedChartId && ( +
+ {selectedChartId?.title ? ( + ) : ( + )}
- {/* Options Container */}
- {/* Name Dropdown */} +
+ Element to Style + 0 + ? elementOptions.map((opt) => opt.display) + : ["No elements found"] + } + onSelect={(value) => { + const selected = elementOptions.find( + (opt) => opt.display === value + ); + setSelectedElementToStyle(selected?.value || null); + }} + /> +
+
Name - { - setSelectedName(value); - handleUpdateWidget({ title: value }); - }} +
- {/* Element Dropdown */} -
- Element - { - setSelectedElement(value); - handleUpdateWidget({ type: value }); - }} - /> -
+ {selectedChartId?.title && ( +
+ Chart Type + { + handleUpdateWidget({ type: value }); + }} + /> +
+ )} - {/* Font Family Dropdown */}
Font Family { - setSelectedFont(value); - handleUpdateWidget({ fontFamily: value }); - }} + onSelect={(value) => setSelectedFont(value)} />
- {/* Size Dropdown */}
Size { - setSelectedSize(value); - handleUpdateWidget({ fontSize: value }); - }} + onSelect={(value) => setSelectedSize(value)} />
- {/* Weight Dropdown */}
Weight { - setSelectedWeight(value); - handleUpdateWidget({ fontWeight: value }); - }} + onSelect={(value) => setSelectedWeight(value)} />
- {/* Element Color Picker */}
setShowColorPicker((prev) => !prev)} > Element Color -
{" "} - {/* Change icon based on the visibility */} +
- {/* Show color picker only when 'showColorPicker' is true */} {showColorPicker && (
{ value={elementColor} onChange={(e) => { setElementColor(e.target.value); - handleUpdateWidget({ - data: { - labels: selectedChartId?.data?.labels || [], - datasets: [ - { - ...selectedChartId?.data?.datasets[0], - backgroundColor: e.target.value, // Update the element color - }, - ], - }, - }); + if (selectedChartId?.data) { + handleUpdateWidget({ + data: { + ...selectedChartId.data, + datasets: [ + { + ...selectedChartId.data.datasets[0], + backgroundColor: e.target.value, + }, + ], + }, + }); + } }} /> - {/* Display the selected color value */} {elementColor}
)} @@ -206,4 +311,4 @@ const Design = () => { ); }; -export default Design; +export default Design; \ No newline at end of file diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index 02069d8..3de44bf 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -438,7 +438,6 @@ const Tools: React.FC = () => { }`} onClick={() => { setIsPlaying(!isPlaying); - setActiveTool("play"); }} > diff --git a/app/src/components/ui/componets/Panel.tsx b/app/src/components/ui/componets/Panel.tsx index 7d34c3f..8ec26a0 100644 --- a/app/src/components/ui/componets/Panel.tsx +++ b/app/src/components/ui/componets/Panel.tsx @@ -124,8 +124,8 @@ const Panel: React.FC = ({ selectedZone.widgets.filter((w) => w.panel === panel).length; const calculatePanelCapacity = (panel: Side) => { - const CHART_WIDTH = 150; - const CHART_HEIGHT = 150; + const CHART_WIDTH = 170; + const CHART_HEIGHT = 170; const FALLBACK_HORIZONTAL_CAPACITY = 5; const FALLBACK_VERTICAL_CAPACITY = 3; diff --git a/app/src/components/ui/componets/functions/determinePosition.ts b/app/src/components/ui/componets/functions/determinePosition.ts index 5e3db3f..325ad14 100644 --- a/app/src/components/ui/componets/functions/determinePosition.ts +++ b/app/src/components/ui/componets/functions/determinePosition.ts @@ -1,4 +1,3 @@ - export function determinePosition( canvasRect: DOMRect, relativeX: number, @@ -12,6 +11,23 @@ export function determinePosition( const centerX = canvasRect.width / 2; const centerY = canvasRect.height / 2; + // Define a threshold for considering a point as "centered" + const centerThreshold = 10; // Adjust this value as needed + + // Check if the point is within the center threshold + const isCenterX = Math.abs(relativeX - centerX) <= centerThreshold; + const isCenterY = Math.abs(relativeY - centerY) <= centerThreshold; + + // If the point is centered, return a special "centered" position + if (isCenterX && isCenterY) { + return { + top: "auto", + left: "auto", + right: "auto", + bottom: "auto", + }; + } + let position: { top: number | "auto"; left: number | "auto"; @@ -21,7 +37,7 @@ export function determinePosition( if (relativeY < centerY) { if (relativeX < centerX) { - + // Top-left quadrant position = { top: relativeY - 41.5, left: relativeX - 125, @@ -29,7 +45,7 @@ export function determinePosition( bottom: "auto", }; } else { - + // Top-right quadrant position = { top: relativeY - 41.5, right: canvasRect.width - relativeX - 125, @@ -39,7 +55,7 @@ export function determinePosition( } } else { if (relativeX < centerX) { - + // Bottom-left quadrant position = { bottom: canvasRect.height - relativeY - 41.5, left: relativeX - 125, @@ -47,7 +63,7 @@ export function determinePosition( top: "auto", }; } else { - + // Bottom-right quadrant position = { bottom: canvasRect.height - relativeY - 41.5, right: canvasRect.width - relativeX - 125, @@ -58,4 +74,4 @@ export function determinePosition( } return position; -} +} \ No newline at end of file diff --git a/app/src/components/ui/list/DropDownList.tsx b/app/src/components/ui/list/DropDownList.tsx index e77f35b..3d2a828 100644 --- a/app/src/components/ui/list/DropDownList.tsx +++ b/app/src/components/ui/list/DropDownList.tsx @@ -32,14 +32,22 @@ const DropDownList: React.FC = ({ listType = "default", remove, }) => { - const [isOpen, setIsOpen] = useState(defaultOpen); - const { zones, setZones } = useZones() + const { zones, setZones } = useZones(); const handleToggle = () => { setIsOpen((prev) => !prev); // Toggle the state }; - const [zoneDataList, setZoneDataList] = useState<{ id: string; name: string }[]>([]); + + interface Asset { + id: string; + name: string; + } + + const [zoneDataList, setZoneDataList] = useState< + { id: string; name: string; assets: Asset[] }[] + >([]); + const { selectedZone, setSelectedZone } = useSelectedZoneStore(); useEffect(() => { @@ -53,12 +61,65 @@ const DropDownList: React.FC = ({ // { id: "70fa55cd-b5c9-4f80-a8c4-6319af3bfb4e", name: "zone6" }, // ]) - - const value = (zones || []).map((val: { zoneId: string; zoneName: string }) => ({ - id: val.zoneId, - name: val.zoneName - })); - setZoneDataList(prev => (JSON.stringify(prev) !== JSON.stringify(value) ? value : prev)); + const value = (zones || []).map( + (val: { zoneId: string; zoneName: string }) => ({ + id: val.zoneId, + name: val.zoneName, + }) + ); + setZoneDataList([ + { + id: "zone1", + name: "Zone 1", + assets: [ + { + id: "asset1", + name: "Asset 1", + }, + { + id: "asset2", + name: "Asset 2", + }, + { + id: "asset3", + name: "Asset 3", + }, + ], + }, + { + id: "zone2", + name: "Zone 2", + assets: [ + { + id: "asset4", + name: "Asset 4", + }, + { + id: "asset5", + name: "Asset 5", + }, + { + id: "asset6", + name: "Asset 6", + }, + ], + }, + { + id: "zone3", + name: "Zone 3", + assets: [ + { + id: "asset7", + name: "Asset 7", + }, + { + id: "asset8", + name: "Asset 8", + }, + ], + }, + ]); + }, [zones]); return ( @@ -101,6 +162,7 @@ const DropDownList: React.FC = ({ value="Buildings" showKebabMenu={false} showAddIcon={false} + items={zoneDataList} /> = ({ items = [], remove }) => { + console.log("items: ", items); const { activeModule, setActiveModule } = useModuleStore(); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); const { setSubModule } = useSubModuleStore(); + const [expandedZones, setExpandedZones] = useState>( + {} + ); + useEffect(() => { useSelectedZoneStore.getState().setSelectedZone({ zoneName: "", @@ -27,11 +51,16 @@ const List: React.FC = ({ items = [], remove }) => { widgets: [], }); }, [activeModule]); - + + const toggleZoneExpansion = (zoneId: string) => { + setExpandedZones((prev) => ({ + ...prev, + [zoneId]: !prev[zoneId], + })); + }; async function handleSelectZone(id: string) { try { - // Avoid re-fetching if the same zone is already selected if (selectedZone?.zoneId === id) { console.log("Zone is already selected:", selectedZone.zoneName); return; @@ -65,27 +94,74 @@ const List: React.FC = ({ items = [], remove }) => { <> {items.length > 0 ? (
    - {items.map((item, index) => ( -
  • -
    -
    handleSelectZone(item.id)}> - -
    -
    -
    - -
    -
    - -
    - {remove && ( -
    - + {items.map((item) => ( + +
  • +
    +
    +
    handleSelectZone(item.id)} + > +
    - )} +
    +
    +
    + +
    +
    + +
    + {remove && ( +
    + +
    + )} + {item.assets && item.assets.length > 0 && ( +
    toggleZoneExpansion(item.id)} + > + +
    + )} +
    -
- + + {/* Nested assets list - only shown when expanded */} + {item.assets && + item.assets.length > 0 && + expandedZones[item.id] && ( +
    + {item.assets.map((asset) => ( +
  • +
    +
    + +
    +
    +
    + +
    +
    + +
    + {remove && ( +
    + +
    + )} +
    +
    +
  • + ))} +
+ )} + ))} ) : ( diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index 85f2c54..d74d9ca 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -1,6 +1,7 @@ import React, { useState, useRef, useEffect } from "react"; import { ExitIcon, PlayStopIcon, ResetIcon } from "../../icons/SimulationIcons"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { useActiveTool } from "../../../store/store"; const SimulationPlayer: React.FC = () => { const [speed, setSpeed] = useState(1); @@ -8,6 +9,7 @@ const SimulationPlayer: React.FC = () => { const { setIsPlaying } = usePlayButtonStore(); const sliderRef = useRef(null); const isDragging = useRef(false); + const { setActiveTool } = useActiveTool(); // Button functions const handleReset = () => { @@ -19,6 +21,7 @@ const SimulationPlayer: React.FC = () => { const handleExit = () => { setPlaySimulation(false); setIsPlaying(false); + setActiveTool("cursor") }; // Slider functions starts diff --git a/app/src/modules/builder/agv/agv.tsx b/app/src/modules/builder/agv/agv.tsx index ace2e3e..6218662 100644 --- a/app/src/modules/builder/agv/agv.tsx +++ b/app/src/modules/builder/agv/agv.tsx @@ -19,7 +19,9 @@ const Agv = ({ }) => { const [pathPoints, setPathPoints] = useState< { - uuid: string; + modelUuid: string; + modelSpeed: number; + bufferTime: number; points: { x: number; y: number; z: number }[]; }[] >([]); @@ -32,6 +34,7 @@ const Agv = ({ (val: any) => val.modelName === "agv" ); + console.log("agvModels: ", agvModels); let findMesh = agvModels.filter( (val: any) => val.modeluuid === selectedActionSphere?.path?.modeluuid && @@ -49,8 +52,9 @@ const Agv = ({ "y" in findMesh[0].point.actions.end ? [ { - uuid: findMesh[0].modeluuid, // Ensure it's a number - + modelUuid: findMesh[0].modeluuid, // Ensure it's a number + modelSpeed: findMesh[0].point.speed, + bufferTime: findMesh[0].point.actions.buffer, points: [ { x: findMesh[0].position[0], @@ -72,15 +76,29 @@ const Agv = ({ ] : []; if (result.length > 0) { + // setPathPoints((prev) => [...prev, ...result]); setPathPoints((prev) => { - const existingUUIDs = new Set(prev.map((item) => item.uuid)); - const newItems = result.filter( - (item) => !existingUUIDs.has(item.uuid) + const existingIndex = prev.findIndex( + (item) => item.modelUuid === result[0].modelUuid ); - return [...prev, ...newItems]; + + if (existingIndex !== -1) { + const updatedPathPoints = [...prev]; + updatedPathPoints[existingIndex] = result[0]; + return updatedPathPoints; + } else { + return [...prev, ...result]; + } }); } } + // setPathPoints((prev) => { + // const existingUUIDs = new Set(prev.map((item) => item.uuid)); + // const newItems = result.filter( + // (item) => !existingUUIDs.has(item.uuid) + // ); + // return [...prev, ...newItems]; + // }); }, [simulationPaths, selectedActionSphere]); let groupRef = useRef() as Types.RefGroup; @@ -96,12 +114,40 @@ const Agv = ({ plane={plane} /> {pathPoints.map((pair, i) => ( - + <> + + {/* {pair.points.length > 2 && ( + <> + + + + + + + + + + )} */} + ))} diff --git a/app/src/modules/builder/agv/pathNavigator.tsx b/app/src/modules/builder/agv/pathNavigator.tsx index 9da0d59..a83b690 100644 --- a/app/src/modules/builder/agv/pathNavigator.tsx +++ b/app/src/modules/builder/agv/pathNavigator.tsx @@ -3,30 +3,38 @@ 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 { useTh } from "leva/dist/declarations/src/styles"; import { useActiveTool } from "../../../store/store"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -// Define interface for props interface PathNavigatorProps { navMesh: any; selectedPoints: any; id: string; + speed: number; + bufferTime: number; } export default function PathNavigator({ navMesh, selectedPoints, id, + speed, + bufferTime, }: PathNavigatorProps) { const [path, setPath] = useState<[number, number, number][]>([]); const progressRef = useRef(0); const distancesRef = useRef([]); const totalDistanceRef = useRef(0); const currentSegmentIndex = useRef(0); + const [stop, setStop] = useState(true); const { scene } = useThree(); - const { activeTool } = useActiveTool(); + const { isPlaying, setIsPlaying } = usePlayButtonStore(); const [startPoint, setStartPoint] = useState(new THREE.Vector3()); - const meshRef = useRef(null); + const isWaiting = useRef(false); // Flag to track waiting state + const delayTime = bufferTime; + + const movingForward = useRef(true); // Tracks whether the object is moving forward + // Compute distances and total distance when the path changes useEffect(() => { if (!scene || !id || path.length < 2) return; @@ -39,19 +47,17 @@ export default function PathNavigator({ distances.push(segmentDistance); totalDistance += segmentDistance; } - distancesRef.current = distances; totalDistanceRef.current = totalDistance; - progressRef.current = 0; // Reset progress when the path changes + progressRef.current = 0; }, [path]); + useEffect(() => { if (!navMesh || selectedPoints.length === 0) return; - // Flatten the selectedPoints array into a single list of points const allPoints = selectedPoints.flat(); - - // Compute paths between consecutive points const computedPath: [number, number, number][] = []; + for (let i = 0; i < allPoints.length - 1; i++) { const start = allPoints[i]; setStartPoint( @@ -64,36 +70,35 @@ export default function PathNavigator({ const navMeshQuery = new NavMeshQuery(navMesh); const { path: segmentPath } = navMeshQuery.computePath(start, end); - if (segmentPath && segmentPath.length > 0) { - computedPath.push( - ...segmentPath.map(({ x, y, z }): [number, number, number] => [ - x, - y + 0.1, - z, - ]) - ); + if (!segmentPath || segmentPath.length === 0) { + continue; } + + computedPath.push( + ...segmentPath.map(({ x, y, z }): [number, number, number] => [ + x, + y + 0.1, + z, + ]) + ); } catch (error) {} } - // Set the full computed path - if (computedPath.length > 0) { setPath(computedPath); - currentSegmentIndex.current = 0; // Reset to the first segment + currentSegmentIndex.current = 0; } - }, [selectedPoints, navMesh, path]); - + }, [selectedPoints, navMesh]); useFrame((_, delta) => { if (!scene || !id || path.length < 2) return; - // Find the object in the scene + // Find the object in the scene by its UUID const findObject = scene.getObjectByProperty("uuid", id); - if (activeTool === "play") { - if (!findObject) return; + if (!findObject) return; - const speed = 5; - progressRef.current += delta * speed; + if (isPlaying) { + const fast = speed; + progressRef.current += delta * fast; let coveredDistance = progressRef.current; let accumulatedDistance = 0; @@ -108,9 +113,24 @@ export default function PathNavigator({ index++; } - // If the object has reached the end of the path, stop moving if (index >= distancesRef.current.length) { progressRef.current = totalDistanceRef.current; + + if (!isWaiting.current) { + isWaiting.current = true; // Set waiting flag + + setTimeout(() => { + progressRef.current = 0; // Reset progress + movingForward.current = !movingForward.current; // Toggle direction + + // Reverse the path and distances arrays + path.reverse(); + distancesRef.current.reverse(); + + // Reset the waiting flag + isWaiting.current = false; + }, delayTime * 1000); // Convert seconds to milliseconds + } return; } @@ -119,31 +139,35 @@ export default function PathNavigator({ const end = new THREE.Vector3(...path[index + 1]); const segmentDistance = distancesRef.current[index]; - const t = (coveredDistance - accumulatedDistance) / segmentDistance; + const t = Math.min( + (coveredDistance - accumulatedDistance) / segmentDistance, + 1 + ); // Clamp t to avoid overshooting const position = start.clone().lerp(end, t); findObject.position.copy(position); // Rotate the object to face the direction of movement const direction = new THREE.Vector3().subVectors(end, start).normalize(); - const targetQuaternion = new THREE.Quaternion().setFromUnitVectors( - new THREE.Vector3(0, 0, 1), // Assuming forward direction is (0, 0, 1) - direction - ); - findObject.quaternion.slerp(targetQuaternion, 0.1); // Smoothly interpolate rotation - } else if (activeTool === "cursor") { - findObject?.position.copy(startPoint); + const targetYRotation = Math.atan2(direction.x, direction.z); + findObject.rotation.y += (targetYRotation - findObject.rotation.y) * 0.1; + } else { + findObject.position.copy(startPoint); } }); - + return ( <> - {path.length > 0 && } - {/* {path.length > 0 && ( - 0 ? path[0] : [0, 0.1, 0]}> - - - - )} */} + {path.length > 0 && ( + <> + + {selectedPoints.map((val: any, i: any) => ( + + + + + ))} + + )} ); } diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts index a4255bd..a1ed87b 100644 --- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts +++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts @@ -24,6 +24,7 @@ async function addAssetModel( socket: Socket, selectedItem: any, setSelectedItem: any, + setSimulationPaths: any, plane: Types.RefMesh, ): Promise { @@ -64,7 +65,7 @@ async function addAssetModel( const cachedModel = THREE.Cache.get(selectedItem.id); if (cachedModel) { // console.log(`[Cache] Fetching ${selectedItem.name}`); - handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket); + handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket); return; } else { const cachedModelBlob = await retrieveGLTF(selectedItem.id); @@ -77,7 +78,7 @@ async function addAssetModel( URL.revokeObjectURL(blobUrl); THREE.Cache.remove(blobUrl); THREE.Cache.add(selectedItem.id, gltf); - handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket); + handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket); }, () => { TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup); @@ -89,7 +90,7 @@ async function addAssetModel( const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`).then((res) => res.blob()); await storeGLTF(selectedItem.id, modelBlob); THREE.Cache.add(selectedItem.id, gltf); - await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket); + await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket); }, () => { TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup); @@ -112,10 +113,11 @@ async function handleModelLoad( tempLoader: Types.RefMesh, isTempLoader: Types.RefBoolean, setFloorItems: Types.setFloorItemSetState, + setSimulationPaths: any, socket: Socket ) { const model = gltf.scene.clone(); - model.userData = { name: selectedItem.name, modelId: selectedItem.id }; + model.userData = { name: selectedItem.name, modelId: selectedItem.id, modeluuid: model.uuid }; model.position.set(intersectPoint!.x, 3 + intersectPoint!.y, intersectPoint!.z); model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); @@ -152,7 +154,7 @@ async function handleModelLoad( if (res.type === "Conveyor") { const pointUUIDs = res.points.map(() => THREE.MathUtils.generateUUID()); - const eventData: Extract = { + const backendEventData: Extract = { type: 'Conveyor', points: res.points.map((point: any, index: number) => ({ uuid: pointUUIDs[index], @@ -176,49 +178,94 @@ async function handleModelLoad( speed: 'Inherit' }; - // console.log('eventData: ', eventData); - newFloorItem.eventData = eventData; + // API + + // await setFloorItemApi( + // organization, + // newFloorItem.modeluuid, + // newFloorItem.modelname, + // newFloorItem.modelfileID, + // newFloorItem.position, + // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + // false, + // true, + // newFloorItem.eventData + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + isLocked: false, + isVisible: true, + eventData: backendEventData, + socketId: socket.id + }; + + console.log('data: ', data); + setFloorItems((prevItems) => { + const updatedItems = [...(prevItems || []), newFloorItem]; + localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); + return updatedItems; + }); + + const eventData: any = backendEventData; + eventData.modeluuid = newFloorItem.modeluuid; + eventData.modelName = newFloorItem.modelname; + eventData.position = newFloorItem.position; + eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z]; + + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + ...(prevEvents || []), + eventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + + } else { + + // API + + // await setFloorItemApi( + // organization, + // newFloorItem.modeluuid, + // newFloorItem.modelname, + // newFloorItem.modelfileID, + // newFloorItem.position, + // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + // false, + // true + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + isLocked: false, + isVisible: true, + socketId: socket.id + }; + + setFloorItems((prevItems) => { + const updatedItems = [...(prevItems || []), newFloorItem]; + localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); + return updatedItems; + }); + + socket.emit("v2:model-asset:add", data); + } - setFloorItems((prevItems) => { - const updatedItems = [...(prevItems || []), newFloorItem]; - localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); - return updatedItems; - }); - - // API - - // await setFloorItemApi( - // organization, - // newFloorItem.modeluuid, - // newFloorItem.modelname, - // newFloorItem.modelfileID, - // newFloorItem.position, - // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, - // false, - // true, - // newFloorItem.eventData - // ); - - // SOCKET - - const data = { - organization, - modeluuid: newFloorItem.modeluuid, - modelname: newFloorItem.modelname, - modelfileID: newFloorItem.modelfileID, - position: newFloorItem.position, - rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, - isLocked: false, - isVisible: true, - eventData: newFloorItem.eventData, - socketId: socket.id - }; - console.log('data: ', data); - - - socket.emit("v2:model-asset:add", data); - gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" }); gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } }); }); diff --git a/app/src/modules/builder/geomentries/assets/assetManager.ts b/app/src/modules/builder/geomentries/assets/assetManager.ts index 38d0721..240e7a1 100644 --- a/app/src/modules/builder/geomentries/assets/assetManager.ts +++ b/app/src/modules/builder/geomentries/assets/assetManager.ts @@ -121,7 +121,7 @@ export default async function assetManager( const model = gltf; model.uuid = item.modeluuid; - model.userData = { name: item.modelname, modelId: item.modelfileID }; + model.userData = { name: item.modelname, modelId: item.modelfileID, modeluuid: item.modeluuid }; model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); model.position.set(...item.position); model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z); diff --git a/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts b/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts index 3bbe2cc..a71aaea 100644 --- a/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts +++ b/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts @@ -10,6 +10,7 @@ async function DeleteFloorItems( itemsGroup: Types.RefGroup, hoveredDeletableFloorItem: Types.RefMesh, setFloorItems: Types.setFloorItemSetState, + setSimulationPaths: any, socket: Socket ): Promise { @@ -74,6 +75,12 @@ async function DeleteFloorItems( itemsGroup.current.remove(hoveredDeletableFloorItem.current); } setFloorItems(updatedItems); + + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).filter(event => event.modeluuid !== removedItem.modeluuid); + return updatedEvents; + }); + toast.success("Model Removed!"); } } diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx index bee47b5..2b77fb2 100644 --- a/app/src/modules/builder/groups/floorItemsGroup.tsx +++ b/app/src/modules/builder/groups/floorItemsGroup.tsx @@ -10,6 +10,7 @@ import { useRenderDistance, useselectedFloorItem, useSelectedItem, + useSimulationPaths, useSocketStore, useToggleView, useTransformMode, @@ -64,6 +65,7 @@ const FloorItemsGroup = ({ const { setselectedFloorItem } = useselectedFloorItem(); const { activeTool } = useActiveTool(); const { selectedItem, setSelectedItem } = useSelectedItem(); + const { simulationPaths, setSimulationPaths } = useSimulationPaths(); const { setLoadingProgress } = useLoadingProgress(); const { activeModule } = useModuleStore(); const { socket } = useSocketStore(); @@ -96,16 +98,23 @@ const FloorItemsGroup = ({ }; getFloorAssets(organization).then((data) => { - const uniqueItems = (data as Types.FloorItems).filter( - (item, index, self) => - index === self.findIndex((t) => t.modelfileID === item.modelfileID) - ); - totalAssets = uniqueItems.length; - if (totalAssets === 0) { + if (data.length > 0) { + const uniqueItems = (data as Types.FloorItems).filter( + (item, index, self) => + index === self.findIndex((t) => t.modelfileID === item.modelfileID) + ); + totalAssets = uniqueItems.length; + if (totalAssets === 0) { + updateLoadingProgress(100); + return; + } + gltfLoaderWorker.postMessage({ floorItems: data }); + } else { + console.log('data: ', data); + gltfLoaderWorker.postMessage({ floorItems: [] }); + loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationPaths); updateLoadingProgress(100); - return; } - gltfLoaderWorker.postMessage({ floorItems: data }); }); gltfLoaderWorker.onmessage = async (event) => { @@ -122,7 +131,7 @@ const FloorItemsGroup = ({ updateLoadingProgress(progress); if (loadedAssets === totalAssets) { - loadInitialFloorItems(itemsGroup, setFloorItems); + loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationPaths); updateLoadingProgress(100); } }); @@ -240,6 +249,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, setFloorItems, + setSimulationPaths, socket ); } @@ -365,6 +375,7 @@ const FloorItemsGroup = ({ socket, selectedItem, setSelectedItem, + setSimulationPaths, plane ); } @@ -408,7 +419,6 @@ const FloorItemsGroup = ({ activeModule, ]); - useEffect(() => {}, [floorItems]); useFrame(() => { if (controls) diff --git a/app/src/modules/collaboration/socketResponses.dev.tsx b/app/src/modules/collaboration/socketResponses.dev.tsx index d17557d..b8cec55 100644 --- a/app/src/modules/collaboration/socketResponses.dev.tsx +++ b/app/src/modules/collaboration/socketResponses.dev.tsx @@ -105,7 +105,7 @@ export default function SocketResponses({ // console.log(`Getting ${data.data.modelname} from cache`); const model = cachedModel.scene.clone(); model.uuid = data.data.modeluuid; - model.userData = { name: data.data.modelname, modelId: data.data.modelFileID }; + model.userData = { name: data.data.modelname, modelId: data.data.modelFileID, modeluuid: data.data.modeluuid }; model.position.set(...data.data.position as [number, number, number]); model.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z); model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); @@ -183,7 +183,7 @@ export default function SocketResponses({ THREE.Cache.remove(url); const model = gltf.scene; model.uuid = data.data.modeluuid; - model.userData = { name: data.data.modelname, modelId: data.data.modelFileID }; + model.userData = { name: data.data.modelname, modelId: data.data.modelFileID, modeluuid: data.data.modeluuid }; model.position.set(...data.data.position as [number, number, number]); model.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z); model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); diff --git a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts index dfff78d..3630378 100644 --- a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts @@ -11,7 +11,8 @@ import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAss async function loadInitialFloorItems( itemsGroup: Types.RefGroup, - setFloorItems: Types.setFloorItemSetState + setFloorItems: Types.setFloorItemSetState, + setSimulationPaths: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => void ): Promise { if (!itemsGroup.current) return; let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; @@ -22,6 +23,8 @@ async function loadInitialFloorItems( localStorage.setItem("FloorItems", JSON.stringify(items)); await initializeDB(); + if (items.message === "floorItems not found") return; + if (items) { const storedFloorItems: Types.FloorItems = items; const loader = new GLTFLoader(); @@ -50,6 +53,7 @@ async function loadInitialFloorItems( }); for (const item of storedFloorItems) { + console.log('item: ', item); if (!item.modelfileID) return; const itemPosition = new THREE.Vector3(item.position[0], item.position[1], item.position[2]); let storedPosition; @@ -68,7 +72,7 @@ async function loadInitialFloorItems( const cachedModel = THREE.Cache.get(item.modelfileID!); if (cachedModel) { // console.log(`[Cache] Fetching ${item.modelname}`); - processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems); + processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); return; @@ -85,7 +89,7 @@ async function loadInitialFloorItems( URL.revokeObjectURL(blobUrl); THREE.Cache.remove(blobUrl); THREE.Cache.add(item.modelfileID!, gltf); - processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems); + processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); }, @@ -108,7 +112,7 @@ async function loadInitialFloorItems( const modelBlob = await fetch(modelUrl).then((res) => res.blob()); await storeGLTF(item.modelfileID!, modelBlob); THREE.Cache.add(item.modelfileID!, gltf); - processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems); + processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); }, @@ -121,34 +125,23 @@ async function loadInitialFloorItems( }); } else { // console.log(`Item ${item.modelname} is not near`); + setFloorItems((prevItems) => [ + ...(prevItems || []), + { + modeluuid: item.modeluuid, + modelname: item.modelname, + position: item.position, + rotation: item.rotation, + modelfileID: item.modelfileID, + isLocked: item.isLocked, + isVisible: item.isVisible, + }, + ]); + if (item.eventData) { - setFloorItems((prevItems) => [ - ...(prevItems || []), - { - modeluuid: item.modeluuid, - modelname: item.modelname, - position: item.position, - rotation: item.rotation, - modelfileID: item.modelfileID, - isLocked: item.isLocked, - isVisible: item.isVisible, - // eventData: item.eventData, - }, - ]); - } else { - setFloorItems((prevItems) => [ - ...(prevItems || []), - { - modeluuid: item.modeluuid, - modelname: item.modelname, - position: item.position, - rotation: item.rotation, - modelfileID: item.modelfileID, - isLocked: item.isLocked, - isVisible: item.isVisible, - }, - ]); + processEventData(item, setSimulationPaths); } + modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, () => { }); } @@ -164,12 +157,13 @@ function processLoadedModel( gltf: any, item: Types.FloorItemType, itemsGroup: Types.RefGroup, - setFloorItems: Types.setFloorItemSetState + setFloorItems: Types.setFloorItemSetState, + setSimulationPaths: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => void ) { const model = gltf; model.uuid = item.modeluuid; model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); - model.userData = { name: item.modelname, modelId: item.modelfileID }; + model.userData = { name: item.modelname, modelId: item.modelfileID, modeluuid: item.modeluuid }; model.position.set(...item.position); model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z); @@ -186,39 +180,67 @@ function processLoadedModel( itemsGroup?.current?.add(model); - if (item.eventData) { - setFloorItems((prevItems) => [ - ...(prevItems || []), - { - modeluuid: item.modeluuid, - modelname: item.modelname, - position: item.position, - rotation: item.rotation, - modelfileID: item.modelfileID, - isLocked: item.isLocked, - isVisible: item.isVisible, - // eventData: item.eventData, - }, - ]); - } else { - setFloorItems((prevItems) => [ - ...(prevItems || []), - { - modeluuid: item.modeluuid, - modelname: item.modelname, - position: item.position, - rotation: item.rotation, - modelfileID: item.modelfileID, - isLocked: item.isLocked, - isVisible: item.isVisible, - }, - ]); + setFloorItems((prevItems) => [ + ...(prevItems || []), + { + modeluuid: item.modeluuid, + modelname: item.modelname, + position: item.position, + rotation: item.rotation, + modelfileID: item.modelfileID, + isLocked: item.isLocked, + isVisible: item.isVisible, + }, + ]); + + if (item.eventData || item.modelfileID === '67e3da19c2e8f37134526e6a') { + processEventData(item, setSimulationPaths); } gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' }); gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' }); } +function processEventData(item: Types.FloorItemType, setSimulationPaths: any) { + + if (item.eventData?.type === 'Conveyor') { + + const data: any = item.eventData; + data.modeluuid = item.modeluuid; + data.modelName = item.modelname; + data.position = item.position; + data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z]; + + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + ...(prevEvents || []), + data as Types.ConveyorEventsSchema + ]); + } else { + + const pointUUID = THREE.MathUtils.generateUUID(); + const pointPosition = new THREE.Vector3(0, 1.3, 0); + + const newVehiclePath: Types.VehicleEventsSchema = { + modeluuid: item.modeluuid, + modelName: item.modelname, + type: 'Vehicle', + point: { + uuid: pointUUID, + position: [pointPosition.x, pointPosition.y, pointPosition.z], + actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: {}, hitCount: 1, end: {}, buffer: 0 }, + connections: { source: { pathUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, + speed: 2, + }, + position: [...item.position], + }; + + setSimulationPaths((prevEvents: (Types.VehicleEventsSchema)[]) => [ + ...(prevEvents || []), + newVehiclePath as Types.VehicleEventsSchema + ]); + } +} + function checkLoadingCompletion( modelsLoaded: number, modelsToLoad: number, diff --git a/app/src/modules/scene/controls/selection/copyPasteControls.tsx b/app/src/modules/scene/controls/selection/copyPasteControls.tsx index 4628311..dea1da6 100644 --- a/app/src/modules/scene/controls/selection/copyPasteControls.tsx +++ b/app/src/modules/scene/controls/selection/copyPasteControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useEffect, useMemo } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; import { toast } from "react-toastify"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import * as Types from "../../../../types/world/worldTypes"; @@ -10,6 +10,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); + const { simulationPaths, setSimulationPaths } = useSimulationPaths(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore() @@ -150,37 +151,128 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas return updatedItems; }); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; + if (eventData) { + if (eventData.type === 'Conveyor' && eventData) { + const createConveyorPoint = (index: number) => { + const pointUUID = THREE.MathUtils.generateUUID(); + const hasActions = (eventData as Types.ConveyorEventsSchema)?.points[index].actions.length > 0; - //REST + const defaultAction = { + uuid: THREE.MathUtils.generateUUID(), + name: 'Action 1', + type: 'Inherit', + material: 'Inherit', + delay: 'Inherit', + spawnInterval: 'Inherit', + isUsed: true + }; - // await setFloorItemApi( - // organization, - // obj.uuid, - // obj.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, - // false, - // true, - // ); + return { + uuid: pointUUID, + position: (eventData as Types.ConveyorEventsSchema)?.points[index].position, + rotation: (eventData as Types.ConveyorEventsSchema)?.points[index].rotation, + actions: hasActions + ? (eventData as Types.ConveyorEventsSchema)?.points[index].actions.map(action => ({ + ...action, + uuid: THREE.MathUtils.generateUUID() + })) + : [defaultAction], + triggers: (eventData as Types.ConveyorEventsSchema)?.points[index].triggers, + connections: { + source: { pathUUID: obj.uuid, pointUUID }, + targets: [] + } + }; + }; - //SOCKET + const backendEventData = { + type: 'Conveyor', + points: [ + createConveyorPoint(0), // point1 + createConveyorPoint(1), // middlePoint + createConveyorPoint(2) // point2 + ], + speed: (eventData as Types.ConveyorEventsSchema)?.speed + }; - const data = { - organization, - modeluuid: newFloorItem.modeluuid, - modelname: newFloorItem.modelname, - modelfileID: newFloorItem.modelfileID, - position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, - isLocked: false, - isVisible: true, - socketId: socket.id, - }; + //REST - socket.emit("v2:model-asset:add", data); + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // backendEventData + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: backendEventData, + socketId: socket.id, + }; + + const newEventData: any = backendEventData; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + ...(prevEvents || []), + newEventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + } + } else { + + //REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + socketId: socket.id, + }; + + socket.emit("v2:model-asset:add", data); + + } itemsGroupRef.current.add(obj); } diff --git a/app/src/modules/scene/controls/selection/duplicationControls.tsx b/app/src/modules/scene/controls/selection/duplicationControls.tsx index 6b32195..185ca68 100644 --- a/app/src/modules/scene/controls/selection/duplicationControls.tsx +++ b/app/src/modules/scene/controls/selection/duplicationControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useEffect, useMemo } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; import { toast } from "react-toastify"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import * as Types from "../../../../types/world/worldTypes"; @@ -10,11 +10,11 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); + const { simulationPaths, setSimulationPaths } = useSimulationPaths(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); - useEffect(() => { if (!camera || !scene || toggleView) return; const canvasElement = gl.domElement; @@ -131,37 +131,129 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb return updatedItems; }); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; - //REST + if (eventData) { + if (eventData.type === 'Conveyor' && eventData) { + const createConveyorPoint = (index: number) => { + const pointUUID = THREE.MathUtils.generateUUID(); + const hasActions = (eventData as Types.ConveyorEventsSchema)?.points[index].actions.length > 0; - // await setFloorItemApi( - // organization, - // obj.uuid, - // obj.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, - // false, - // true, - // ); + const defaultAction = { + uuid: THREE.MathUtils.generateUUID(), + name: 'Action 1', + type: 'Inherit', + material: 'Inherit', + delay: 'Inherit', + spawnInterval: 'Inherit', + isUsed: true + }; - //SOCKET + return { + uuid: pointUUID, + position: (eventData as Types.ConveyorEventsSchema)?.points[index].position, + rotation: (eventData as Types.ConveyorEventsSchema)?.points[index].rotation, + actions: hasActions + ? (eventData as Types.ConveyorEventsSchema)?.points[index].actions.map(action => ({ + ...action, + uuid: THREE.MathUtils.generateUUID() + })) + : [defaultAction], + triggers: (eventData as Types.ConveyorEventsSchema)?.points[index].triggers, + connections: { + source: { pathUUID: obj.uuid, pointUUID }, + targets: [] + } + }; + }; - const data = { - organization, - modeluuid: newFloorItem.modeluuid, - modelname: newFloorItem.modelname, - modelfileID: newFloorItem.modelfileID, - position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, - isLocked: false, - isVisible: true, - socketId: socket.id, - }; + const backendEventData = { + type: 'Conveyor', + points: [ + createConveyorPoint(0), // point1 + createConveyorPoint(1), // middlePoint + createConveyorPoint(2) // point2 + ], + speed: (eventData as Types.ConveyorEventsSchema)?.speed + }; - socket.emit("v2:model-asset:add", data); + //REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // backendEventData + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: backendEventData, + socketId: socket.id, + }; + + const newEventData: any = backendEventData; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + ...(prevEvents || []), + newEventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + } + } else { + + //REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + socketId: socket.id, + }; + + socket.emit("v2:model-asset:add", data); + + } itemsGroupRef.current.add(obj); } diff --git a/app/src/modules/scene/controls/selection/moveControls.tsx b/app/src/modules/scene/controls/selection/moveControls.tsx index 15973d8..350b487 100644 --- a/app/src/modules/scene/controls/selection/moveControls.tsx +++ b/app/src/modules/scene/controls/selection/moveControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import { toast } from "react-toastify"; import * as Types from "../../../../types/world/worldTypes"; @@ -12,6 +12,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); + const { simulationPaths, setSimulationPaths } = useSimulationPaths(); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); const itemsData = useRef([]); @@ -179,37 +180,99 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje return updatedItems; }); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; - //REST + if (eventData) { + if (eventData.type === 'Conveyor' && eventData) { - // await setFloorItemApi( - // organization, - // obj.uuid, - // obj.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, - // false, - // true, - // ); + const backendEventData = { + type: 'Conveyor', + points: eventData.points, + speed: (eventData as Types.ConveyorEventsSchema)?.speed + }; - //SOCKET + //REST - const data = { - organization, - modeluuid: newFloorItem.modeluuid, - modelname: newFloorItem.modelname, - modelfileID: newFloorItem.modelfileID, - position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, - isLocked: false, - isVisible: true, - socketId: socket.id, - }; + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // backendEventData + // ); - socket.emit("v2:model-asset:add", data); + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: backendEventData, + socketId: socket.id, + }; + + const newEventData: any = backendEventData; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + // socket.emit("v2:model-asset:add", data); + } + } else { + + + //REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + socketId: socket.id, + }; + + socket.emit("v2:model-asset:add", data); + + } itemsGroupRef.current.add(obj); } diff --git a/app/src/modules/scene/controls/selection/rotateControls.tsx b/app/src/modules/scene/controls/selection/rotateControls.tsx index f8771be..6ad8309 100644 --- a/app/src/modules/scene/controls/selection/rotateControls.tsx +++ b/app/src/modules/scene/controls/selection/rotateControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import { toast } from "react-toastify"; import * as Types from "../../../../types/world/worldTypes"; @@ -12,6 +12,7 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); + const { simulationPaths, setSimulationPaths } = useSimulationPaths(); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); const itemsData = useRef([]); @@ -182,37 +183,99 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo return updatedItems; }); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; - //REST + if (eventData) { + if (eventData.type === 'Conveyor' && eventData) { - // await setFloorItemApi( - // organization, - // obj.uuid, - // obj.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, - // false, - // true, - // ); + const backendEventData = { + type: 'Conveyor', + points: eventData.points, + speed: (eventData as Types.ConveyorEventsSchema)?.speed + }; - //SOCKET + //REST - const data = { - organization, - modeluuid: newFloorItem.modeluuid, - modelname: newFloorItem.modelname, - modelfileID: newFloorItem.modelfileID, - position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, - isLocked: false, - isVisible: true, - socketId: socket.id, - }; + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // backendEventData + // ); - socket.emit("v2:model-asset:add", data); + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + eventData: backendEventData, + socketId: socket.id, + }; + + const newEventData: any = backendEventData; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + // socket.emit("v2:model-asset:add", data); + } + } else { + + + //REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + socketId: socket.id, + }; + + socket.emit("v2:model-asset:add", data); + + } itemsGroupRef.current.add(obj); } diff --git a/app/src/modules/scene/controls/selection/selectionControls.tsx b/app/src/modules/scene/controls/selection/selectionControls.tsx index 8e9ac96..fd7e19e 100644 --- a/app/src/modules/scene/controls/selection/selectionControls.tsx +++ b/app/src/modules/scene/controls/selection/selectionControls.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox"; import { SelectionHelper } from "./selectionHelper"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; import BoundingBox from "./boundingBoxHelper"; import { toast } from "react-toastify"; // import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi'; @@ -20,6 +20,7 @@ const SelectionControls: React.FC = () => { const itemsGroupRef = useRef(undefined); const selectionGroup = useRef() as Types.RefGroup; const { toggleView } = useToggleView(); + const { setSimulationPaths } = useSimulationPaths(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const [movedObjects, setMovedObjects] = useState([]); const [rotatedObjects, setRotatedObjects] = useState([]); @@ -239,6 +240,11 @@ const SelectionControls: React.FC = () => { } }); + setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).filter(event => event.modeluuid !== selectedMesh.uuid); + return updatedEvents; + }); + itemsGroupRef.current?.remove(selectedMesh); }); diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index 15597b8..ffa020e 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -93,13 +93,13 @@ export default function PostProcessing() { @@ -108,9 +108,9 @@ export default function PostProcessing() { { const newPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[] = []; - floorItems.forEach((item: Types.FloorItemType) => { - if (item.modelfileID === "672a090f80d91ac979f4d0bd") { - console.log('item: ', item); - const point1Position = new THREE.Vector3(0, 0.85, 2.2); - const middlePointPosition = new THREE.Vector3(0, 0.85, 0); - const point2Position = new THREE.Vector3(0, 0.85, -2.2); + // floorItems.forEach((item: Types.FloorItemType) => { + // if (item.modelfileID === "672a090f80d91ac979f4d0bd") { + // const point1Position = new THREE.Vector3(0, 0.85, 2.2); + // const middlePointPosition = new THREE.Vector3(0, 0.85, 0); + // const point2Position = new THREE.Vector3(0, 0.85, -2.2); - const point1UUID = THREE.MathUtils.generateUUID(); - const middlePointUUID = THREE.MathUtils.generateUUID(); - const point2UUID = THREE.MathUtils.generateUUID(); + // const point1UUID = THREE.MathUtils.generateUUID(); + // const middlePointUUID = THREE.MathUtils.generateUUID(); + // const point2UUID = THREE.MathUtils.generateUUID(); - const newPath: Types.ConveyorEventsSchema = { - modeluuid: item.modeluuid, - modelName: item.modelname, - type: 'Conveyor', - points: [ - { - uuid: point1UUID, - position: [point1Position.x, point1Position.y, point1Position.z], - rotation: [0, 0, 0], - actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], - triggers: [], - connections: { source: { pathUUID: item.modeluuid, pointUUID: point1UUID }, targets: [] }, - }, - { - uuid: middlePointUUID, - position: [middlePointPosition.x, middlePointPosition.y, middlePointPosition.z], - rotation: [0, 0, 0], - actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], - triggers: [], - connections: { source: { pathUUID: item.modeluuid, pointUUID: middlePointUUID }, targets: [] }, - }, - { - uuid: point2UUID, - position: [point2Position.x, point2Position.y, point2Position.z], - rotation: [0, 0, 0], - actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], - triggers: [], - connections: { source: { pathUUID: item.modeluuid, pointUUID: point2UUID }, targets: [] }, - }, - ], - position: [...item.position], - rotation: [item.rotation.x, item.rotation.y, item.rotation.z], - speed: 'Inherit', - }; + // const newPath: Types.ConveyorEventsSchema = { + // modeluuid: item.modeluuid, + // modelName: item.modelname, + // type: 'Conveyor', + // points: [ + // { + // uuid: point1UUID, + // position: [point1Position.x, point1Position.y, point1Position.z], + // rotation: [0, 0, 0], + // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], + // triggers: [], + // connections: { source: { pathUUID: item.modeluuid, pointUUID: point1UUID }, targets: [] }, + // }, + // { + // uuid: middlePointUUID, + // position: [middlePointPosition.x, middlePointPosition.y, middlePointPosition.z], + // rotation: [0, 0, 0], + // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], + // triggers: [], + // connections: { source: { pathUUID: item.modeluuid, pointUUID: middlePointUUID }, targets: [] }, + // }, + // { + // uuid: point2UUID, + // position: [point2Position.x, point2Position.y, point2Position.z], + // rotation: [0, 0, 0], + // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], + // triggers: [], + // connections: { source: { pathUUID: item.modeluuid, pointUUID: point2UUID }, targets: [] }, + // }, + // ], + // position: [...item.position], + // rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + // speed: 'Inherit', + // }; - newPaths.push(newPath); - } else if (item.modelfileID === "67e3da19c2e8f37134526e6a") { - const pointUUID = THREE.MathUtils.generateUUID(); - const pointPosition = new THREE.Vector3(0, 1.3, 0); + // newPaths.push(newPath); + // } else if (item.modelfileID === "67e3da19c2e8f37134526e6a") { + // const pointUUID = THREE.MathUtils.generateUUID(); + // const pointPosition = new THREE.Vector3(0, 1.3, 0); - const newVehiclePath: Types.VehicleEventsSchema = { - modeluuid: item.modeluuid, - modelName: item.modelname, - type: 'Vehicle', - point: { - uuid: pointUUID, - position: [pointPosition.x, pointPosition.y, pointPosition.z], - actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: {}, hitCount: 1, end: {}, buffer: 0 }, - connections: { source: { pathUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, - speed: 2, - }, - position: [...item.position], - }; + // const newVehiclePath: Types.VehicleEventsSchema = { + // modeluuid: item.modeluuid, + // modelName: item.modelname, + // type: 'Vehicle', + // point: { + // uuid: pointUUID, + // position: [pointPosition.x, pointPosition.y, pointPosition.z], + // actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: {}, hitCount: 1, end: {}, buffer: 0 }, + // connections: { source: { pathUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, + // speed: 2, + // }, + // position: [...item.position], + // }; - newPaths.push(newVehiclePath); - } - }); + // newPaths.push(newVehiclePath); + // } + // }); - setSimulationPaths(newPaths); + // setSimulationPaths(newPaths); + // console.log('floorItems: ', floorItems); }, [floorItems]); return null; diff --git a/app/src/modules/simulation/path/pathCreation.tsx b/app/src/modules/simulation/path/pathCreation.tsx index 7af4afd..dd531a9 100644 --- a/app/src/modules/simulation/path/pathCreation.tsx +++ b/app/src/modules/simulation/path/pathCreation.tsx @@ -14,12 +14,14 @@ import { } from "../../../store/store"; import { useFrame, useThree } from "@react-three/fiber"; import { useSubModuleStore } from "../../../store/useModuleStore"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObject; }) { + const { isPlaying } = usePlayButtonStore(); const { renderDistance } = useRenderDistance(); const { setSubModule } = useSubModuleStore(); const { setSelectedActionSphere, selectedActionSphere } = @@ -66,7 +68,7 @@ function PathCreation({ const distance = new THREE.Vector3( ...group.position.toArray() ).distanceTo(camera.position); - group.visible = distance <= renderDistance; + group.visible = ((distance <= renderDistance) && !isPlaying); } }); }); @@ -193,7 +195,7 @@ function PathCreation({ }; return ( - + {simulationPaths.map((path) => { if (path.type === "Conveyor") { const points = path.points.map( diff --git a/app/src/modules/simulation/process/animation.Worker.js b/app/src/modules/simulation/process/animation.Worker.js new file mode 100644 index 0000000..faa6587 --- /dev/null +++ b/app/src/modules/simulation/process/animation.Worker.js @@ -0,0 +1,916 @@ +// // animation-worker.js +// // This web worker handles animation calculations off the main thread + +// /* eslint-disable no-restricted-globals */ +// // The above disables the ESLint rule for this file since 'self' is valid in web workers + +// // Store process data, animation states, and objects +// let processes = []; +// let animationStates = {}; +// let lastTimestamp = 0; + +// // Message handler for communication with main thread +// self.onmessage = function (event) { +// const { type, data } = event.data; + +// switch (type) { +// case "initialize": +// processes = data.processes; +// initializeAnimationStates(); +// break; + +// case "update": +// const { timestamp, isPlaying } = data; +// if (isPlaying) { +// const delta = (timestamp - lastTimestamp) / 1000; // Convert to seconds +// updateAnimations(delta, timestamp); +// } +// lastTimestamp = timestamp; +// break; + +// case "reset": +// resetAnimations(); +// break; + +// case "togglePlay": +// // If resuming from pause, recalculate the time delta +// lastTimestamp = data.timestamp; +// break; +// } +// }; + +// // Initialize animation states for all processes +// function initializeAnimationStates() { +// animationStates = {}; + +// processes.forEach((process) => { +// animationStates[process.id] = { +// spawnedObjects: {}, +// nextSpawnTime: 0, +// objectIdCounter: 0, +// }; +// }); + +// // Send initial states back to main thread +// self.postMessage({ +// type: "statesInitialized", +// data: { animationStates }, +// }); +// } + +// // Reset all animations +// function resetAnimations() { +// initializeAnimationStates(); +// } + +// // Find spawn point in a process +// function findSpawnPoint(process) { +// 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, path }; +// } +// } +// } +// return null; +// } + +// // Create a new spawned object with proper initial position +// function createSpawnedObject(process, spawnPoint, currentTime, materialType) { +// // Extract spawn position from the actual spawn point +// const position = spawnPoint.point.position +// ? [...spawnPoint.point.position] +// : [0, 0, 0]; + +// // Get the path position and add it to the spawn point position +// const pathPosition = spawnPoint.path.pathPosition || [0, 0, 0]; +// const absolutePosition = [ +// position[0] + pathPosition[0], +// position[1] + pathPosition[1], +// position[2] + pathPosition[2], +// ]; + +// return { +// id: `obj-${process.id}-${animationStates[process.id].objectIdCounter}`, +// position: absolutePosition, +// state: { +// currentIndex: 0, +// progress: 0, +// isAnimating: true, +// speed: process.speed || 1, +// isDelaying: false, +// delayStartTime: 0, +// currentDelayDuration: 0, +// delayComplete: false, +// currentPathIndex: 0, +// // Store the spawn point index to start animation from correct path point +// spawnPointIndex: getPointIndexInProcess(process, spawnPoint.point), +// }, +// visible: true, +// materialType: materialType || "Default", +// spawnTime: currentTime, +// }; +// } + +// // Get the index of a point within the process animation path +// function getPointIndexInProcess(process, point) { +// if (!process.paths) return 0; + +// let cumulativePoints = 0; +// for (const path of process.paths) { +// for (let i = 0; i < (path.points?.length || 0); i++) { +// if (path.points[i].uuid === point.uuid) { +// return cumulativePoints + i; +// } +// } +// cumulativePoints += path.points?.length || 0; +// } + +// return 0; +// } + +// // Get point data for current animation index +// function getPointDataForAnimationIndex(process, index) { +// 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 null; +// } + +// // Convert process paths to Vector3 format +// function getProcessPath(process) { +// return process.animationPath?.map((p) => ({ x: p.x, y: p.y, z: p.z })) || []; +// } + +// // Handle material swap for an object +// function handleMaterialSwap(processId, objectId, materialType) { +// const processState = animationStates[processId]; +// if (!processState || !processState.spawnedObjects[objectId]) return; + +// processState.spawnedObjects[objectId].materialType = materialType; + +// // Notify main thread about material change +// self.postMessage({ +// type: "materialChanged", +// data: { +// processId, +// objectId, +// materialType, +// }, +// }); +// } + +// // Handle point actions for an object +// function handlePointActions(processId, objectId, actions = [], currentTime) { +// let shouldStopAnimation = false; +// const processState = animationStates[processId]; + +// if (!processState || !processState.spawnedObjects[objectId]) return false; + +// const objectState = processState.spawnedObjects[objectId]; + +// actions.forEach((action) => { +// if (!action.isUsed) return; + +// switch (action.type) { +// case "Delay": +// if (objectState.state.isDelaying) return; + +// const delayDuration = +// typeof action.delay === "number" +// ? action.delay +// : parseFloat(action.delay || "0"); + +// if (delayDuration > 0) { +// objectState.state.isDelaying = true; +// objectState.state.delayStartTime = currentTime; +// objectState.state.currentDelayDuration = delayDuration; +// objectState.state.delayComplete = false; +// shouldStopAnimation = true; +// } +// break; + +// case "Despawn": +// delete processState.spawnedObjects[objectId]; +// shouldStopAnimation = true; + +// // Notify main thread about despawn +// self.postMessage({ +// type: "objectDespawned", +// data: { +// processId, +// objectId, +// }, +// }); +// break; + +// case "Swap": +// if (action.material) { +// handleMaterialSwap(processId, objectId, action.material); +// } +// break; + +// default: +// break; +// } +// }); + +// return shouldStopAnimation; +// } + +// // Check if point has non-inherit actions +// function hasNonInheritActions(actions = []) { +// return actions.some((action) => action.isUsed && action.type !== "Inherit"); +// } + +// // Calculate vector lerp (linear interpolation) +// function lerpVectors(v1, v2, alpha) { +// return { +// x: v1.x + (v2.x - v1.x) * alpha, +// y: v1.y + (v2.y - v1.y) * alpha, +// z: v1.z + (v2.z - v1.z) * alpha, +// }; +// } + +// // Calculate vector distance +// function distanceBetweenVectors(v1, v2) { +// const dx = v2.x - v1.x; +// const dy = v2.y - v1.y; +// const dz = v2.z - v1.z; +// return Math.sqrt(dx * dx + dy * dy + dz * dz); +// } + +// // Process spawn logic +// function processSpawns(currentTime) { +// processes.forEach((process) => { +// const processState = animationStates[process.id]; +// if (!processState) return; + +// const spawnPointData = findSpawnPoint(process); +// if (!spawnPointData || !spawnPointData.point.actions) return; + +// const spawnAction = spawnPointData.point.actions.find( +// (a) => a.isUsed && a.type === "Spawn" +// ); +// if (!spawnAction) return; + +// const spawnInterval = +// typeof spawnAction.spawnInterval === "number" +// ? spawnAction.spawnInterval +// : parseFloat(spawnAction.spawnInterval || "0"); + +// if (currentTime >= processState.nextSpawnTime) { +// const newObject = createSpawnedObject( +// process, +// spawnPointData, +// currentTime, +// spawnAction.material || "Default" +// ); + +// processState.spawnedObjects[newObject.id] = newObject; +// processState.objectIdCounter++; +// processState.nextSpawnTime = currentTime + spawnInterval; + +// // Notify main thread about new object +// self.postMessage({ +// type: "objectSpawned", +// data: { +// processId: process.id, +// object: newObject, +// }, +// }); +// } +// }); +// } + +// // Update all animations +// function updateAnimations(delta, currentTime) { +// // First handle spawning of new objects +// processSpawns(currentTime); + +// // Then animate existing objects +// processes.forEach((process) => { +// const processState = animationStates[process.id]; +// if (!processState) return; + +// const path = getProcessPath(process); +// if (path.length < 2) return; + +// const updatedObjects = {}; +// let hasChanges = false; + +// Object.entries(processState.spawnedObjects).forEach(([objectId, obj]) => { +// if (!obj.visible || !obj.state.isAnimating) return; + +// const stateRef = obj.state; + +// // Use the spawnPointIndex as starting point if it's the initial movement +// if (stateRef.currentIndex === 0 && stateRef.progress === 0) { +// stateRef.currentIndex = stateRef.spawnPointIndex || 0; +// } + +// // Get current point data +// const currentPointData = getPointDataForAnimationIndex( +// process, +// stateRef.currentIndex +// ); + +// // Execute actions when arriving at a new point +// if (stateRef.progress === 0 && currentPointData?.actions) { +// const shouldStop = handlePointActions( +// process.id, +// objectId, +// currentPointData.actions, +// currentTime +// ); +// if (shouldStop) return; +// } + +// // Handle delays +// if (stateRef.isDelaying) { +// if ( +// currentTime - stateRef.delayStartTime >= +// stateRef.currentDelayDuration +// ) { +// stateRef.isDelaying = false; +// stateRef.delayComplete = true; +// } else { +// updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; +// return; // Keep waiting +// } +// } + +// const nextPointIdx = stateRef.currentIndex + 1; +// const isLastPoint = nextPointIdx >= path.length; + +// if (isLastPoint) { +// if (currentPointData?.actions) { +// const shouldStop = !hasNonInheritActions(currentPointData.actions); +// if (shouldStop) { +// // Reached the end of path with no more actions +// delete processState.spawnedObjects[objectId]; + +// // Notify main thread to remove the object +// self.postMessage({ +// type: "objectCompleted", +// data: { +// processId: process.id, +// objectId, +// }, +// }); +// return; +// } +// } +// } + +// if (!isLastPoint) { +// const currentPos = path[stateRef.currentIndex]; +// const nextPos = path[nextPointIdx]; +// const distance = distanceBetweenVectors(currentPos, nextPos); +// const movement = stateRef.speed * delta; + +// // Update progress based on distance and speed +// const oldProgress = stateRef.progress; +// stateRef.progress += movement / distance; + +// if (stateRef.progress >= 1) { +// // Reached next point +// stateRef.currentIndex = nextPointIdx; +// stateRef.progress = 0; +// stateRef.delayComplete = false; +// obj.position = [nextPos.x, nextPos.y, nextPos.z]; +// } else { +// // Interpolate position +// const lerpedPos = lerpVectors(currentPos, nextPos, stateRef.progress); +// obj.position = [lerpedPos.x, lerpedPos.y, lerpedPos.z]; +// } + +// // Only send updates when there's meaningful movement +// if (Math.abs(oldProgress - stateRef.progress) > 0.01) { +// hasChanges = true; +// } +// } + +// updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; +// }); + +// // Update animation state with modified objects +// if (Object.keys(updatedObjects).length > 0) { +// processState.spawnedObjects = { +// ...processState.spawnedObjects, +// ...updatedObjects, +// }; + +// // Only send position updates when there are meaningful changes +// if (hasChanges) { +// self.postMessage({ +// type: "positionsUpdated", +// data: { +// processId: process.id, +// objects: updatedObjects, +// }, +// }); +// } +// } +// }); +// } + +// animation-worker.js +// This web worker handles animation calculations off the main thread + +/* eslint-disable no-restricted-globals */ +// The above disables the ESLint rule for this file since 'self' is valid in web workers + +// Store process data, animation states, and objects +let processes = []; +let animationStates = {}; +let lastTimestamp = 0; +let debugMode = true; + +// Logger function for debugging +function log(...args) { + if (debugMode) { + self.postMessage({ + type: "debug", + data: { message: args.join(' ') } + }); + } +} + +// Message handler for communication with main thread +self.onmessage = function (event) { + const { type, data } = event.data; + log(`Worker received message: ${type}`); + + switch (type) { + case "initialize": + processes = data.processes; + log(`Initialized with ${processes.length} processes`); + initializeAnimationStates(); + break; + + case "update": + const { timestamp, isPlaying } = data; + if (isPlaying) { + const delta = lastTimestamp === 0 ? 0.016 : (timestamp - lastTimestamp) / 1000; // Convert to seconds + updateAnimations(delta, timestamp); + } + lastTimestamp = timestamp; + break; + + case "reset": + log("Resetting animations"); + resetAnimations(); + break; + + case "togglePlay": + // If resuming from pause, recalculate the time delta + log(`Toggle play: ${data.isPlaying}`); + lastTimestamp = data.timestamp; + break; + + case "setDebug": + debugMode = data.enabled; + log(`Debug mode: ${debugMode}`); + break; + } +}; + +// Initialize animation states for all processes +function initializeAnimationStates() { + animationStates = {}; + + processes.forEach((process) => { + if (!process || !process.id) { + log("Invalid process found:", process); + return; + } + + animationStates[process.id] = { + spawnedObjects: {}, + nextSpawnTime: 0, + objectIdCounter: 0, + }; + }); + + // Send initial states back to main thread + self.postMessage({ + type: "statesInitialized", + data: { animationStates }, + }); +} + +// Reset all animations +function resetAnimations() { + initializeAnimationStates(); +} + +// Find spawn point in a process +function findSpawnPoint(process) { + if (!process || !process.paths) { + log(`No paths found for process ${process?.id}`); + return null; + } + + for (const path of process.paths) { + if (!path || !path.points) continue; + + for (const point of path.points) { + if (!point || !point.actions) continue; + + const spawnAction = point.actions.find( + (a) => a && a.isUsed && a.type === "Spawn" + ); + if (spawnAction) { + return { point, path }; + } + } + } + log(`No spawn points found for process ${process.id}`); + return null; +} + +// Create a new spawned object with proper initial position +function createSpawnedObject(process, spawnPoint, currentTime, materialType) { + // Extract spawn position from the actual spawn point + const position = spawnPoint.point.position + ? [...spawnPoint.point.position] + : [0, 0, 0]; + + // Get the path position and add it to the spawn point position + const pathPosition = spawnPoint.path.pathPosition || [0, 0, 0]; + const absolutePosition = [ + position[0] + pathPosition[0], + position[1] + pathPosition[1], + position[2] + pathPosition[2], + ]; + + return { + id: `obj-${process.id}-${animationStates[process.id].objectIdCounter}`, + position: absolutePosition, + state: { + currentIndex: 0, + progress: 0, + isAnimating: true, + speed: process.speed || 1, + isDelaying: false, + delayStartTime: 0, + currentDelayDuration: 0, + delayComplete: false, + currentPathIndex: 0, + // Store the spawn point index to start animation from correct path point + spawnPointIndex: getPointIndexInProcess(process, spawnPoint.point), + }, + visible: true, + materialType: materialType || "Default", + spawnTime: currentTime, + }; +} + +// Get the index of a point within the process animation path +function getPointIndexInProcess(process, point) { + if (!process.paths) return 0; + + let cumulativePoints = 0; + for (const path of process.paths) { + for (let i = 0; i < (path.points?.length || 0); i++) { + if (path.points[i].uuid === point.uuid) { + return cumulativePoints + i; + } + } + cumulativePoints += path.points?.length || 0; + } + + return 0; +} + +// Get point data for current animation index +function getPointDataForAnimationIndex(process, index) { + 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 null; +} + +// Convert process paths to Vector3 format +function getProcessPath(process) { + if (!process.animationPath) { + log(`No animation path for process ${process.id}`); + return []; + } + return process.animationPath.map((p) => ({ x: p.x, y: p.y, z: p.z })) || []; +} + +// Handle material swap for an object +function handleMaterialSwap(processId, objectId, materialType) { + const processState = animationStates[processId]; + if (!processState || !processState.spawnedObjects[objectId]) return; + + processState.spawnedObjects[objectId].materialType = materialType; + + // Notify main thread about material change + self.postMessage({ + type: "materialChanged", + data: { + processId, + objectId, + materialType, + }, + }); +} + +// Handle point actions for an object +function handlePointActions(processId, objectId, actions = [], currentTime) { + let shouldStopAnimation = false; + const processState = animationStates[processId]; + + if (!processState || !processState.spawnedObjects[objectId]) return false; + + const objectState = processState.spawnedObjects[objectId]; + + actions.forEach((action) => { + if (!action || !action.isUsed) return; + + switch (action.type) { + case "Delay": + if (objectState.state.isDelaying) return; + + const delayDuration = + typeof action.delay === "number" + ? action.delay + : parseFloat(action.delay || "0"); + + if (delayDuration > 0) { + objectState.state.isDelaying = true; + objectState.state.delayStartTime = currentTime; + objectState.state.currentDelayDuration = delayDuration; + objectState.state.delayComplete = false; + shouldStopAnimation = true; + } + break; + + case "Despawn": + delete processState.spawnedObjects[objectId]; + shouldStopAnimation = true; + + // Notify main thread about despawn + self.postMessage({ + type: "objectDespawned", + data: { + processId, + objectId, + }, + }); + break; + + case "Swap": + if (action.material) { + handleMaterialSwap(processId, objectId, action.material); + } + break; + + default: + break; + } + }); + + return shouldStopAnimation; +} + +// Check if point has non-inherit actions +function hasNonInheritActions(actions = []) { + return actions.some((action) => action && action.isUsed && action.type !== "Inherit"); +} + +// Calculate vector lerp (linear interpolation) +function lerpVectors(v1, v2, alpha) { + return { + x: v1.x + (v2.x - v1.x) * alpha, + y: v1.y + (v2.y - v1.y) * alpha, + z: v1.z + (v2.z - v1.z) * alpha, + }; +} + +// Calculate vector distance +function distanceBetweenVectors(v1, v2) { + const dx = v2.x - v1.x; + const dy = v2.y - v1.y; + const dz = v2.z - v1.z; + return Math.sqrt(dx * dx + dy * dy + dz * dz); +} + +// Process spawn logic +function processSpawns(currentTime) { + processes.forEach((process) => { + const processState = animationStates[process.id]; + if (!processState) return; + + const spawnPointData = findSpawnPoint(process); + if (!spawnPointData || !spawnPointData.point.actions) return; + + const spawnAction = spawnPointData.point.actions.find( + (a) => a.isUsed && a.type === "Spawn" + ); + if (!spawnAction) return; + + const spawnInterval = + typeof spawnAction.spawnInterval === "number" + ? spawnAction.spawnInterval + : parseFloat(spawnAction.spawnInterval || "2"); // Default to 2 seconds if not specified + + if (currentTime >= processState.nextSpawnTime) { + const newObject = createSpawnedObject( + process, + spawnPointData, + currentTime, + spawnAction.material || "Default" + ); + + processState.spawnedObjects[newObject.id] = newObject; + processState.objectIdCounter++; + processState.nextSpawnTime = currentTime + spawnInterval; + + log(`Spawned object ${newObject.id} for process ${process.id}`); + + // Notify main thread about new object + self.postMessage({ + type: "objectSpawned", + data: { + processId: process.id, + object: newObject, + }, + }); + } + }); +} + +// Update all animations +function updateAnimations(delta, currentTime) { + // First handle spawning of new objects + processSpawns(currentTime); + + // Then animate existing objects + processes.forEach((process) => { + const processState = animationStates[process.id]; + if (!processState) return; + + const path = getProcessPath(process); + if (path.length < 2) { + log(`Path too short for process ${process.id}, length: ${path.length}`); + return; + } + + const updatedObjects = {}; + let hasChanges = false; + + Object.entries(processState.spawnedObjects).forEach(([objectId, obj]) => { + if (!obj.visible || !obj.state.isAnimating) return; + + const stateRef = obj.state; + + // Use the spawnPointIndex as starting point if it's the initial movement + if (stateRef.currentIndex === 0 && stateRef.progress === 0) { + stateRef.currentIndex = stateRef.spawnPointIndex || 0; + } + + // Get current point data + const currentPointData = getPointDataForAnimationIndex( + process, + stateRef.currentIndex + ); + + // Execute actions when arriving at a new point + if (stateRef.progress === 0 && currentPointData?.actions) { + const shouldStop = handlePointActions( + process.id, + objectId, + currentPointData.actions, + currentTime + ); + if (shouldStop) return; + } + + // Handle delays + if (stateRef.isDelaying) { + if ( + currentTime - stateRef.delayStartTime >= + stateRef.currentDelayDuration + ) { + stateRef.isDelaying = false; + stateRef.delayComplete = true; + } else { + updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; + return; // Keep waiting + } + } + + const nextPointIdx = stateRef.currentIndex + 1; + const isLastPoint = nextPointIdx >= path.length; + + if (isLastPoint) { + if (currentPointData?.actions) { + const shouldStop = !hasNonInheritActions(currentPointData.actions); + if (shouldStop) { + // Reached the end of path with no more actions + delete processState.spawnedObjects[objectId]; + log(`Object ${objectId} completed path`); + + // Notify main thread to remove the object + self.postMessage({ + type: "objectCompleted", + data: { + processId: process.id, + objectId, + }, + }); + return; + } + } + } + + if (!isLastPoint) { + const currentPos = path[stateRef.currentIndex]; + const nextPos = path[nextPointIdx]; + const distance = distanceBetweenVectors(currentPos, nextPos); + + // Ensure we don't divide by zero + if (distance > 0) { + const movement = stateRef.speed * delta; + + // Update progress based on distance and speed + const oldProgress = stateRef.progress; + stateRef.progress += movement / distance; + + if (stateRef.progress >= 1) { + // Reached next point + stateRef.currentIndex = nextPointIdx; + stateRef.progress = 0; + stateRef.delayComplete = false; + obj.position = [nextPos.x, nextPos.y, nextPos.z]; + } else { + // Interpolate position + const lerpedPos = lerpVectors(currentPos, nextPos, stateRef.progress); + obj.position = [lerpedPos.x, lerpedPos.y, lerpedPos.z]; + } + + // Only send updates when there's meaningful movement + if (Math.abs(oldProgress - stateRef.progress) > 0.01) { + hasChanges = true; + } + } else { + // Skip to next point if distance is zero + stateRef.currentIndex = nextPointIdx; + stateRef.progress = 0; + obj.position = [nextPos.x, nextPos.y, nextPos.z]; + hasChanges = true; + } + } + + updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; + }); + + // Update animation state with modified objects + if (Object.keys(updatedObjects).length > 0) { + processState.spawnedObjects = { + ...processState.spawnedObjects, + ...updatedObjects, + }; + + // Only send position updates when there are meaningful changes + if (hasChanges) { + self.postMessage({ + type: "positionsUpdated", + data: { + processId: process.id, + objects: updatedObjects, + }, + }); + } + } + }); +} diff --git a/app/src/modules/simulation/process/mesh.tsx b/app/src/modules/simulation/process/mesh.tsx new file mode 100644 index 0000000..f6b94bb --- /dev/null +++ b/app/src/modules/simulation/process/mesh.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const Mesh: React.FC = () => { + return ; +}; + +export default Mesh; diff --git a/app/src/modules/simulation/process/processAnimator.tsx b/app/src/modules/simulation/process/processAnimator.tsx index 123ec80..512b165 100644 --- a/app/src/modules/simulation/process/processAnimator.tsx +++ b/app/src/modules/simulation/process/processAnimator.tsx @@ -1,3 +1,582 @@ +// import React, { useRef, useState, useEffect, useMemo } from "react"; +// import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +// import { GLTFLoader } from "three-stdlib"; +// import { useLoader, useFrame } from "@react-three/fiber"; +// import * as THREE from "three"; +// import { GLTF } from "three-stdlib"; +// import boxGltb from "../../../assets/gltf-glb/crate_box.glb"; + +// interface PointAction { +// uuid: string; +// name: string; +// type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap"; +// objectType: string; +// material: string; +// delay: string | number; +// spawnInterval: string | number; +// isUsed: boolean; +// } + +// interface ProcessPoint { +// uuid: string; +// position: number[]; +// rotation: number[]; +// actions: PointAction[]; +// connections: { +// source: { pathUUID: string; pointUUID: string }; +// targets: { pathUUID: string; pointUUID: string }[]; +// }; +// } + +// interface ProcessPath { +// modeluuid: string; +// modelName: string; +// points: ProcessPoint[]; +// pathPosition: number[]; +// pathRotation: number[]; +// speed: number; +// } + +// interface ProcessData { +// id: string; +// paths: ProcessPath[]; +// animationPath: { x: number; y: number; z: number }[]; +// pointActions: PointAction[][]; +// speed: number; +// customMaterials?: Record; +// renderAs?: "box" | "custom"; +// } + +// interface AnimationState { +// currentIndex: number; +// progress: number; +// isAnimating: boolean; +// speed: number; +// isDelaying: boolean; +// delayStartTime: number; +// currentDelayDuration: number; +// delayComplete: boolean; +// currentPathIndex: number; +// } + +// interface SpawnedObject { +// ref: React.RefObject; +// state: AnimationState; +// visible: boolean; +// material: THREE.Material; +// spawnTime: number; +// currentMaterialType: string; +// position: THREE.Vector3; // The position of the object +// } + +// interface ProcessAnimationState { +// spawnedObjects: { [objectId: string]: SpawnedObject }; +// nextSpawnTime: number; +// objectIdCounter: number; +// } + +// const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ +// processes, +// }) => { +// +// const gltf = useLoader(GLTFLoader, boxGltb) as GLTF; +// const { isPlaying } = usePlayButtonStore(); +// const groupRef = useRef(null); + +// const [animationStates, setAnimationStates] = useState< +// Record +// >({}); + +// // Base materials +// const baseMaterials = useMemo( +// () => ({ +// Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), +// Box: new THREE.MeshStandardMaterial({ +// color: 0xcccccc, +// metalness: 0.8, +// roughness: 0.2, +// }), +// Crate: new THREE.MeshStandardMaterial({ +// color: 0x00aaff, +// metalness: 0.1, +// roughness: 0.5, +// }), +// Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), +// }), +// [] +// ); + +// // Initialize animation states when processes or play state changes +// useEffect(() => { +// if (!isPlaying) { +// setAnimationStates({}); +// return; +// } + +// const newStates: Record = {}; +// processes.forEach((process) => { +// newStates[process.id] = { +// spawnedObjects: {}, +// nextSpawnTime: 0, +// objectIdCounter: 0, +// }; +// }); +// setAnimationStates(newStates); +// }, [isPlaying, processes]); + +// // Find spawn point in a process +// 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; +// }; + +// // Find the corresponding animation path point for a spawn point +// const findAnimationPathPoint = (process: ProcessData, spawnPoint: ProcessPoint): THREE.Vector3 => { +// // If we have an animation path, use the first point +// if (process.animationPath && process.animationPath.length > 0) { +// // Find the index of this point in the path +// let pointIndex = 0; + +// // Try to find the corresponding point in the animation path +// 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) { +// // Found the matching point +// if (process.animationPath[pointIndex]) { +// const p = process.animationPath[pointIndex]; +// return new THREE.Vector3(p.x, p.y, p.z); +// } +// } +// pointIndex++; +// } +// } + +// // Fallback to the spawn point's position +// return new THREE.Vector3( +// spawnPoint.position[0], +// spawnPoint.position[1], +// spawnPoint.position[2] +// ); +// } + +// // If no animation path, use the spawn point's position +// return new THREE.Vector3( +// spawnPoint.position[0], +// spawnPoint.position[1], +// spawnPoint.position[2] +// ); +// }; + +// // Create a new spawned object +// const createSpawnedObject = ( +// process: ProcessData, +// currentTime: number, +// materialType: string, +// spawnPoint: ProcessPoint +// ): SpawnedObject => { +// const processMaterials = { +// ...baseMaterials, +// ...(process.customMaterials || {}), +// }; + +// // Get the position where we should spawn +// const spawnPosition = findAnimationPathPoint(process, spawnPoint); + +// return { +// ref: React.createRef(), +// state: { +// currentIndex: 0, +// progress: 0, +// isAnimating: true, +// speed: process.speed || 1, +// isDelaying: false, +// delayStartTime: 0, +// currentDelayDuration: 0, +// delayComplete: false, +// currentPathIndex: 0, +// }, +// visible: true, +// material: +// processMaterials[materialType as keyof typeof processMaterials] || +// baseMaterials.Default, +// currentMaterialType: materialType, +// spawnTime: currentTime, +// position: spawnPosition, // Store the position directly +// }; +// }; + +// // Handle material swap for an object +// const handleMaterialSwap = ( +// processId: string, +// objectId: string, +// materialType: string +// ) => { +// setAnimationStates((prev) => { +// const processState = prev[processId]; +// if (!processState || !processState.spawnedObjects[objectId]) return prev; + +// const process = processes.find((p) => p.id === processId); +// const processMaterials = { +// ...baseMaterials, +// ...(process?.customMaterials || {}), +// }; + +// const newMaterial = +// processMaterials[materialType as keyof typeof processMaterials] || +// baseMaterials.Default; + +// return { +// ...prev, +// [processId]: { +// ...processState, +// spawnedObjects: { +// ...processState.spawnedObjects, +// [objectId]: { +// ...processState.spawnedObjects[objectId], +// material: newMaterial, +// currentMaterialType: materialType, +// }, +// }, +// }, +// }; +// }); +// }; + +// // Handle point actions for an object +// const handlePointActions = ( +// processId: string, +// objectId: string, +// actions: PointAction[] = [], +// currentTime: number +// ): 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.spawnedObjects[objectId] || +// processState.spawnedObjects[objectId].state.isDelaying +// ) { +// return prev; +// } + +// const delayDuration = +// typeof action.delay === "number" +// ? action.delay +// : parseFloat(action.delay as string) || 0; + +// if (delayDuration > 0) { +// return { +// ...prev, +// [processId]: { +// ...processState, +// spawnedObjects: { +// ...processState.spawnedObjects, +// [objectId]: { +// ...processState.spawnedObjects[objectId], +// state: { +// ...processState.spawnedObjects[objectId].state, +// 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); +// } +// break; + +// default: +// break; +// } +// }); + +// return shouldStopAnimation; +// }; + +// // Check if point has non-inherit actions +// const hasNonInheritActions = (actions: PointAction[] = []): boolean => { +// return actions.some((action) => action.isUsed && action.type !== "Inherit"); +// }; + +// // Get point data for current animation index +// const getPointDataForAnimationIndex = ( +// 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 null; +// }; + +// // Spawn objects for all processes +// useFrame((state) => { +// if (!isPlaying) return; + +// const currentTime = state.clock.getElapsedTime(); +// setAnimationStates((prev) => { +// const newStates = { ...prev }; + +// processes.forEach((process) => { +// const processState = newStates[process.id]; +// if (!processState) return; + +// const spawnPoint = findSpawnPoint(process); +// if (!spawnPoint || !spawnPoint.actions) return; + +// const spawnAction = spawnPoint.actions.find( +// (a) => a.isUsed && a.type === "Spawn" +// ); +// if (!spawnAction) return; + +// const spawnInterval = +// typeof spawnAction.spawnInterval === "number" +// ? spawnAction.spawnInterval +// : parseFloat(spawnAction.spawnInterval as string) || 0; + +// if (currentTime >= processState.nextSpawnTime) { +// const objectId = `obj-${process.id}-${processState.objectIdCounter}`; + +// // Create the new object with the spawn point +// const newObject = createSpawnedObject( +// process, +// currentTime, +// spawnAction.material || "Default", +// spawnPoint +// ); + +// newStates[process.id] = { +// ...processState, +// spawnedObjects: { +// ...processState.spawnedObjects, +// [objectId]: newObject, +// }, +// objectIdCounter: processState.objectIdCounter + 1, +// nextSpawnTime: currentTime + spawnInterval, +// }; +// } +// }); + +// return newStates; +// }); +// }); + +// // Animate objects for all processes +// useFrame((state, delta) => { +// if (!isPlaying) return; + +// const currentTime = state.clock.getElapsedTime(); +// setAnimationStates((prev) => { +// const newStates = { ...prev }; + +// processes.forEach((process) => { +// const processState = newStates[process.id]; +// if (!processState) return; + +// const path = +// process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) || +// []; +// if (path.length < 2) return; + +// const updatedObjects = { ...processState.spawnedObjects }; + +// Object.entries(processState.spawnedObjects).forEach( +// ([objectId, obj]) => { +// if (!obj.visible || !obj.state.isAnimating) return; + +// const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current; +// if (!currentRef) return; + +// // Set the position when the reference is first available +// if (obj.position && obj.state.currentIndex === 0 && obj.state.progress === 0) { +// currentRef.position.copy(obj.position); +// } + +// const stateRef = obj.state; + +// // Get current point data +// const currentPointData = getPointDataForAnimationIndex( +// process, +// stateRef.currentIndex +// ); + +// // Execute actions when arriving at a new point +// if (stateRef.progress === 0 && currentPointData?.actions) { +// const shouldStop = handlePointActions( +// process.id, +// objectId, +// currentPointData.actions, +// currentTime +// ); +// if (shouldStop) return; +// } + +// // Handle delays +// if (stateRef.isDelaying) { +// if ( +// currentTime - stateRef.delayStartTime >= +// stateRef.currentDelayDuration +// ) { +// stateRef.isDelaying = false; +// stateRef.delayComplete = true; +// } else { +// return; // Keep waiting +// } +// } + +// const nextPointIdx = stateRef.currentIndex + 1; +// const isLastPoint = nextPointIdx >= path.length; + +// if (isLastPoint) { +// if (currentPointData?.actions) { +// const shouldStop = !hasNonInheritActions( +// currentPointData.actions +// ); +// if (shouldStop) { +// currentRef.position.copy(path[stateRef.currentIndex]); +// delete updatedObjects[objectId]; +// return; +// } +// } +// } + +// if (!isLastPoint) { +// const nextPoint = path[nextPointIdx]; +// const distance = +// path[stateRef.currentIndex].distanceTo(nextPoint); +// const movement = stateRef.speed * delta; +// stateRef.progress += movement / distance; + +// if (stateRef.progress >= 1) { +// stateRef.currentIndex = nextPointIdx; +// stateRef.progress = 0; +// stateRef.delayComplete = false; +// currentRef.position.copy(nextPoint); +// } else { +// currentRef.position.lerpVectors( +// path[stateRef.currentIndex], +// nextPoint, +// stateRef.progress +// ); +// } +// } + +// updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; +// } +// ); + +// newStates[process.id] = { +// ...processState, +// spawnedObjects: updatedObjects, +// }; +// }); + +// return newStates; +// }); +// }); + +// if (!processes || processes.length === 0) { +// return null; +// } + +// return ( +// <> +// {Object.entries(animationStates).flatMap(([processId, processState]) => +// Object.entries(processState.spawnedObjects) +// .filter(([_, obj]) => obj.visible) +// .map(([objectId, obj]) => { +// const process = processes.find((p) => p.id === processId); +// const renderAs = process?.renderAs || "custom"; + +// return renderAs === "box" ? ( +// } +// material={obj.material} +// position={obj.position} // Set position directly in the JSX +// > +// +// +// ) : ( +// gltf?.scene && ( +// } +// position={obj.position} // Set position directly in the JSX +// > +// +// +// ) +// ); +// }) +// )} +// +// ); +// }; + +// export default ProcessAnimator; + import React, { useRef, useState, useEffect, useMemo } from "react"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import { GLTFLoader } from "three-stdlib"; @@ -5,11 +584,13 @@ import { useLoader, useFrame } from "@react-three/fiber"; import * as THREE from "three"; import { GLTF } from "three-stdlib"; import boxGltb from "../../../assets/gltf-glb/crate_box.glb"; +import camera from "../../../assets/gltf-glb/camera face 2.gltf"; interface PointAction { uuid: string; name: string; type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap"; + objectType: string; material: string; delay: string | number; spawnInterval: string | number; @@ -42,6 +623,8 @@ interface ProcessData { animationPath: { x: number; y: number; z: number }[]; pointActions: PointAction[][]; speed: number; + customMaterials?: Record; + renderAs?: "box" | "custom"; } interface AnimationState { @@ -54,37 +637,46 @@ interface AnimationState { currentDelayDuration: number; delayComplete: boolean; currentPathIndex: number; - spawnPoints: Record< - string, - { - position: THREE.Vector3; - interval: number; - lastSpawnTime: number; - } - >; } -const MAX_SPAWNED_OBJECTS = 20; +interface SpawnedObject { + ref: React.RefObject; + state: AnimationState; + visible: boolean; + material: THREE.Material; + spawnTime: number; + currentMaterialType: string; + position: THREE.Vector3; // The position of the object +} + +interface ProcessAnimationState { + spawnedObjects: { [objectId: string]: SpawnedObject }; + nextSpawnTime: number; + objectIdCounter: number; + // New fields for process-wide delay + isProcessDelaying: boolean; + processDelayStartTime: number; + processDelayDuration: number; +} const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ processes, }) => { - console.log("processes: ", processes); + const gltf = useLoader(GLTFLoader, boxGltb) as GLTF; - const { isPlaying, setIsPlaying } = usePlayButtonStore(); - + const { isPlaying } = usePlayButtonStore(); const groupRef = useRef(null); - const meshRef = useRef(null); - const [visible, setVisible] = useState(false); - const spawnedObjectsRef = useRef([]); - const materials = useMemo( + const [animationStates, setAnimationStates] = useState< + Record + >({}); + + // Base materials + const baseMaterials = useMemo( () => ({ Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), - Box: new THREE.MeshStandardMaterial({ + Box: new THREE.MeshPhongMaterial({ color: 0xcccccc, - metalness: 0.8, - roughness: 0.2, }), Crate: new THREE.MeshStandardMaterial({ color: 0x00aaff, @@ -96,108 +688,166 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ [] ); - const [currentMaterial, setCurrentMaterial] = useState( - materials.Default - ); - - const { animationPath, currentProcess } = useMemo(() => { - const defaultProcess = { - animationPath: [], - pointActions: [], - speed: 1, - paths: [], - }; - const cp = processes?.[0] || defaultProcess; - return { - animationPath: - cp.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) || [], - currentProcess: cp, - }; - }, [processes]); - - const animationStateRef = useRef({ - currentIndex: 0, - progress: 0, - isAnimating: false, - speed: currentProcess.speed, - isDelaying: false, - delayStartTime: 0, - currentDelayDuration: 0, - delayComplete: false, - currentPathIndex: 0, - spawnPoints: {}, - }); - - const getPointDataForAnimationIndex = (index: number) => { - if (!processes[0]?.paths) return null; - - if (index < 3) { - return processes[0].paths[0]?.points[index]; - } else { - const path2Index = index - 3; - return processes[0].paths[1]?.points[path2Index]; + // Initialize animation states when processes or play state changes + useEffect(() => { + if (!isPlaying) { + setAnimationStates({}); + return; } + + const newStates: Record = {}; + processes.forEach((process) => { + newStates[process.id] = { + spawnedObjects: {}, + nextSpawnTime: 0, + objectIdCounter: 0, + // Initialize process-wide delay state + isProcessDelaying: false, + processDelayStartTime: 0, + processDelayDuration: 0, + }; + }); + setAnimationStates(newStates); + }, [isPlaying, processes]); + + // Find spawn point in a process + 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; }; - useEffect(() => { - if (isPlaying) { - setVisible(true); - animationStateRef.current = { + // Find the corresponding animation path point for a spawn point + const findAnimationPathPoint = ( + process: ProcessData, + spawnPoint: ProcessPoint + ): THREE.Vector3 => { + // If we have an animation path, use the first point + if (process.animationPath && process.animationPath.length > 0) { + // Find the index of this point in the path + let pointIndex = 0; + + // Try to find the corresponding point in the animation path + 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) { + // Found the matching point + if (process.animationPath[pointIndex]) { + const p = process.animationPath[pointIndex]; + return new THREE.Vector3(p.x, p.y, p.z); + } + } + pointIndex++; + } + } + + // Fallback to the spawn point's position + return new THREE.Vector3( + spawnPoint.position[0], + spawnPoint.position[1], + spawnPoint.position[2] + ); + } + + // If no animation path, use the spawn point's position + return new THREE.Vector3( + spawnPoint.position[0], + spawnPoint.position[1], + spawnPoint.position[2] + ); + }; + + // Create a new spawned object + const createSpawnedObject = ( + process: ProcessData, + currentTime: number, + materialType: string, + spawnPoint: ProcessPoint + ): SpawnedObject => { + const processMaterials = { + ...baseMaterials, + ...(process.customMaterials || {}), + }; + + // Get the position where we should spawn + const spawnPosition = findAnimationPathPoint(process, spawnPoint); + + return { + ref: React.createRef(), + state: { currentIndex: 0, progress: 0, isAnimating: true, - speed: currentProcess.speed, + speed: process.speed || 1, isDelaying: false, delayStartTime: 0, currentDelayDuration: 0, delayComplete: false, currentPathIndex: 0, - spawnPoints: {}, + }, + visible: true, + material: + processMaterials[materialType as keyof typeof processMaterials] || + baseMaterials.Default, + currentMaterialType: materialType, + spawnTime: currentTime, + position: spawnPosition, // Store the position directly + }; + }; + + // Handle material swap for an object + const handleMaterialSwap = ( + processId: string, + objectId: string, + materialType: string + ) => { + setAnimationStates((prev) => { + const processState = prev[processId]; + if (!processState || !processState.spawnedObjects[objectId]) return prev; + + const process = processes.find((p) => p.id === processId); + const processMaterials = { + ...baseMaterials, + ...(process?.customMaterials || {}), }; - // Clear spawned objects - if (groupRef.current) { - spawnedObjectsRef.current.forEach((obj) => { - if (groupRef.current?.children.includes(obj)) { - groupRef.current.remove(obj); - } - if (obj instanceof THREE.Mesh) { - obj.material.dispose(); - } - }); - spawnedObjectsRef.current = []; - } + const newMaterial = + processMaterials[materialType as keyof typeof processMaterials] || + baseMaterials.Default; - const currentRef = gltf?.scene ? groupRef.current : meshRef.current; - if (currentRef && animationPath.length > 0) { - currentRef.position.copy(animationPath[0]); - } - } else { - animationStateRef.current.isAnimating = false; - } - }, [isPlaying, currentProcess, animationPath]); - - const handleMaterialSwap = (materialType: string) => { - const newMaterial = - materials[materialType as keyof typeof materials] || materials.Default; - setCurrentMaterial(newMaterial); - - spawnedObjectsRef.current.forEach((obj) => { - if (obj instanceof THREE.Mesh) { - obj.material = newMaterial.clone(); - } + return { + ...prev, + [processId]: { + ...processState, + spawnedObjects: { + ...processState.spawnedObjects, + [objectId]: { + ...processState.spawnedObjects[objectId], + material: newMaterial, + currentMaterialType: materialType, + }, + }, + }, + }; }); }; - const hasNonInheritActions = (actions: PointAction[] = []) => { - return actions.some((action) => action.isUsed && action.type !== "Inherit"); - }; - + // Handle point actions for an object const handlePointActions = ( + processId: string, + objectId: string, actions: PointAction[] = [], - currentTime: number, - currentPosition: THREE.Vector3 - ) => { + currentTime: number + ): boolean => { let shouldStopAnimation = false; actions.forEach((action) => { @@ -205,53 +855,75 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ switch (action.type) { case "Delay": - if ( - !animationStateRef.current.isDelaying && - !animationStateRef.current.delayComplete - ) { + 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) { - animationStateRef.current.isDelaying = true; - animationStateRef.current.delayStartTime = currentTime; - animationStateRef.current.currentDelayDuration = delayDuration; - shouldStopAnimation = true; + return { + ...prev, + [processId]: { + ...processState, + // Set process-wide delay instead of object-specific delay + isProcessDelaying: true, + processDelayStartTime: currentTime, + processDelayDuration: delayDuration, + // Update the specific object's state as well + spawnedObjects: { + ...processState.spawnedObjects, + [objectId]: { + ...processState.spawnedObjects[objectId], + state: { + ...processState.spawnedObjects[objectId].state, + isAnimating: false, // Explicitly pause animation during delay + isDelaying: true, + delayStartTime: currentTime, + currentDelayDuration: delayDuration, + delayComplete: false, + }, + }, + }, + }, + }; } - } - break; - - case "Despawn": - setVisible(false); - setIsPlaying(false); - animationStateRef.current.isAnimating = false; + return prev; + }); shouldStopAnimation = true; break; - case "Spawn": - const spawnInterval = - typeof action.spawnInterval === "number" - ? action.spawnInterval - : parseFloat(action.spawnInterval as string) || 1; + case "Despawn": + setAnimationStates((prev) => { + const processState = prev[processId]; + if (!processState) return prev; - const positionKey = currentPosition.toArray().join(","); + const newSpawnedObjects = { ...processState.spawnedObjects }; + delete newSpawnedObjects[objectId]; - animationStateRef.current.spawnPoints[positionKey] = { - position: currentPosition.clone(), - interval: spawnInterval, - lastSpawnTime: currentTime - spawnInterval, // Force immediate spawn - }; + return { + ...prev, + [processId]: { + ...processState, + spawnedObjects: newSpawnedObjects, + }, + }; + }); + shouldStopAnimation = true; break; case "Swap": if (action.material) { - handleMaterialSwap(action.material); + handleMaterialSwap(processId, objectId, action.material); } break; - case "Inherit": + default: break; } }); @@ -259,158 +931,376 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ return shouldStopAnimation; }; - useFrame((state, delta) => { - const currentRef = gltf?.scene ? groupRef.current : meshRef.current; - if ( - !currentRef || - !animationStateRef.current.isAnimating || - animationPath.length < 2 - ) { - return; + // Check if point has non-inherit actions + const hasNonInheritActions = (actions: PointAction[] = []): boolean => { + return actions.some((action) => action.isUsed && action.type !== "Inherit"); + }; + + // Get point data for current animation index + const getPointDataForAnimationIndex = ( + 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 null; + }; + + // Spawn objects for all processes + useFrame((state) => { + if (!isPlaying) return; + const currentTime = state.clock.getElapsedTime(); - const path = animationPath; - const stateRef = animationStateRef.current; + setAnimationStates((prev) => { + const newStates = { ...prev }; - if (stateRef.currentIndex === 3 && stateRef.currentPathIndex === 0) { - stateRef.currentPathIndex = 1; - } + processes.forEach((process) => { + const processState = newStates[process.id]; + if (!processState) return; - const currentPointData = getPointDataForAnimationIndex( - stateRef.currentIndex - ); - - if (stateRef.progress === 0 && currentPointData?.actions) { - const shouldStop = handlePointActions( - currentPointData.actions, - currentTime, - currentRef.position - ); - if (shouldStop) return; - } - - if (stateRef.isDelaying) { - if ( - currentTime - stateRef.delayStartTime >= - stateRef.currentDelayDuration - ) { - stateRef.isDelaying = false; - stateRef.delayComplete = true; - } else { - return; - } - } - - // Handle spawning - this is the key updated part - Object.entries(stateRef.spawnPoints).forEach(([key, spawnPoint]) => { - if (currentTime - spawnPoint.lastSpawnTime >= spawnPoint.interval) { - spawnPoint.lastSpawnTime = currentTime; - - if (gltf?.scene && groupRef?.current) { - const newObject = gltf.scene.clone(); - newObject.position.copy(spawnPoint.position); - - newObject.traverse((child) => { - if (child instanceof THREE.Mesh) { - child.material = currentMaterial.clone(); - } - }); - - groupRef.current.add(newObject); - spawnedObjectsRef.current.push(newObject); - - // Clean up old objects if needed - console.log( - "spawnedObjectsRef.current.length: ", - spawnedObjectsRef.current.length - ); - if (spawnedObjectsRef.current.length > MAX_SPAWNED_OBJECTS) { - const oldest = spawnedObjectsRef.current.shift(); - if (oldest && groupRef.current.children.includes(oldest)) { - groupRef.current.remove(oldest); - if (oldest instanceof THREE.Mesh) { - oldest.material.dispose(); - } - } + // Skip spawning if the process is currently in a delay + if (processState.isProcessDelaying) { + // Check if delay is over + if ( + currentTime - processState.processDelayStartTime >= + processState.processDelayDuration + ) { + // Reset process delay state + newStates[process.id] = { + ...processState, + isProcessDelaying: false, + // Reset delay state on all objects in this process + spawnedObjects: Object.entries( + processState.spawnedObjects + ).reduce( + (acc, [id, obj]) => ({ + ...acc, + [id]: { + ...obj, + state: { + ...obj.state, + isDelaying: false, + delayComplete: true, + isAnimating: true, // Ensure animation resumes + // Force a small progress to ensure movement starts + progress: + obj.state.progress === 0 ? 0.001 : obj.state.progress, + }, + }, + }), + {} + ), + }; } + return; // Skip spawning while delaying } - } - }); - const nextPointIdx = stateRef.currentIndex + 1; - const isLastPoint = nextPointIdx >= path.length; + const spawnPoint = findSpawnPoint(process); + if (!spawnPoint || !spawnPoint.actions) return; - if (isLastPoint) { - if (currentPointData?.actions) { - const shouldStop = !hasNonInheritActions(currentPointData.actions); - if (shouldStop) { - currentRef.position.copy(path[stateRef.currentIndex]); - setIsPlaying(false); - stateRef.isAnimating = false; - return; - } - } - } - - if (!isLastPoint) { - const nextPoint = path[nextPointIdx]; - const distance = path[stateRef.currentIndex].distanceTo(nextPoint); - const movement = stateRef.speed * delta; - stateRef.progress += movement / distance; - - if (stateRef.progress >= 1) { - stateRef.currentIndex = nextPointIdx; - stateRef.progress = 0; - stateRef.delayComplete = false; - currentRef.position.copy(nextPoint); - } else { - currentRef.position.lerpVectors( - path[stateRef.currentIndex], - nextPoint, - stateRef.progress + const spawnAction = spawnPoint.actions.find( + (a) => a.isUsed && a.type === "Spawn" ); - } - } + if (!spawnAction) return; + + const spawnInterval = + typeof spawnAction.spawnInterval === "number" + ? spawnAction.spawnInterval + : parseFloat(spawnAction.spawnInterval as string) || 0; + + if (currentTime >= processState.nextSpawnTime) { + const objectId = `obj-${process.id}-${processState.objectIdCounter}`; + + // Create the new object with the spawn point + const newObject = createSpawnedObject( + process, + currentTime, + spawnAction.material || "Default", + spawnPoint + ); + + newStates[process.id] = { + ...processState, + spawnedObjects: { + ...processState.spawnedObjects, + [objectId]: newObject, + }, + objectIdCounter: processState.objectIdCounter + 1, + nextSpawnTime: currentTime + spawnInterval, + }; + } + }); + + return newStates; + }); }); - useEffect(() => { - return () => { - if (groupRef.current) { - spawnedObjectsRef.current.forEach((obj) => { - if (groupRef.current?.children.includes(obj)) { - groupRef.current.remove(obj); + // Animate objects for all processes + useFrame((state, delta) => { + if (!isPlaying) return; + + const currentTime = state.clock.getElapsedTime(); + setAnimationStates((prev) => { + const newStates = { ...prev }; + + processes.forEach((process) => { + const processState = newStates[process.id]; + if (!processState) return; + + // Check if the process-wide delay is active + if (processState.isProcessDelaying) { + // Check if the delay has completed + if ( + currentTime - processState.processDelayStartTime >= + processState.processDelayDuration + ) { + // Reset process delay state AND resume animation + newStates[process.id] = { + ...processState, + isProcessDelaying: false, + // Reset delay state on all objects in this process AND ensure isAnimating is true + spawnedObjects: Object.entries( + processState.spawnedObjects + ).reduce( + (acc, [id, obj]) => ({ + ...acc, + [id]: { + ...obj, + state: { + ...obj.state, + isDelaying: false, + delayComplete: true, + isAnimating: true, // Ensure animation resumes + // Important: Force progress to a small positive value to ensure movement + progress: + obj.state.progress === 0 ? 0.005 : obj.state.progress, + }, + }, + }), + {} + ), + }; + // Skip the rest of the processing for this frame to allow the state update to take effect + return newStates; + } else { + // If we're still in a process-wide delay, don't animate anything + return newStates; } - if (obj instanceof THREE.Mesh) { - obj.material.dispose(); + } + + const path = + process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) || + []; + if (path.length < 2) return; + + const updatedObjects = { ...processState.spawnedObjects }; + + Object.entries(processState.spawnedObjects).forEach( + ([objectId, obj]) => { + // Skip objects that are explicitly not visible + if (!obj.visible) return; + + const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current; + if (!currentRef) return; + + // Set the position when the reference is first available + if ( + obj.position && + obj.state.currentIndex === 0 && + obj.state.progress === 0 + ) { + currentRef.position.copy(obj.position); + } + + const stateRef = obj.state; + + // Check if we're delaying at the object level and update accordingly + if (stateRef.isDelaying) { + if ( + currentTime - stateRef.delayStartTime >= + stateRef.currentDelayDuration + ) { + // Delay is complete, resume animation + stateRef.isDelaying = false; + stateRef.delayComplete = true; + stateRef.isAnimating = true; // Explicitly resume animation + + // Force movement from the current point by setting progress to a small value + // if we're at the start of a segment + if (stateRef.progress === 0) { + stateRef.progress = 0.005; + } + + // Force an immediate position update to ensure visually accurate position + const nextPointIdx = stateRef.currentIndex + 1; + if (nextPointIdx < path.length) { + // Calculate the position slightly ahead of the current point + const slightProgress = Math.max(stateRef.progress, 0.005); + currentRef.position.lerpVectors( + path[stateRef.currentIndex], + nextPointIdx < path.length + ? path[nextPointIdx] + : path[stateRef.currentIndex], + slightProgress + ); + } + } else { + // Still delaying, don't animate this object + updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; + return; + } + } + + // Skip animation if the object shouldn't be animating + if (!stateRef.isAnimating) return; + + // Get current point data + const currentPointData = getPointDataForAnimationIndex( + process, + stateRef.currentIndex + ); + + // Execute actions when arriving at a new point + if (stateRef.progress === 0 && currentPointData?.actions) { + const shouldStop = handlePointActions( + process.id, + objectId, + currentPointData.actions, + currentTime + ); + if (shouldStop) { + updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; + return; + } + } + + const nextPointIdx = stateRef.currentIndex + 1; + const isLastPoint = nextPointIdx >= path.length; + + if (isLastPoint) { + if (currentPointData?.actions) { + const shouldStop = !hasNonInheritActions( + currentPointData.actions + ); + if (shouldStop) { + // uncomment this or write own logic to handle the object when reaching the last point of the process + + // currentRef.position.copy(path[stateRef.currentIndex]); + // delete updatedObjects[objectId]; + return; + } + } + } + + if (!isLastPoint) { + const nextPoint = path[nextPointIdx]; + const distance = + path[stateRef.currentIndex].distanceTo(nextPoint); + const movement = stateRef.speed * delta; + + // If we just resumed from a delay, ensure we make actual progress + if (stateRef.delayComplete && stateRef.progress < 0.01) { + // Boost initial movement after delay to ensure visible progress + stateRef.progress = 0.05; // Small but visible initial progress + stateRef.delayComplete = false; // Reset flag so we don't do this again + } else { + // Normal progress calculation + stateRef.progress += movement / distance; + } + + if (stateRef.progress >= 1) { + // We've reached the next point + stateRef.currentIndex = nextPointIdx; + stateRef.progress = 0; + currentRef.position.copy(nextPoint); + + // Check if we need to execute actions at this new point + const newPointData = getPointDataForAnimationIndex( + process, + stateRef.currentIndex + ); + + if (newPointData?.actions) { + // We've arrived at a new point with actions, handle them in the next frame + // We don't call handlePointActions directly here to avoid state update issues + // The actions will be handled in the next frame when progress is 0 + } + } else { + // Normal path interpolation + currentRef.position.lerpVectors( + path[stateRef.currentIndex], + nextPoint, + stateRef.progress + ); + } + } + + updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; } - }); - spawnedObjectsRef.current = []; - } - }; - }, []); + ); + + newStates[process.id] = { + ...processState, + spawnedObjects: updatedObjects, + }; + }); + + return newStates; + }); + }); if (!processes || processes.length === 0) { return null; } - if (!gltf?.scene) { - return visible ? ( - - - - ) : null; - } + return ( + <> + {Object.entries(animationStates).flatMap(([processId, processState]) => + Object.entries(processState.spawnedObjects) + .filter(([_, obj]) => obj.visible) + .map(([objectId, obj]) => { + const process = processes.find((p) => p.id === processId); + const renderAs = process?.renderAs || "custom"; - return visible ? ( - - - - ) : null; + return renderAs === "box" ? ( + } + material={obj.material} + position={obj.position} // Set position directly in the JSX + > + + + ) : ( + gltf?.scene && ( + } + position={obj.position} // Set position directly in the JSX + > + + + ) + ); + }) + )} + + ); }; export default ProcessAnimator; diff --git a/app/src/modules/simulation/process/processContainer.tsx b/app/src/modules/simulation/process/processContainer.tsx index 967376c..bf8fa48 100644 --- a/app/src/modules/simulation/process/processContainer.tsx +++ b/app/src/modules/simulation/process/processContainer.tsx @@ -10,6 +10,7 @@ const ProcessContainer: React.FC = () => { <> {processes.length > 0 && } + ); }; diff --git a/app/src/modules/simulation/process/processCreator.tsx b/app/src/modules/simulation/process/processCreator.tsx index 8e1ed1d..91bc4c1 100644 --- a/app/src/modules/simulation/process/processCreator.tsx +++ b/app/src/modules/simulation/process/processCreator.tsx @@ -1,3 +1,402 @@ +// import React, { +// useEffect, +// useMemo, +// useState, +// useCallback, +// useRef, +// } from "react"; +// import { useSimulationPaths } from "../../../store/store"; +// import * as THREE from "three"; +// import { useThree } from "@react-three/fiber"; +// import { +// ConveyorEventsSchema, +// VehicleEventsSchema, +// } from "../../../types/world/worldTypes"; + +// // Type definitions +// export interface PointAction { +// uuid: string; +// name: string; +// type: string; +// material: string; +// delay: number | string; +// spawnInterval: string | number; +// isUsed: boolean; +// } + +// export interface PathPoint { +// uuid: string; +// position: [number, number, number]; +// actions: PointAction[]; +// connections: { +// targets: Array<{ pathUUID: string }>; +// }; +// } + +// export interface SimulationPath { +// modeluuid: string; +// points: PathPoint[]; +// pathPosition: [number, number, number]; +// speed?: number; +// } + +// export interface Process { +// id: string; +// paths: SimulationPath[]; +// animationPath: THREE.Vector3[]; +// pointActions: PointAction[][]; +// speed: number; +// } + +// interface ProcessCreatorProps { +// onProcessesCreated: (processes: Process[]) => void; +// } + +// // Convert event schemas to SimulationPath +// function convertToSimulationPath( +// path: ConveyorEventsSchema | VehicleEventsSchema +// ): SimulationPath { +// const { modeluuid } = path; + +// // Simplified normalizeAction function that preserves exact original properties +// const normalizeAction = (action: any): PointAction => { +// return { ...action }; // Return exact copy with no modifications +// }; + +// if (path.type === "Conveyor") { +// return { +// modeluuid, +// points: path.points.map((point) => ({ +// uuid: point.uuid, +// position: point.position, +// actions: point.actions.map(normalizeAction), // Preserve exact actions +// connections: { +// targets: point.connections.targets.map((target) => ({ +// pathUUID: target.pathUUID, +// })), +// }, +// })), +// pathPosition: path.position, +// speed: +// typeof path.speed === "string" +// ? parseFloat(path.speed) || 1 +// : path.speed || 1, +// }; +// } else { +// return { +// modeluuid, +// points: [ +// { +// uuid: path.point.uuid, +// position: path.point.position, +// actions: Array.isArray(path.point.actions) +// ? path.point.actions.map(normalizeAction) +// : [normalizeAction(path.point.actions)], +// connections: { +// targets: path.point.connections.targets.map((target) => ({ +// pathUUID: target.pathUUID, +// })), +// }, +// }, +// ], +// pathPosition: path.position, +// speed: path.point.speed || 1, +// }; +// } +// } + +// // Custom shallow comparison for arrays +// const areArraysEqual = (a: any[], b: any[]) => { +// if (a.length !== b.length) return false; +// for (let i = 0; i < a.length; i++) { +// if (a[i] !== b[i]) return false; +// } +// return true; +// }; + +// // Helper function to create an empty process +// const createEmptyProcess = (): Process => ({ +// id: `process-${Math.random().toString(36).substring(2, 11)}`, +// paths: [], +// animationPath: [], +// pointActions: [], +// speed: 1, +// }); + +// // Enhanced connection checking function +// function shouldReverseNextPath( +// currentPath: SimulationPath, +// nextPath: SimulationPath +// ): boolean { +// if (nextPath.points.length !== 3) return false; + +// const currentLastPoint = currentPath.points[currentPath.points.length - 1]; +// const nextFirstPoint = nextPath.points[0]; +// const nextLastPoint = nextPath.points[nextPath.points.length - 1]; + +// // Check if current last connects to next last (requires reversal) +// const connectsToLast = currentLastPoint.connections.targets.some( +// (target) => +// target.pathUUID === nextPath.modeluuid && +// nextLastPoint.connections.targets.some( +// (t) => t.pathUUID === currentPath.modeluuid +// ) +// ); + +// // Check if current last connects to next first (no reversal needed) +// const connectsToFirst = currentLastPoint.connections.targets.some( +// (target) => +// target.pathUUID === nextPath.modeluuid && +// nextFirstPoint.connections.targets.some( +// (t) => t.pathUUID === currentPath.modeluuid +// ) +// ); + +// // Only reverse if connected to last point and not to first point +// return connectsToLast && !connectsToFirst; +// } + +// // Updated path adjustment function +// function adjustPathPointsOrder(paths: SimulationPath[]): SimulationPath[] { +// if (paths.length < 2) return paths; + +// const adjustedPaths = [...paths]; + +// for (let i = 0; i < adjustedPaths.length - 1; i++) { +// const currentPath = adjustedPaths[i]; +// const nextPath = adjustedPaths[i + 1]; + +// if (shouldReverseNextPath(currentPath, nextPath)) { +// const reversedPoints = [ +// nextPath.points[2], +// nextPath.points[1], +// nextPath.points[0], +// ]; + +// adjustedPaths[i + 1] = { +// ...nextPath, +// points: reversedPoints, +// }; +// } +// } + +// return adjustedPaths; +// } + +// // Main hook for process creation +// export function useProcessCreation() { +// const { scene } = useThree(); +// const [processes, setProcesses] = useState([]); + +// const hasSpawnAction = useCallback((path: SimulationPath): boolean => { +// return path.points.some((point) => +// point.actions.some((action) => action.type.toLowerCase() === "spawn") +// ); +// }, []); + +// const createProcess = useCallback( +// (paths: SimulationPath[]): Process => { +// if (!paths || paths.length === 0) { +// return createEmptyProcess(); +// } + +// const animationPath: THREE.Vector3[] = []; +// const pointActions: PointAction[][] = []; +// const processSpeed = paths[0]?.speed || 1; + +// for (const path of paths) { +// for (const point of path.points) { +// const obj = scene.getObjectByProperty("uuid", point.uuid); +// if (!obj) { +// console.warn(`Object with UUID ${point.uuid} not found in scene`); +// continue; +// } + +// const position = obj.getWorldPosition(new THREE.Vector3()); +// animationPath.push(position.clone()); +// pointActions.push(point.actions); +// } +// } + +// return { +// id: `process-${Math.random().toString(36).substring(2, 11)}`, +// paths, +// animationPath, +// pointActions, +// speed: processSpeed, +// }; +// }, +// [scene] +// ); + +// const getAllConnectedPaths = useCallback( +// ( +// initialPath: SimulationPath, +// allPaths: SimulationPath[], +// visited: Set = new Set() +// ): SimulationPath[] => { +// const connectedPaths: SimulationPath[] = []; +// const queue: SimulationPath[] = [initialPath]; +// visited.add(initialPath.modeluuid); + +// const pathMap = new Map(); +// allPaths.forEach((path) => pathMap.set(path.modeluuid, path)); + +// while (queue.length > 0) { +// const currentPath = queue.shift()!; +// connectedPaths.push(currentPath); + +// // Process outgoing connections +// for (const point of currentPath.points) { +// for (const target of point.connections.targets) { +// if (!visited.has(target.pathUUID)) { +// const targetPath = pathMap.get(target.pathUUID); +// if (targetPath) { +// visited.add(target.pathUUID); +// queue.push(targetPath); +// } +// } +// } +// } + +// // Process incoming connections +// for (const [uuid, path] of pathMap) { +// if (!visited.has(uuid)) { +// const hasConnectionToCurrent = path.points.some((point) => +// point.connections.targets.some( +// (t) => t.pathUUID === currentPath.modeluuid +// ) +// ); +// if (hasConnectionToCurrent) { +// visited.add(uuid); +// queue.push(path); +// } +// } +// } +// } + +// return connectedPaths; +// }, +// [] +// ); + +// const createProcessesFromPaths = useCallback( +// (paths: SimulationPath[]): Process[] => { +// if (!paths || paths.length === 0) return []; + +// const visited = new Set(); +// const processes: Process[] = []; +// const pathMap = new Map(); +// paths.forEach((path) => pathMap.set(path.modeluuid, path)); + +// for (const path of paths) { +// if (!visited.has(path.modeluuid) && hasSpawnAction(path)) { +// const connectedPaths = getAllConnectedPaths(path, paths, visited); +// const adjustedPaths = adjustPathPointsOrder(connectedPaths); +// const process = createProcess(adjustedPaths); +// processes.push(process); +// } +// } + +// return processes; +// }, +// [createProcess, getAllConnectedPaths, hasSpawnAction] +// ); + +// return { +// processes, +// createProcessesFromPaths, +// setProcesses, +// }; +// } + +// const ProcessCreator: React.FC = React.memo( +// ({ onProcessesCreated }) => { +// const { simulationPaths } = useSimulationPaths(); +// const { createProcessesFromPaths } = useProcessCreation(); +// const prevPathsRef = useRef([]); +// const prevProcessesRef = useRef([]); + +// const convertedPaths = useMemo((): SimulationPath[] => { +// if (!simulationPaths) return []; +// return simulationPaths.map((path) => +// convertToSimulationPath( +// path as ConveyorEventsSchema | VehicleEventsSchema +// ) +// ); +// }, [simulationPaths]); + +// const pathsDependency = useMemo(() => { +// if (!convertedPaths) return null; +// return convertedPaths.map((path) => ({ +// id: path.modeluuid, +// hasSpawn: path.points.some((p: PathPoint) => +// p.actions.some((a: PointAction) => a.type.toLowerCase() === "spawn") +// ), +// connections: path.points +// .flatMap((p: PathPoint) => +// p.connections.targets.map((t: { pathUUID: string }) => t.pathUUID) +// ) +// .join(","), +// })); +// }, [convertedPaths]); + +// useEffect(() => { +// if (!convertedPaths || convertedPaths.length === 0) { +// if (prevProcessesRef.current.length > 0) { +// onProcessesCreated([]); +// prevProcessesRef.current = []; +// } +// return; +// } + +// if (areArraysEqual(prevPathsRef.current, convertedPaths)) { +// return; +// } + +// prevPathsRef.current = convertedPaths; +// const newProcesses = createProcessesFromPaths(convertedPaths); + +// // console.log("--- Action Types in Paths ---"); +// // convertedPaths.forEach((path) => { +// // path.points.forEach((point) => { +// // point.actions.forEach((action) => { +// // console.log( +// // `Path ${path.modeluuid}, Point ${point.uuid}: ${action.type}` +// // ); +// // }); +// // }); +// // }); +// // console.log("New processes:", newProcesses); + +// if ( +// newProcesses.length !== prevProcessesRef.current.length || +// !newProcesses.every( +// (proc, i) => +// proc.paths.length === prevProcessesRef.current[i]?.paths.length && +// proc.paths.every( +// (path, j) => +// path.modeluuid === +// prevProcessesRef.current[i]?.paths[j]?.modeluuid +// ) +// ) +// ) { +// onProcessesCreated(newProcesses); +// // prevProcessesRef.current = newProcesses; +// } +// }, [ +// pathsDependency, +// onProcessesCreated, +// convertedPaths, +// createProcessesFromPaths, +// ]); + +// return null; +// } +// ); + +// export default ProcessCreator; + import React, { useEffect, useMemo, @@ -156,12 +555,38 @@ function shouldReverseNextPath( return connectsToLast && !connectsToFirst; } +// Check if a point has a spawn action +function hasSpawnAction(point: PathPoint): boolean { + return point.actions.some((action) => action.type.toLowerCase() === "spawn"); +} + +// Ensure spawn point is always at the beginning of the path +function ensureSpawnPointIsFirst(path: SimulationPath): SimulationPath { + if (path.points.length !== 3) return path; + + // If the third point has spawn action and first doesn't, reverse the array + if (hasSpawnAction(path.points[2]) && !hasSpawnAction(path.points[0])) { + return { + ...path, + points: [...path.points].reverse(), + }; + } + + return path; +} + // Updated path adjustment function function adjustPathPointsOrder(paths: SimulationPath[]): SimulationPath[] { - if (paths.length < 2) return paths; + if (paths.length < 1) return paths; const adjustedPaths = [...paths]; + // First ensure all paths have spawn points at the beginning + for (let i = 0; i < adjustedPaths.length; i++) { + adjustedPaths[i] = ensureSpawnPointIsFirst(adjustedPaths[i]); + } + + // Then handle connections between paths for (let i = 0; i < adjustedPaths.length - 1; i++) { const currentPath = adjustedPaths[i]; const nextPath = adjustedPaths[i + 1]; @@ -326,13 +751,17 @@ const ProcessCreator: React.FC = React.memo( ); }, [simulationPaths]); + // Enhanced dependency tracking that includes action types const pathsDependency = useMemo(() => { if (!convertedPaths) return null; return convertedPaths.map((path) => ({ id: path.modeluuid, - hasSpawn: path.points.some((p: PathPoint) => - p.actions.some((a: PointAction) => a.type.toLowerCase() === "spawn") - ), + // Track all action types for each point + actionSignature: path.points + .map((point, index) => + point.actions.map((action) => `${index}-${action.type}`).join("|") + ) + .join(","), connections: path.points .flatMap((p: PathPoint) => p.connections.targets.map((t: { pathUUID: string }) => t.pathUUID) @@ -341,6 +770,7 @@ const ProcessCreator: React.FC = React.memo( })); }, [convertedPaths]); + // Force process recreation when paths change useEffect(() => { if (!convertedPaths || convertedPaths.length === 0) { if (prevProcessesRef.current.length > 0) { @@ -350,42 +780,16 @@ const ProcessCreator: React.FC = React.memo( return; } - if (areArraysEqual(prevPathsRef.current, convertedPaths)) { - return; - } - - prevPathsRef.current = convertedPaths; + // Always regenerate processes if the pathsDependency has changed + // This ensures action type changes will be detected const newProcesses = createProcessesFromPaths(convertedPaths); + prevPathsRef.current = convertedPaths; - // console.log("--- Action Types in Paths ---"); - // convertedPaths.forEach((path) => { - // path.points.forEach((point) => { - // point.actions.forEach((action) => { - // console.log( - // `Path ${path.modeluuid}, Point ${point.uuid}: ${action.type}` - // ); - // }); - // }); - // }); - // console.log("New processes:", newProcesses); - - if ( - newProcesses.length !== prevProcessesRef.current.length || - !newProcesses.every( - (proc, i) => - proc.paths.length === prevProcessesRef.current[i]?.paths.length && - proc.paths.every( - (path, j) => - path.modeluuid === - prevProcessesRef.current[i]?.paths[j]?.modeluuid - ) - ) - ) { - onProcessesCreated(newProcesses); - // prevProcessesRef.current = newProcesses; - } + // Always update processes when action types change + onProcessesCreated(newProcesses); + prevProcessesRef.current = newProcesses; }, [ - pathsDependency, + pathsDependency, // This now includes action types onProcessesCreated, convertedPaths, createProcessesFromPaths, diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index fd8b520..830a0b1 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -19,7 +19,7 @@ function Simulation() { const [processes, setProcesses] = useState([]); useEffect(() => { - // console.log('simulationPaths: ', simulationPaths); + console.log('simulationPaths: ', simulationPaths); }, [simulationPaths]); // useEffect(() => { diff --git a/app/src/modules/visualization/handleSaveTemplate.ts b/app/src/modules/visualization/handleSaveTemplate.ts index 6a3bad4..e5f90ab 100644 --- a/app/src/modules/visualization/handleSaveTemplate.ts +++ b/app/src/modules/visualization/handleSaveTemplate.ts @@ -30,7 +30,7 @@ export const handleSaveTemplate = async ({ }: HandleSaveTemplateProps): Promise => { try { // Check if the selected zone has any widgets - if (!selectedZone.widgets || selectedZone.widgets.length === 0) { + if (!selectedZone.panelOrder || selectedZone.panelOrder.length === 0) { console.warn("No widgets found in the selected zone."); return; } diff --git a/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts b/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts new file mode 100644 index 0000000..86c8f71 --- /dev/null +++ b/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts @@ -0,0 +1,32 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const setEventApi = async ( + organization: string, + modeluuid: string, + eventData: any +) => { + try { + const body: any = { organization, modeluuid, eventData }; + + const response = await fetch(`${url_Backend_dwinzo}/api/v2/eventDataUpdate`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error("Failed to set or update Floor Item"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; \ No newline at end of file diff --git a/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts b/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts index 75583cc..e25e05e 100644 --- a/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts +++ b/app/src/services/factoryBuilder/assest/floorAsset/setFloorItemApi.ts @@ -30,7 +30,6 @@ export const setFloorItemApi = async ( } const result = await response.json(); - console.log('result: ', result); return result; } catch (error) { if (error instanceof Error) { diff --git a/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts b/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts index 5c18031..9eae5cb 100644 --- a/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts +++ b/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts @@ -1,7 +1,7 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; // let url_Backend_dwinzo = `http://192.168.0.102:5000`; export const saveTemplateApi = async (organization: string, template: {}) => { - console.log('template: ', template); + console.log('template: ', template); try { const response = await fetch(`${url_Backend_dwinzo}/api/v2/template/save`, { method: "POST", @@ -16,7 +16,7 @@ export const saveTemplateApi = async (organization: string, template: {}) => { } const result = await response.json(); - console.log('result: ', result); + return result; } catch (error) { if (error instanceof Error) { diff --git a/app/src/store/store.ts b/app/src/store/store.ts index bd903f2..365eb97 100644 --- a/app/src/store/store.ts +++ b/app/src/store/store.ts @@ -343,14 +343,21 @@ export const useSelectedPath = create((set: any) => ({ interface SimulationPathsStore { simulationPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]; setSimulationPaths: ( - paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[] + paths: + | (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[] + | ((prev: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[] + ) => (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) ) => void; } export const useSimulationPaths = create((set) => ({ simulationPaths: [], - setSimulationPaths: (paths) => set({ simulationPaths: paths }), -})); + setSimulationPaths: (paths) => + set((state) => ({ + simulationPaths: + typeof paths === "function" ? paths(state.simulationPaths) : paths, + })), +})) export const useIsConnecting = create((set: any) => ({ isConnecting: false, diff --git a/app/src/styles/components/lists.scss b/app/src/styles/components/lists.scss index a9b0145..cf66d2b 100644 --- a/app/src/styles/components/lists.scss +++ b/app/src/styles/components/lists.scss @@ -3,15 +3,23 @@ .dropdown-list-container { border-bottom: 1px solid var(--border-color); + + .lists-container { + margin-bottom: 6px; + } + &:last-child { border: none; } + .head { @include flex-space-between; padding: 6px 12px; + .options { @include flex-center; gap: 6px; + .option { @include flex-center; cursor: pointer; @@ -22,34 +30,52 @@ } .list-wrapper { + + .no-item { padding: 12px; } + .list-container { padding: 2px; + // margin-left: 10px; + overflow: hidden; + + .list-item { @include flex-space-between; width: 100%; text-align: center; - padding: 4px 12px; + padding: 4px 8px; border-radius: #{$border-radius-large}; + .value { width: 100%; text-align: start; max-width: 180px; } + .options-container { @include flex-center; gap: 6px; + .option { @include flex-center; cursor: pointer; } } } + .active { background-color: var(--highlight-accent-color); color: var(--primary-color); } } -} + + .asset-list { + border-left: 2px solid var(--border-color); + + margin-left: 20px + } + +} \ No newline at end of file diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index 63297c9..d4cf595 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -253,6 +253,7 @@ .user-profile-container { display: flex; + .user-profile { background: var(--accent-color); color: var(--primary-color); @@ -483,6 +484,9 @@ height: 150px; background: #f0f0f0; // border-radius: 8px; + display: flex; + align-items: center; + // justify-content: center; } .optionsContainer { @@ -497,7 +501,8 @@ justify-content: space-between; gap: 12px; - .regularDropdown-container { + .regularDropdown-container, + input { width: 160px; } @@ -686,6 +691,7 @@ font-weight: var(--font-weight-regular); padding: 8px 0; } + .input-toggle-container { padding: 0; margin-bottom: 6px; @@ -1009,11 +1015,9 @@ top: 50%; right: -10px; transform: translate(0, -50%); - background: linear-gradient( - 144.19deg, - #f1e7cd 16.62%, - #fffaef 85.81% - ); + background: linear-gradient(144.19deg, + #f1e7cd 16.62%, + #fffaef 85.81%); } .category-image { @@ -1117,11 +1121,13 @@ } } } + .assets-result { width: 100%; height: 100%; margin: 8px 10px; + .assets-wrapper { margin: 0; } -} +} \ No newline at end of file diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index 74ef66e..6909081 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -61,7 +61,7 @@ border-radius: 8px; max-width: 80%; overflow: auto; - // max-width: calc(100% - 450px); + max-width: calc(100% - 500px); &::-webkit-scrollbar { display: none; @@ -166,14 +166,16 @@ .panel { position: absolute; - background: white; + background: var(--background-color); box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; border-radius: 6px; - overflow: visible !important; + overflow: auto; z-index: $z-index-tools; overflow: auto; - + &::-webkit-scrollbar { + display: none; + } .panel-content { position: relative; height: 100%; @@ -319,6 +321,7 @@ right: 0; top: 0; bottom: 0; + } } @@ -715,6 +718,13 @@ z-index: 2 !important; } +.connectionSuccess { + outline-color: #43C06D; +} + +.connectionFails { + outline-color: #ffe3e0; +} .editWidgetOptions {