diff --git a/app/package-lock.json b/app/package-lock.json
index fa04c9f..3d5aea4 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -30,7 +30,6 @@
"glob": "^11.0.0",
"gsap": "^3.12.5",
"html2canvas": "^1.4.1",
- "immer": "^10.1.1",
"leva": "^0.10.0",
"mqtt": "^5.10.4",
"postprocessing": "^6.36.4",
@@ -2022,7 +2021,7 @@
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
@@ -2034,7 +2033,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"
@@ -4137,26 +4136,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",
@@ -4268,25 +4247,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",
@@ -9040,7 +9019,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",
@@ -9917,7 +9896,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"
}
@@ -12747,10 +12726,9 @@
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
"node_modules/immer": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
- "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
- "license": "MIT",
+ "version": "9.0.21",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
+ "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
@@ -15281,7 +15259,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",
@@ -18012,16 +17990,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/react-dev-utils/node_modules/immer": {
- "version": "9.0.21",
- "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
- "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/immer"
- }
- },
"node_modules/react-dev-utils/node_modules/loader-utils": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz",
@@ -20759,7 +20727,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",
@@ -20802,7 +20770,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"
},
@@ -20814,7 +20782,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",
@@ -21310,7 +21278,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",
@@ -22369,7 +22337,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/src/assets/gltf-glb/arm_ui_drop.glb b/app/src/assets/gltf-glb/arm_ui_drop.glb
new file mode 100644
index 0000000..2a75e7e
Binary files /dev/null and b/app/src/assets/gltf-glb/arm_ui_drop.glb differ
diff --git a/app/src/assets/gltf-glb/arm_ui_pick.glb b/app/src/assets/gltf-glb/arm_ui_pick.glb
new file mode 100644
index 0000000..979ade4
Binary files /dev/null and b/app/src/assets/gltf-glb/arm_ui_pick.glb differ
diff --git a/app/src/components/icons/analysis.tsx b/app/src/components/icons/analysis.tsx
new file mode 100644
index 0000000..3a5542b
--- /dev/null
+++ b/app/src/components/icons/analysis.tsx
@@ -0,0 +1,93 @@
+export function ThroughputSummaryIcon() {
+ return (
+
+ );
+}
+export function ProductionCapacityIcon() {
+ return (
+
+ );
+}
+export function ROISummaryIcon() {
+ return (
+
+ );
+}
+export function PowerIcon() {
+ return (
+
+ );
+}
diff --git a/app/src/components/ui/analysis/ProductionCapacity.tsx b/app/src/components/ui/analysis/ProductionCapacity.tsx
new file mode 100644
index 0000000..268ea81
--- /dev/null
+++ b/app/src/components/ui/analysis/ProductionCapacity.tsx
@@ -0,0 +1,62 @@
+import React from "react";
+import { ProductionCapacityIcon } from "../../icons/analysis";
+
+const ProductionCapacity = () => {
+ const totalBars = 6;
+ const progressPercent = 50;
+
+ const barsToFill = Math.floor((progressPercent / 100) * totalBars);
+ const partialFillPercent =
+ ((progressPercent / 100) * totalBars - barsToFill) * 100;
+
+ return (
+
+
+
+
+
Throughput Summary
+
08:00 - 09:00 AM
+
+
+
+
+
+
+ 128 Units/hour
+
+
+ {/* Progress Bar */}
+
+ {[...Array(totalBars)].map((_, i) => (
+
+ {i < barsToFill ? (
+
+ ) : i === barsToFill ? (
+
+ ) : null}
+
+ ))}
+
+
+
+
+
+ Avg. Process Time
+ 28.4 Secs/unit
+
+
+ Machine Utilization
+ 78%
+
+
+
+
+ );
+};
+
+export default ProductionCapacity;
diff --git a/app/src/components/ui/analysis/ROISummary.tsx b/app/src/components/ui/analysis/ROISummary.tsx
new file mode 100644
index 0000000..331eaaf
--- /dev/null
+++ b/app/src/components/ui/analysis/ROISummary.tsx
@@ -0,0 +1,22 @@
+import React from "react";
+import { ROISummaryIcon } from "../../icons/analysis";
+
+const ROISummary = () => {
+ return (
+
+
+
+
+
ROI Summary
+
From 24 November, 2025
+
+
+
+
+
+
+
+ );
+};
+
+export default ROISummary;
diff --git a/app/src/components/ui/analysis/ThroughputSummary.tsx b/app/src/components/ui/analysis/ThroughputSummary.tsx
new file mode 100644
index 0000000..cb4fac5
--- /dev/null
+++ b/app/src/components/ui/analysis/ThroughputSummary.tsx
@@ -0,0 +1,146 @@
+import React from "react";
+import { Line } from "react-chartjs-2";
+import {
+ Chart as ChartJS,
+ LineElement,
+ CategoryScale,
+ LinearScale,
+ PointElement,
+} from "chart.js";
+import { PowerIcon, ThroughputSummaryIcon } from "../../icons/analysis";
+
+ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement);
+
+const ThroughputSummary = () => {
+ const data = {
+ labels: ["08:00", "08:10", "08:20", "08:30", "08:40", "08:50", "09:00"],
+ datasets: [
+ {
+ label: "Units/hour",
+ data: [100, 120, 110, 130, 125, 128, 132],
+ borderColor: "#B392F0",
+ tension: 0.4,
+ pointRadius: 0, // hide points
+ },
+ ],
+ };
+
+ const options = {
+ responsive: true,
+ scales: {
+ x: {
+ grid: {
+ display: false,
+ },
+ ticks: {
+ display: false,
+ color: "#fff",
+ },
+ },
+ y: {
+ grid: {
+ display: false,
+ },
+ ticks: {
+ display: false,
+ color: "#fff",
+ },
+ },
+ },
+ plugins: {
+ legend: {
+ display: false,
+ },
+ tooltip: {
+ enabled: true,
+ },
+ },
+ };
+
+ const shiftUtilization = {
+ "shift 1": 25,
+ "shift 2": 45,
+ "shift 3": 15,
+ };
+
+ return (
+
+
+
+
+
Throughput Summary
+
08:00 - 09:00 AM
+
+
+
+
+
+
+
+
+ 1240 Units/hour
+
+
+
+
+
+
+
+
Shift Utilization
+
+
85%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ThroughputSummary;
diff --git a/app/src/components/ui/arm/PickDropPoints.tsx b/app/src/components/ui/arm/PickDropPoints.tsx
new file mode 100644
index 0000000..4544a46
--- /dev/null
+++ b/app/src/components/ui/arm/PickDropPoints.tsx
@@ -0,0 +1,54 @@
+import React, { useRef } from "react";
+import * as THREE from "three";
+import { ThreeEvent } from "@react-three/fiber";
+
+interface PickDropProps {
+ position: number[];
+ modelUuid: string;
+ pointUuid: string;
+ actionType: "pick" | "drop";
+ actionUuid: string;
+ gltfScene: THREE.Group;
+ selectedPoint: THREE.Mesh | null;
+ handlePointerDown: (e: ThreeEvent) => void;
+ isSelected: boolean;
+}
+
+const PickDropPoints: React.FC = ({
+ position,
+ modelUuid,
+ pointUuid,
+ actionType,
+ actionUuid,
+ gltfScene,
+ selectedPoint,
+ handlePointerDown,
+ isSelected,
+}) => {
+ const groupRef = useRef(null);
+
+ return (
+ {
+ e.stopPropagation(); // Important to prevent event bubbling
+ if (!isSelected) return;
+ handlePointerDown(e);
+ }}
+ userData={{ modelUuid, pointUuid, actionType, actionUuid }}
+ >
+
+
+ );
+};
+
+export default PickDropPoints;
diff --git a/app/src/components/ui/arm/useDraggableGLTF.ts b/app/src/components/ui/arm/useDraggableGLTF.ts
new file mode 100644
index 0000000..b7e9272
--- /dev/null
+++ b/app/src/components/ui/arm/useDraggableGLTF.ts
@@ -0,0 +1,131 @@
+import { useRef } from "react";
+import * as THREE from "three";
+import { ThreeEvent, useThree } from "@react-three/fiber";
+
+type OnUpdateCallback = (object: THREE.Object3D) => void;
+
+export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
+ const { camera, gl, controls, scene } = useThree();
+ const activeObjRef = useRef(null);
+ const planeRef = useRef(
+ new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
+ );
+ const offsetRef = useRef(new THREE.Vector3());
+ const initialPositionRef = useRef(new THREE.Vector3());
+
+ const raycaster = new THREE.Raycaster();
+ const pointer = new THREE.Vector2();
+
+ const handlePointerDown = (e: ThreeEvent) => {
+ e.stopPropagation();
+
+ let obj: THREE.Object3D | null = e.object;
+
+ // Traverse up until we find modelUuid in userData
+ while (obj && !obj.userData?.modelUuid) {
+ obj = obj.parent;
+ }
+
+ if (!obj) return;
+
+ // Disable orbit controls while dragging
+ if (controls) (controls as any).enabled = false;
+
+ activeObjRef.current = obj;
+ initialPositionRef.current.copy(obj.position);
+
+ // Get world position
+ const objectWorldPos = new THREE.Vector3();
+ obj.getWorldPosition(objectWorldPos);
+
+ // Set plane at the object's Y level
+ planeRef.current.set(new THREE.Vector3(0, 1, 0), -objectWorldPos.y);
+
+ // Convert pointer to NDC
+ const rect = gl.domElement.getBoundingClientRect();
+ pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
+ pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
+
+ // Raycast to intersection
+ raycaster.setFromCamera(pointer, camera);
+ const intersection = new THREE.Vector3();
+ raycaster.ray.intersectPlane(planeRef.current, intersection);
+
+ // Calculate offset
+ offsetRef.current.copy(objectWorldPos).sub(intersection);
+
+ // Start listening for drag
+ gl.domElement.addEventListener("pointermove", handlePointerMove);
+ gl.domElement.addEventListener("pointerup", handlePointerUp);
+ };
+
+ const handlePointerMove = (e: PointerEvent) => {
+ if (!activeObjRef.current) return;
+
+ // Check if Shift key is pressed
+ const isShiftKeyPressed = e.shiftKey;
+
+ // Get the mouse position relative to the canvas
+ const rect = gl.domElement.getBoundingClientRect();
+ pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
+ pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
+
+ // Update raycaster to point to the mouse position
+ raycaster.setFromCamera(pointer, camera);
+
+ // Create a vector to store intersection point
+ const intersection = new THREE.Vector3();
+ const intersects = raycaster.ray.intersectPlane(planeRef.current, intersection);
+ if (!intersects) return;
+
+ // Add offset for dragging
+ intersection.add(offsetRef.current);
+ console.log('intersection: ', intersection);
+
+ // Get the parent's world matrix if exists
+ const parent = activeObjRef.current.parent;
+ const targetPosition = new THREE.Vector3();
+
+ if (isShiftKeyPressed) {
+ console.log('isShiftKeyPressed: ', isShiftKeyPressed);
+ // For Y-axis only movement, maintain original X and Z
+ console.log('initialPositionRef: ', initialPositionRef);
+ console.log('intersection.y: ', intersection);
+ targetPosition.set(
+ initialPositionRef.current.x,
+ intersection.y,
+ initialPositionRef.current.z
+ );
+ } else {
+ // For free movement
+ targetPosition.copy(intersection);
+ }
+
+ // Convert world position to local if object is nested inside a parent
+ if (parent) {
+ parent.worldToLocal(targetPosition);
+ }
+
+ // Update object position
+ activeObjRef.current.position.copy(targetPosition);
+ };
+
+ const handlePointerUp = () => {
+ if (controls) (controls as any).enabled = true;
+
+ if (activeObjRef.current) {
+ // Pass the updated position to the onUpdate callback to persist it
+ onUpdate(activeObjRef.current);
+ }
+
+ gl.domElement.removeEventListener("pointermove", handlePointerMove);
+ gl.domElement.removeEventListener("pointerup", handlePointerUp);
+
+ activeObjRef.current = null;
+ };
+
+ return { handlePointerDown };
+}
+
+
+
diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx
index e5ff1c1..d367493 100644
--- a/app/src/modules/builder/builder.tsx
+++ b/app/src/modules/builder/builder.tsx
@@ -18,20 +18,20 @@ import Window from "../../assets/gltf-glb/window.glb";
////////// Zustand State Imports //////////
import {
- useToggleView,
- useDeletePointOrLine,
- useMovePoint,
- useActiveLayer,
- useSocketStore,
- useWallVisibility,
- useRoofVisibility,
- useShadows,
- useUpdateScene,
- useWalls,
- useToolMode,
- useRefTextUpdate,
- useRenderDistance,
- useLimitDistance,
+ useToggleView,
+ useDeletePointOrLine,
+ useMovePoint,
+ useActiveLayer,
+ useSocketStore,
+ useWallVisibility,
+ useRoofVisibility,
+ useShadows,
+ useUpdateScene,
+ useWalls,
+ useToolMode,
+ useRefTextUpdate,
+ useRenderDistance,
+ useLimitDistance,
} from "../../store/store";
////////// 3D Function Imports //////////
@@ -56,300 +56,301 @@ import ZoneGroup from "./groups/zoneGroup";
import useModuleStore from "../../store/useModuleStore";
import MeasurementTool from "../scene/tools/measurementTool";
import NavMesh from "../simulation/vehicle/navMesh/navMesh";
+import ProductionCapacity from "../../components/ui/analysis/ProductionCapacity";
export default function Builder() {
- const state = useThree(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
- const csg = useRef(); // Reference for CSG object, used for 3D modeling.
- const CSGGroup = useRef() as Types.RefMesh; // Reference to a group of CSG objects.
- const scene = useRef() as Types.RefScene; // Reference to the scene.
- const camera = useRef() as Types.RefCamera; // Reference to the camera object.
- const controls = useRef(); // Reference to the controls object.
- const raycaster = useRef() as Types.RefRaycaster; // Reference for raycaster used for detecting objects being pointed at in the scene.
- const dragPointControls = useRef() as Types.RefDragControl; // Reference for drag point controls, an array for drag control.
+ const state = useThree(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
+ const csg = useRef(); // Reference for CSG object, used for 3D modeling.
+ const CSGGroup = useRef() as Types.RefMesh; // Reference to a group of CSG objects.
+ const scene = useRef() as Types.RefScene; // Reference to the scene.
+ const camera = useRef() as Types.RefCamera; // Reference to the camera object.
+ const controls = useRef(); // Reference to the controls object.
+ const raycaster = useRef() as Types.RefRaycaster; // Reference for raycaster used for detecting objects being pointed at in the scene.
+ const dragPointControls = useRef() as Types.RefDragControl; // Reference for drag point controls, an array for drag control.
- // Assigning the scene and camera from the Three.js state to the references.
+ // Assigning the scene and camera from the Three.js state to the references.
- scene.current = state.scene;
- camera.current = state.camera;
- controls.current = state.controls;
- raycaster.current = state.raycaster;
+ scene.current = state.scene;
+ camera.current = state.camera;
+ controls.current = state.controls;
+ raycaster.current = state.raycaster;
- const plane = useRef(null); // Reference for a plane object for raycaster reference.
- const grid = useRef() as any; // Reference for a grid object for raycaster reference.
- const snappedPoint = useRef() as Types.RefVector3; // Reference for storing a snapped point at the (end = isSnapped) and (start = ispreSnapped) of the line.
- const isSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (end).
- const anglesnappedPoint = useRef() as Types.RefVector3; // Reference for storing an angle-snapped point when the line is in 90 degree etc...
- const isAngleSnapped = useRef(false) as Types.RefBoolean; // Boolean to indicate if angle snapping is active.
- const isSnappedUUID = useRef() as Types.RefString; // UUID reference to identify the snapped point.
- const ispreSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (start).
- const tempLoader = useRef() as Types.RefMesh; // Reference for a temporary loader for the floor items.
- const isTempLoader = useRef() as Types.RefBoolean; // Reference to check if a temporary loader is active.
- const Tube = useRef() as Types.RefTubeGeometry; // Reference for tubes used for reference line creation and updation.
- const line = useRef([]) as Types.RefLine; // Reference for line which stores the current line that is being drawn.
- const lines = useRef([]) as Types.RefLines; // Reference for lines which stores all the lines that are ever drawn.
- const onlyFloorline = useRef([]); // Reference for floor lines which does not have walls or roof and have only floor used to store the current line that is being drawn.
- const onlyFloorlines = useRef([]); // Reference for all the floor lines that are ever drawn.
- const ReferenceLineMesh = useRef() as Types.RefMesh; // Reference for storing the mesh of the reference line for moving it during draw.
- const LineCreated = useRef(false) as Types.RefBoolean; // Boolean to track whether the reference line is created or not.
- const referencePole = useRef() as Types.RefMesh; // Reference for a pole that is used as the reference for the user to show where it is placed.
- const itemsGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the floor items (Gltf).
- const floorGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the roofs and the floors.
- const AttachedObject = useRef() as Types.RefMesh; // Reference for an object that is attached using dbl click for transform controls rotation.
- const floorPlanGroup = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines group and the points group.
- const floorPlanGroupLine = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines that are drawn.
- const floorPlanGroupPoint = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the points that are created.
- const floorGroupAisle = useRef() as Types.RefGroup;
- const zoneGroup = useRef() as Types.RefGroup;
- const currentLayerPoint = useRef([]) as Types.RefMeshArray; // Reference for points that re in the current layer used to update the points in drag controls.
- const hoveredDeletablePoint = useRef() as Types.RefMesh; // Reference for the currently hovered point that can be deleted.
- const hoveredDeletableLine = useRef() as Types.RefMesh; // Reference for the currently hovered line that can be deleted.
- const hoveredDeletableFloorItem = useRef() as Types.RefMesh; // Reference for the currently hovered floor item that can be deleted.
- const hoveredDeletableWallItem = useRef() as Types.RefMesh; // Reference for the currently hovered wall item that can be deleted.
- const hoveredDeletablePillar = useRef() as Types.RefMesh; // Reference for the currently hovered pillar that can be deleted.
- const currentWallItem = useRef() as Types.RefMesh; // Reference for the currently selected wall item that can be scaled, dragged etc...
+ const plane = useRef(null); // Reference for a plane object for raycaster reference.
+ const grid = useRef() as any; // Reference for a grid object for raycaster reference.
+ const snappedPoint = useRef() as Types.RefVector3; // Reference for storing a snapped point at the (end = isSnapped) and (start = ispreSnapped) of the line.
+ const isSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (end).
+ const anglesnappedPoint = useRef() as Types.RefVector3; // Reference for storing an angle-snapped point when the line is in 90 degree etc...
+ const isAngleSnapped = useRef(false) as Types.RefBoolean; // Boolean to indicate if angle snapping is active.
+ const isSnappedUUID = useRef() as Types.RefString; // UUID reference to identify the snapped point.
+ const ispreSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (start).
+ const tempLoader = useRef() as Types.RefMesh; // Reference for a temporary loader for the floor items.
+ const isTempLoader = useRef() as Types.RefBoolean; // Reference to check if a temporary loader is active.
+ const Tube = useRef() as Types.RefTubeGeometry; // Reference for tubes used for reference line creation and updation.
+ const line = useRef([]) as Types.RefLine; // Reference for line which stores the current line that is being drawn.
+ const lines = useRef([]) as Types.RefLines; // Reference for lines which stores all the lines that are ever drawn.
+ const onlyFloorline = useRef([]); // Reference for floor lines which does not have walls or roof and have only floor used to store the current line that is being drawn.
+ const onlyFloorlines = useRef([]); // Reference for all the floor lines that are ever drawn.
+ const ReferenceLineMesh = useRef() as Types.RefMesh; // Reference for storing the mesh of the reference line for moving it during draw.
+ const LineCreated = useRef(false) as Types.RefBoolean; // Boolean to track whether the reference line is created or not.
+ const referencePole = useRef() as Types.RefMesh; // Reference for a pole that is used as the reference for the user to show where it is placed.
+ const itemsGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the floor items (Gltf).
+ const floorGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the roofs and the floors.
+ const AttachedObject = useRef() as Types.RefMesh; // Reference for an object that is attached using dbl click for transform controls rotation.
+ const floorPlanGroup = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines group and the points group.
+ const floorPlanGroupLine = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines that are drawn.
+ const floorPlanGroupPoint = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the points that are created.
+ const floorGroupAisle = useRef() as Types.RefGroup;
+ const zoneGroup = useRef() as Types.RefGroup;
+ const currentLayerPoint = useRef([]) as Types.RefMeshArray; // Reference for points that re in the current layer used to update the points in drag controls.
+ const hoveredDeletablePoint = useRef() as Types.RefMesh; // Reference for the currently hovered point that can be deleted.
+ const hoveredDeletableLine = useRef() as Types.RefMesh; // Reference for the currently hovered line that can be deleted.
+ const hoveredDeletableFloorItem = useRef() as Types.RefMesh; // Reference for the currently hovered floor item that can be deleted.
+ const hoveredDeletableWallItem = useRef() as Types.RefMesh; // Reference for the currently hovered wall item that can be deleted.
+ const hoveredDeletablePillar = useRef() as Types.RefMesh; // Reference for the currently hovered pillar that can be deleted.
+ const currentWallItem = useRef() as Types.RefMesh; // Reference for the currently selected wall item that can be scaled, dragged etc...
- const cursorPosition = new THREE.Vector3(); // 3D vector for storing the cursor position.
+ const cursorPosition = new THREE.Vector3(); // 3D vector for storing the cursor position.
- const [selectedItemsIndex, setSelectedItemsIndex] = useState(null); // State for tracking the index of the selected item.
- const { activeLayer, setActiveLayer } = useActiveLayer(); // State that changes based on which layer the user chooses in Layers.jsx.
- const { toggleView, setToggleView } = useToggleView(); // State for toggling between 2D and 3D.
- const { toolMode, setToolMode } = useToolMode();
- const { movePoint, setMovePoint } = useMovePoint(); // State that stores a boolean which represents whether the move mode is active or not.
- const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
- const { socket } = useSocketStore();
- const { roofVisibility, setRoofVisibility } = useRoofVisibility();
- const { wallVisibility, setWallVisibility } = useWallVisibility();
- const { shadows, setShadows } = useShadows();
- const { renderDistance, setRenderDistance } = useRenderDistance();
- const { limitDistance, setLimitDistance } = useLimitDistance();
- const { updateScene, setUpdateScene } = useUpdateScene();
- const { walls, setWalls } = useWalls();
- const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
- const { activeModule } = useModuleStore();
+ const [selectedItemsIndex, setSelectedItemsIndex] =
+ useState(null); // State for tracking the index of the selected item.
+ const { activeLayer, setActiveLayer } = useActiveLayer(); // State that changes based on which layer the user chooses in Layers.jsx.
+ const { toggleView, setToggleView } = useToggleView(); // State for toggling between 2D and 3D.
+ const { toolMode, setToolMode } = useToolMode();
+ const { movePoint, setMovePoint } = useMovePoint(); // State that stores a boolean which represents whether the move mode is active or not.
+ const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
+ const { socket } = useSocketStore();
+ const { roofVisibility, setRoofVisibility } = useRoofVisibility();
+ const { wallVisibility, setWallVisibility } = useWallVisibility();
+ const { shadows, setShadows } = useShadows();
+ const { renderDistance, setRenderDistance } = useRenderDistance();
+ const { limitDistance, setLimitDistance } = useLimitDistance();
+ const { updateScene, setUpdateScene } = useUpdateScene();
+ const { walls, setWalls } = useWalls();
+ const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
+ const { activeModule } = useModuleStore();
- // const loader = new GLTFLoader();
- // const dracoLoader = new DRACOLoader();
+ // const loader = new GLTFLoader();
+ // const dracoLoader = new DRACOLoader();
- // dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
- // loader.setDRACOLoader(dracoLoader);
+ // dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
+ // loader.setDRACOLoader(dracoLoader);
- ////////// Assest Configuration Values //////////
+ ////////// Assest Configuration Values //////////
- const AssetConfigurations: Types.AssetConfigurations = {
- arch: {
- modelUrl: arch,
- scale: [0.75, 0.75, 0.75],
- csgscale: [2, 4, 0.5],
- csgposition: [0, 2, 0],
- positionY: () => 0,
- type: "Fixed-Move",
- },
- door: {
- modelUrl: door,
- scale: [0.75, 0.75, 0.75],
- csgscale: [2, 4, 0.5],
- csgposition: [0, 2, 0],
- positionY: () => 0,
- type: "Fixed-Move",
- },
- window: {
- modelUrl: Window,
- scale: [0.75, 0.75, 0.75],
- csgscale: [5, 3, 0.5],
- csgposition: [0, 1.5, 0],
- positionY: (intersectionPoint) => intersectionPoint.point.y,
- type: "Free-Move",
- },
- };
+ const AssetConfigurations: Types.AssetConfigurations = {
+ arch: {
+ modelUrl: arch,
+ scale: [0.75, 0.75, 0.75],
+ csgscale: [2, 4, 0.5],
+ csgposition: [0, 2, 0],
+ positionY: () => 0,
+ type: "Fixed-Move",
+ },
+ door: {
+ modelUrl: door,
+ scale: [0.75, 0.75, 0.75],
+ csgscale: [2, 4, 0.5],
+ csgposition: [0, 2, 0],
+ positionY: () => 0,
+ type: "Fixed-Move",
+ },
+ window: {
+ modelUrl: Window,
+ scale: [0.75, 0.75, 0.75],
+ csgscale: [5, 3, 0.5],
+ csgposition: [0, 1.5, 0],
+ positionY: (intersectionPoint) => intersectionPoint.point.y,
+ type: "Free-Move",
+ },
+ };
- ////////// All Toggle's //////////
+ ////////// All Toggle's //////////
- useEffect(() => {
- setRefTextUpdate((prevUpdate: number) => prevUpdate - 1);
- if (dragPointControls.current) {
- dragPointControls.current.enabled = false;
- }
- if (toggleView) {
- Layer2DVisibility(
- activeLayer,
- floorPlanGroup,
- floorPlanGroupLine,
- floorPlanGroupPoint,
- currentLayerPoint,
- dragPointControls
- );
- } else {
- setToolMode(null);
- setDeletePointOrLine(false);
- setMovePoint(false);
- loadWalls(lines, setWalls);
- setUpdateScene(true);
- line.current = [];
- }
- }, [toggleView]);
+ useEffect(() => {
+ setRefTextUpdate((prevUpdate: number) => prevUpdate - 1);
+ if (dragPointControls.current) {
+ dragPointControls.current.enabled = false;
+ }
+ if (toggleView) {
+ Layer2DVisibility(
+ activeLayer,
+ floorPlanGroup,
+ floorPlanGroupLine,
+ floorPlanGroupPoint,
+ currentLayerPoint,
+ dragPointControls
+ );
+ } else {
+ setToolMode(null);
+ setDeletePointOrLine(false);
+ setMovePoint(false);
+ loadWalls(lines, setWalls);
+ setUpdateScene(true);
+ line.current = [];
+ }
+ }, [toggleView]);
- useEffect(() => {
- THREE.Cache.clear();
- THREE.Cache.enabled = true;
- }, []);
+ useEffect(() => {
+ THREE.Cache.clear();
+ THREE.Cache.enabled = true;
+ }, []);
- useEffect(() => {
- const email = localStorage.getItem("email");
- const organization = email!.split("@")[1].split(".")[0];
+ useEffect(() => {
+ const email = localStorage.getItem("email");
+ const organization = email!.split("@")[1].split(".")[0];
- async function fetchVisibility() {
- const visibility = await findEnvironment(
- organization,
- localStorage.getItem("userId")!
- );
- if (visibility) {
- setRoofVisibility(visibility.roofVisibility);
- setWallVisibility(visibility.wallVisibility);
- setShadows(visibility.shadowVisibility);
- setRenderDistance(visibility.renderDistance);
- setLimitDistance(visibility.limitDistance);
- }
- }
- fetchVisibility();
- }, []);
+ async function fetchVisibility() {
+ const visibility = await findEnvironment(
+ organization,
+ localStorage.getItem("userId")!
+ );
+ if (visibility) {
+ setRoofVisibility(visibility.roofVisibility);
+ setWallVisibility(visibility.wallVisibility);
+ setShadows(visibility.shadowVisibility);
+ setRenderDistance(visibility.renderDistance);
+ setLimitDistance(visibility.limitDistance);
+ }
+ }
+ fetchVisibility();
+ }, []);
- ////////// UseFrame is Here //////////
+ ////////// UseFrame is Here //////////
- useFrame(() => {
- if (toolMode) {
- Draw(
- state,
- plane,
- cursorPosition,
- floorPlanGroupPoint,
- floorPlanGroupLine,
- snappedPoint,
- isSnapped,
- isSnappedUUID,
- line,
- lines,
- ispreSnapped,
- floorPlanGroup,
- ReferenceLineMesh,
- LineCreated,
- setRefTextUpdate,
- Tube,
- anglesnappedPoint,
- isAngleSnapped,
- toolMode
- );
- }
- });
+ useFrame(() => {
+ if (toolMode) {
+ Draw(
+ state,
+ plane,
+ cursorPosition,
+ floorPlanGroupPoint,
+ floorPlanGroupLine,
+ snappedPoint,
+ isSnapped,
+ isSnappedUUID,
+ line,
+ lines,
+ ispreSnapped,
+ floorPlanGroup,
+ ReferenceLineMesh,
+ LineCreated,
+ setRefTextUpdate,
+ Tube,
+ anglesnappedPoint,
+ isAngleSnapped,
+ toolMode
+ );
+ }
+ });
- ////////// Return //////////
+ ////////// Return //////////
- return (
- <>
-
+ return (
+ <>
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
- >
- );
+
+ >
+ );
}
diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx
index 1b3defa..f5e61d3 100644
--- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx
+++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx
@@ -1,169 +1,337 @@
-import React, { useEffect, useRef, useState } from 'react';
-import * as THREE from 'three';
-import { useEventsStore } from '../../../../../store/simulation/useEventsStore';
-import useModuleStore from '../../../../../store/useModuleStore';
-import { TransformControls } from '@react-three/drei';
-import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
-import { useSelectedEventSphere, useSelectedEventData } from '../../../../../store/simulation/useSimulationStore';
+import React, { useEffect, useRef, useState } from "react";
+import * as THREE from "three";
+import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
+import useModuleStore from "../../../../../store/useModuleStore";
+import { TransformControls, useGLTF } from "@react-three/drei";
+import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
+import {
+ useSelectedEventSphere,
+ useSelectedEventData,
+} from "../../../../../store/simulation/useSimulationStore";
+import PickDropPoints from "../../../../../components/ui/arm/PickDropPoints";
+import armPick from "../../../../../assets/gltf-glb/arm_ui_pick.glb";
+import armDrop from "../../../../../assets/gltf-glb/arm_ui_drop.glb";
+import useDraggableGLTF from "../../../../../components/ui/arm/useDraggableGLTF";
+
+interface Process {
+ startPoint: number[] | null;
+ endPoint: number[] | null;
+}
+
+interface Action {
+ actionUuid: string;
+ actionName: string;
+ actionType: string;
+ process: Process;
+ triggers: any[];
+}
+
+interface Point {
+ uuid: string;
+ position: number[];
+ rotation: number[];
+ actions: Action[];
+}
+
+interface RoboticArmEvent {
+ modelUuid: string;
+ modelName: string;
+ position: number[];
+ rotation: number[];
+ state: string;
+ type: string;
+ speed: number;
+ point: Point;
+}
function PointsCreator() {
- const { events, updatePoint, getPointByUuid, getEventByModelUuid } = useEventsStore();
- const { activeModule } = useModuleStore();
- const transformRef = useRef(null);
- const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
- const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
- const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere } = useSelectedEventSphere();
- const { setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
+ const { events, updatePoint, getPointByUuid, getEventByModelUuid } =
+ useEventsStore();
- useEffect(() => {
- if (selectedEventSphere) {
- const eventData = getEventByModelUuid(selectedEventSphere.userData.modelUuid);
- if (eventData) {
- setSelectedEventData(
- eventData,
- selectedEventSphere.userData.pointUuid
- );
- } else {
- clearSelectedEventData();
+ const [armBotStatusSample, setArmBotStatusSample] = useState<
+ RoboticArmEvent[]
+ >([
+ {
+ modelUuid: "b3556818-9e46-48b0-a869-a75c92857125",
+ modelName: "robotic_arm",
+ position: [0, 0, 0],
+ rotation: [0, 0, 0],
+ state: "idle",
+ type: "roboticArm",
+ speed: 1.5,
+ point: {
+ uuid: "point-123",
+ position: [0, 1.5, 0],
+ rotation: [0, 0, 0],
+ actions: [
+ {
+ actionUuid: "action-001",
+ actionName: "Pick Component",
+ actionType: "pickAndPlace",
+ process: {
+ startPoint: null,
+ endPoint: null,
+ },
+ triggers: [],
+ },
+ {
+ actionUuid: "action-002",
+ actionName: "Pick Component",
+ actionType: "pickAndPlace",
+ process: {
+ startPoint: null,
+ endPoint: null,
+ },
+ triggers: [],
+ },
+ ],
+ },
+ },
+ {
+ modelUuid: "16a394c7-0808-4bdf-a5d3-e5ca141ffb9f",
+ modelName: "arm without rig (1)",
+ position: [0, 0, 0],
+ rotation: [0, 0, 0],
+ state: "idle",
+ type: "roboticArm",
+ speed: 1.5,
+ point: {
+ uuid: "point-123",
+ position: [0, 1.5, 0],
+ rotation: [0, 0, 0],
+ actions: [
+ {
+ actionUuid: "action-001",
+ actionName: "Pick Component",
+ actionType: "pickAndPlace",
+ process: {
+ startPoint: null,
+ endPoint: null,
+ },
+ triggers: [],
+ },
+ ],
+ },
+ },
+ ]);
+
+ const armUiPick = useGLTF(armPick) as any;
+ const armUiDrop = useGLTF(armDrop) as any;
+
+ const updatePointToState = (obj: THREE.Object3D) => {
+ const { modelUuid, pointUuid, actionType, actionUuid } = obj.userData;
+ const newPosition = obj.position.toArray();
+
+ setArmBotStatusSample((prev) =>
+ prev.map((event) => {
+ if (event.modelUuid === modelUuid) {
+ const updatedActions = event.point.actions.map((action) => {
+ if (action.actionUuid === actionUuid) {
+ const updatedProcess = { ...action.process };
+ if (actionType === "pick") {
+ updatedProcess.startPoint = newPosition;
+ } else if (actionType === "drop") {
+ updatedProcess.endPoint = newPosition;
+ }
+ return {
+ ...action,
+ process: updatedProcess,
+ };
}
- } else {
- clearSelectedEventData();
+ return action;
+ });
+
+ return {
+ ...event,
+ point: {
+ ...event.point,
+ actions: updatedActions,
+ },
+ };
}
- }, [selectedEventSphere]);
-
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- const keyCombination = detectModifierKeys(e);
- if (!selectedEventSphere) return;
- if (keyCombination === "G") {
- setTransformMode((prev) => (prev === "translate" ? null : "translate"));
- }
- if (keyCombination === "R") {
- setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
- }
- };
-
- window.addEventListener("keydown", handleKeyDown);
- return () => window.removeEventListener("keydown", handleKeyDown);
- }, [selectedEventSphere]);
-
- const updatePointToState = (selectedEventSphere: THREE.Mesh) => {
- let point = JSON.parse(JSON.stringify(getPointByUuid(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid)));
- if (point) {
- point.position = [selectedEventSphere.position.x, selectedEventSphere.position.y, selectedEventSphere.position.z];
- updatePoint(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid, point)
- }
- }
-
- return (
- <>
- {activeModule === 'simulation' &&
- <>
-
- {events.map((event, i) => {
- if (event.type === 'transfer') {
- return (
-
- {event.points.map((point, j) => (
- (sphereRefs.current[point.uuid] = el!)}
- onClick={(e) => {
- e.stopPropagation();
- setSelectedEventSphere(sphereRefs.current[point.uuid]);
- }}
- onPointerMissed={() => {
- clearSelectedEventSphere();
- setTransformMode(null);
- }}
- key={`${i}-${j}`}
- position={new THREE.Vector3(...point.position)}
- userData={{ modelUuid: event.modelUuid, pointUuid: point.uuid }}
- >
-
-
-
- ))}
-
- );
- } else if (event.type === 'vehicle') {
- return (
-
- (sphereRefs.current[event.point.uuid] = el!)}
- onClick={(e) => {
- e.stopPropagation();
- setSelectedEventSphere(sphereRefs.current[event.point.uuid]);
- }}
- onPointerMissed={() => {
- clearSelectedEventSphere();
- setTransformMode(null);
- }}
- position={new THREE.Vector3(...event.point.position)}
- userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
- >
-
-
-
-
- );
- } else if (event.type === 'roboticArm') {
- return (
-
- (sphereRefs.current[event.point.uuid] = el!)}
- onClick={(e) => {
- e.stopPropagation();
- setSelectedEventSphere(sphereRefs.current[event.point.uuid]);
- }}
- onPointerMissed={() => {
- clearSelectedEventSphere();
- setTransformMode(null);
- }}
- position={new THREE.Vector3(...event.point.position)}
- userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
- >
-
-
-
-
- );
- } else if (event.type === 'machine') {
- return (
-
- (sphereRefs.current[event.point.uuid] = el!)}
- onClick={(e) => {
- e.stopPropagation();
- setSelectedEventSphere(sphereRefs.current[event.point.uuid]);
- }}
- onPointerMissed={() => {
- clearSelectedEventSphere();
- setTransformMode(null);
- }}
- position={new THREE.Vector3(...event.point.position)}
- userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
- >
-
-
-
-
- );
- } else {
- return null;
- }
- })}
-
- {(selectedEventSphere && transformMode) &&
- { updatePointToState(selectedEventSphere) }} />
- }
- >
- }
- >
+ return event;
+ })
);
+ };
+
+ const { handlePointerDown } = useDraggableGLTF(updatePointToState);
+
+ const { activeModule } = useModuleStore();
+ const transformRef = useRef(null);
+ const [transformMode, setTransformMode] = useState<
+ "translate" | "rotate" | null
+ >(null);
+ const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
+ const {
+ selectedEventSphere,
+ setSelectedEventSphere,
+ clearSelectedEventSphere,
+ } = useSelectedEventSphere();
+ const { setSelectedEventData, clearSelectedEventData } =
+ useSelectedEventData();
+
+ useEffect(() => {
+ if (selectedEventSphere) {
+ const eventData = getEventByModelUuid(
+ selectedEventSphere.userData.modelUuid
+ );
+ if (eventData) {
+ setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
+ } else {
+ clearSelectedEventData();
+ }
+ } else {
+ clearSelectedEventData();
+ }
+ }, [selectedEventSphere]);
+
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ const keyCombination = detectModifierKeys(e);
+ if (!selectedEventSphere) return;
+ if (keyCombination === "G") {
+ setTransformMode((prev) => (prev === "translate" ? null : "translate"));
+ }
+ if (keyCombination === "R") {
+ setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
+ }
+ };
+
+ window.addEventListener("keydown", handleKeyDown);
+ return () => window.removeEventListener("keydown", handleKeyDown);
+ }, [selectedEventSphere]);
+
+ const [selectedPoint, setSelectedPoint] = useState(null);
+
+ const getDefaultPositions = (modelUuid: string) => {
+ const modelData = getModelByUuid(modelUuid);
+ if (modelData) {
+ const baseX = modelData.position?.[0] || 0;
+ const baseY = modelData.position?.[1] + 2.8 || 1.5;
+ const baseZ = modelData.position?.[2] || 0;
+ return {
+ pick: [baseX, baseY, baseZ - 2.5],
+ drop: [baseX, baseY, baseZ - 0.5],
+ default: [baseX, baseY, baseZ - 1.5],
+ };
+ }
+ return {
+ pick: [0.5, 1.5, 0],
+ drop: [-0.5, 1.5, 0],
+ default: [0, 1.5, 0],
+ };
+ };
+
+ const getModelByUuid = (modelUuid: string) => {
+ try {
+ const modelsJson = localStorage.getItem("FloorItems");
+ if (modelsJson) {
+ const models = JSON.parse(modelsJson);
+ return models.find((m: any) => m.modeluuid === modelUuid);
+ }
+ const storeModels = (useModuleStore.getState() as any).models || [];
+ return storeModels.find((m: any) => m.modelUuid === modelUuid);
+ } catch (error) {}
+ return null;
+ };
+
+ useEffect(() => {
+ console.log("armBotStatusSample: ", armBotStatusSample);
+ }, [armBotStatusSample]);
+ return (
+ <>
+ {activeModule === "simulation" && (
+ <>
+
+ {armBotStatusSample.map((event, i) => {
+ if (event.type === "roboticArm") {
+ const defaultPositions = getDefaultPositions(event.modelUuid);
+ const isSelected =
+ selectedPoint?.userData?.modelUuid === event.modelUuid;
+
+ return (
+
+ (sphereRefs.current[event.point.uuid] = el!)}
+ onClick={(e) => {
+ e.stopPropagation();
+ setSelectedEventSphere(
+ sphereRefs.current[event.point.uuid]
+ );
+ setSelectedPoint(e.object as THREE.Mesh);
+ }}
+ onPointerMissed={() => {
+ clearSelectedEventSphere();
+ setTransformMode(null);
+ }}
+ position={new THREE.Vector3(...defaultPositions.default)}
+ userData={{
+ modelUuid: event.modelUuid,
+ pointUuid: event.point.uuid,
+ }}
+ >
+
+
+
+ {event.point.actions.map((action) => {
+ if (action.actionType === "pickAndPlace") {
+ const pickPosition =
+ action.process.startPoint || defaultPositions.pick;
+ const dropPosition =
+ action.process.endPoint || defaultPositions.drop;
+
+ return (
+
+ {/* Pick Point */}
+
+
+ {/* Drop Point */}
+
+
+ );
+ }
+ return null;
+ })}
+
+ );
+ } else {
+ return null;
+ }
+ })}
+
+ >
+ )}
+ >
+ );
}
export default PointsCreator;
+
+
+
diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx
index 2daa091..3f6b4cc 100644
--- a/app/src/pages/Project.tsx
+++ b/app/src/pages/Project.tsx
@@ -23,6 +23,9 @@ import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
import RenderOverlay from "../components/templates/Overlay";
import MenuBar from "../components/ui/menu/menu";
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
+import ProductionCapacity from "../components/ui/analysis/ProductionCapacity";
+import ThroughputSummary from "../components/ui/analysis/ThroughputSummary";
+import ROISummary from "../components/ui/analysis/ROISummary";
const Project: React.FC = () => {
let navigate = useNavigate();
@@ -38,7 +41,7 @@ const Project: React.FC = () => {
setFloorItems([]);
setWallItems([]);
setZones([]);
- setActiveModule('builder')
+ setActiveModule("builder");
const email = localStorage.getItem("email");
if (email) {
const Organization = email!.split("@")[1].split(".")[0];
@@ -57,6 +60,11 @@ const Project: React.FC = () => {
return (
+ {/*
*/}
{loadingProgress &&
}
{!isPlaying && (
diff --git a/app/src/styles/components/analysis/analysis.scss b/app/src/styles/components/analysis/analysis.scss
new file mode 100644
index 0000000..bc33556
--- /dev/null
+++ b/app/src/styles/components/analysis/analysis.scss
@@ -0,0 +1,270 @@
+.analysis {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 100vh;
+ z-index: 100000000000000000000000000000;
+}
+
+.analysis-card {
+ min-width: 333px;
+ // background: var(--primary-color);
+ border-radius: 20px;
+
+ padding: 8px;
+
+ .analysis-card-wrapper {
+ background: var(--background-color);
+ border-radius: 14px;
+ padding: 16px;
+
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+
+ .card-header {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .main-header {
+ line-height: 20px;
+ font-size: var(--font-size-regular);
+ }
+ }
+
+ .process-container {
+ display: flex;
+ flex-direction: column;
+
+ .throughput-value {
+ font-size: 1rem;
+
+ .value {
+ font-weight: bold;
+ font-size: 1.5rem;
+ }
+ }
+
+ .progress-bar-wrapper {
+ display: flex;
+ gap: 8px;
+ margin-top: 6px;
+ }
+
+ .progress-bar {
+ position: relative;
+ width: 36px;
+ height: 4px;
+ border-radius: 13px;
+ overflow: hidden;
+ background-color: #FBEBD7;
+
+ .bar-fill {
+ position: absolute;
+ height: 100%;
+ top: 0;
+ left: 0;
+ background-color: #FC9D2F;
+ border-radius: 13px;
+ }
+
+ .bar-fill.full {
+ width: 100%;
+ }
+
+ .bar-fill.partial {
+ width: 0; // inline style will override this
+ }
+ }
+ }
+
+ .metrics-section {
+ padding-top: 16px;
+ border-top: 1px solid var(--background-color-gray);
+
+ .metric {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 14px;
+ margin-bottom: 8px;
+
+ .label {
+ color: var(--text-color);
+ }
+
+ .value {
+ font-weight: bold;
+ }
+ }
+ }
+ }
+}
+
+
+.throughoutSummary {
+ .throughoutSummary-wrapper {
+ .process-container {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 16px;
+ width: 100%;
+
+ .throughput-value {
+ font-size: var(--font-size-small);
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .value {
+ color: var(--accent-color);
+ }
+
+ /* Let the text take available space */
+ }
+
+ .lineChart {
+ max-width: 200px;
+ height: 100px;
+ position: relative;
+
+ .assetUsage {
+ text-align: right;
+ position: absolute;
+ right: 0;
+ top: 0;
+ }
+
+ canvas {
+ background-color: transparent;
+ }
+ }
+ }
+
+ .footer {
+ display: flex;
+ gap: 16px; // Space between cards
+ margin-top: 24px;
+
+ .footer-card {
+ width: 100%;
+ background: var(--background-color-gray);
+ border-radius: 6px;
+ padding: 8px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+
+ &:first-child {
+ width: 85%;
+ }
+
+ .header {
+ font-size: var(--font-size-regular);
+ }
+
+ .value-container {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: end;
+ gap: 6px;
+ }
+ }
+
+ .shiftUtilization {
+ .value-container {
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+
+ .value {
+ font-size: var(--font-size-xlarge);
+ }
+
+ .progress-wrapper {
+ width: 100%;
+ display: flex;
+ gap: 6px;
+
+ .progress {
+ border-radius: 6px;
+ height: 5px;
+
+ &:nth-child(1) {
+ background-color: #F3C64D;
+ }
+
+ &:nth-child(2) {
+ background-color: #67B3F4;
+ }
+
+ &:nth-child(3) {
+ background-color: #7981F5;
+ }
+ }
+ }
+
+ .progress-indicator {
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+ gap: 6px;
+
+ .shift-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+
+ /* Align items vertically */
+ &:nth-child(1) {
+ .indicator {
+
+ background-color: #F3C64D;
+ }
+ }
+
+ &:nth-child(2) {
+ .indicator {
+
+ background-color: #67B3F4;
+ }
+ }
+
+ &:nth-child(3) {
+ .indicator {
+
+ background-color: #7981F5;
+ }
+ }
+
+ label {
+ font-size: var(--font-size-small);
+ position: relative;
+ }
+
+ .indicator {
+ display: inline-block;
+ width: 5px;
+ height: 5px;
+ border-radius: 50%;
+
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss
index 5e46dd4..c0d2431 100644
--- a/app/src/styles/main.scss
+++ b/app/src/styles/main.scss
@@ -25,6 +25,7 @@
@use 'components/simulation/simulation';
@use 'components/menu/menu';
@use 'components/confirmationPopUp';
+@use 'components/analysis/analysis';
// layout
@use 'layout/loading';