-
00:00
-
24:00
-
-
- {process.map((item, index) => (
-
-
+
+
+
+
+
- ))}
+
+
23 April ,25
+
04:41 PM
+
+
+
+
+ {intervals.map((label, index) => {
+ const segmentProgress = (index / totalSegments) * 100;
+ const isFilled = progress >= segmentProgress;
+ return (
+
+
+ {index < intervals.length - 1 && (
+ = ((index + 1) / totalSegments) * 100
+ ? "filled"
+ : ""
+ }`}
+ >
+ )}
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
0.5X
+
+
+
+
+
+
+
+
+
+
+
+
+
+
4x
+
+ {subModule === "analysis" && (
+
+
00:00
+
24:00
+
+
+ {process.map((item, index) => (
+
+ ))}
+
+
+
+ )}
-
+
+ >
);
};
diff --git a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts
index a351d73..eab6e3e 100644
--- a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts
+++ b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts
@@ -8,7 +8,6 @@ import * as Types from "../../../types/world/worldTypes";
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
-import PointsCalculator from '../../simulation/events/points/functions/pointsCalculator';
async function loadInitialFloorItems(
itemsGroup: Types.RefGroup,
@@ -72,7 +71,7 @@ async function loadInitialFloorItems(
// Check Three.js Cache
const cachedModel = THREE.Cache.get(item.modelfileID!);
if (cachedModel) {
- // console.log(`[Cache] Fetching ${item.modelName}`);
+ //
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, addEvent);
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
@@ -82,7 +81,7 @@ async function loadInitialFloorItems(
// Check IndexedDB
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
if (indexedDBModel) {
- // console.log(`[IndexedDB] Fetching ${item.modelName}`);
+ //
const blobUrl = URL.createObjectURL(indexedDBModel);
loader.load(blobUrl, (gltf) => {
URL.revokeObjectURL(blobUrl);
@@ -103,7 +102,7 @@ async function loadInitialFloorItems(
}
// Fetch from Backend
- // console.log(`[Backend] Fetching ${item.modelName}`);
+ //
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`;
loader.load(modelUrl, async (gltf) => {
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
@@ -121,7 +120,7 @@ async function loadInitialFloorItems(
);
});
} else {
- // console.log(`Item ${item.modelName} is not near`);
+ //
setFloorItems((prevItems) => [
...(prevItems || []),
{
@@ -187,7 +186,7 @@ function processLoadedModel(
},
]);
- if (item.eventData.type === "vehicle") {
+ if (item.eventData.type === "Vehicle") {
const vehicleEvent: VehicleEventSchema = {
modelUuid: item.modelUuid,
modelName: item.modelName,
@@ -202,11 +201,11 @@ function processLoadedModel(
rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
- actionName: "Vehicle Action",
+ actionName: "Action 1",
actionType: "travel",
unLoadDuration: 5,
loadCapacity: 10,
- steeringAngle:0,
+ steeringAngle: 0,
pickUpPoint: null,
unLoadPoint: null,
triggers: []
@@ -254,7 +253,7 @@ function processLoadedModel(
rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
- actionName: "Process Action",
+ actionName: "Action 1",
actionType: "process",
processTime: 10,
swapMaterial: "material-id",
@@ -279,11 +278,11 @@ function processLoadedModel(
actions: [
{
actionUuid: THREE.MathUtils.generateUUID(),
- actionName: "Pick and Place",
+ actionName: "Action 1",
actionType: "pickAndPlace",
process: {
- startPoint: [0, 0, 0],
- endPoint: [0, 0, 0]
+ startPoint: null,
+ endPoint: null
},
triggers: []
}
diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts
index c718ec3..6e978dc 100644
--- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts
+++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts
@@ -186,22 +186,59 @@ async function handleModelLoad(
state: "idle",
type: 'transfer',
speed: 1,
- points: data.points.map((point: THREE.Vector3, index: number) => ({
- uuid: THREE.MathUtils.generateUUID(),
- position: [point.x, point.y, point.z],
- rotation: [0, 0, 0],
- action: {
- actionUuid: THREE.MathUtils.generateUUID(),
- actionName: `Action ${index}`,
- actionType: 'default',
- material: 'Default Material',
- delay: 0,
- spawnInterval: 5,
- spawnCount: 1,
- triggers: []
+ points: data.points.map((point: THREE.Vector3, index: number) => {
+ const triggers: TriggerSchema[] = [];
+
+ if (data.points && index < data.points.length - 1) {
+ triggers.push({
+ triggerUuid: THREE.MathUtils.generateUUID(),
+ triggerName: `Trigger 1`,
+ triggerType: "onComplete",
+ delay: 0,
+ triggeredAsset: {
+ triggeredModel: {
+ modelName: newFloorItem.modelName,
+ modelUuid: newFloorItem.modelUuid
+ },
+ triggeredPoint: {
+ pointName: `Point`,
+ pointUuid: ""
+ },
+ triggeredAction: {
+ actionName: `Action 1`,
+ actionUuid: ""
+ }
+ }
+ });
}
- }))
+
+ return {
+ uuid: THREE.MathUtils.generateUUID(),
+ position: [point.x, point.y, point.z],
+ rotation: [0, 0, 0],
+ action: {
+ actionUuid: THREE.MathUtils.generateUUID(),
+ actionName: `Action 1`,
+ actionType: 'default',
+ material: 'Default Material',
+ delay: 0,
+ spawnInterval: 5,
+ spawnCount: 1,
+ triggers: triggers
+ }
+ };
+ })
};
+
+ for (let i = 0; i < ConveyorEvent.points.length - 1; i++) {
+ const currentPoint = ConveyorEvent.points[i];
+ const nextPoint = ConveyorEvent.points[i + 1];
+
+ if (currentPoint.action.triggers.length > 0) {
+ currentPoint.action.triggers[0].triggeredAsset!.triggeredPoint!.pointUuid = nextPoint.uuid;
+ currentPoint.action.triggers[0].triggeredAsset!.triggeredAction!.actionUuid = nextPoint.action.actionUuid;
+ }
+ }
addEvent(ConveyorEvent);
eventData.points = ConveyorEvent.points.map(point => ({
uuid: point.uuid,
@@ -228,7 +265,7 @@ async function handleModelLoad(
actionType: "travel",
unLoadDuration: 5,
loadCapacity: 10,
- steeringAngle:0,
+ steeringAngle: 0,
pickUpPoint: null,
unLoadPoint: null,
triggers: []
diff --git a/app/src/modules/builder/geomentries/assets/assetManager.ts b/app/src/modules/builder/geomentries/assets/assetManager.ts
index 5f7798e..889d905 100644
--- a/app/src/modules/builder/geomentries/assets/assetManager.ts
+++ b/app/src/modules/builder/geomentries/assets/assetManager.ts
@@ -18,7 +18,7 @@ export default async function assetManager(
const taskId = ++currentTaskId; // Increment taskId for each call
activePromises.set(taskId, true); // Mark task as active
- // console.log("Received message from worker:", data);
+ //
if (data.toRemove.length > 0) {
data.toRemove.forEach((uuid: string) => {
@@ -58,7 +58,7 @@ export default async function assetManager(
// Check Three.js Cache
const cachedModel = THREE.Cache.get(item.modelfileID!);
if (cachedModel) {
- // console.log(`[Cache] Fetching ${item.modelName}`);
+ //
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, resolve);
return;
}
@@ -66,7 +66,7 @@ export default async function assetManager(
// Check IndexedDB
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
if (indexedDBModel) {
- // console.log(`[IndexedDB] Fetching ${item.modelName}`);
+ //
const blobUrl = URL.createObjectURL(indexedDBModel);
loader.load(
blobUrl,
@@ -86,7 +86,7 @@ export default async function assetManager(
}
// Fetch from Backend
- // console.log(`[Backend] Fetching ${item.modelName}`);
+ //
loader.load(
modelUrl,
async (gltf) => {
@@ -114,14 +114,14 @@ export default async function assetManager(
const existingModel = itemsGroup?.current?.getObjectByProperty("uuid", item.modelUuid);
if (existingModel) {
- // console.log(`Model ${item.modelName} already exists in the scene.`);
+ //
resolve();
return;
}
const model = gltf;
model.uuid = item.modelUuid;
- model.userData = { name: item.modelName, modelId: item.modelfileID, modelUuid: item.modelUuid };
+ model.userData = { name: item.modelName, modelId: item.modelfileID, modelUuid: item.modelUuid, eventData: item.eventData };
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/scene/controls/selectionControls/moveControls.tsx b/app/src/modules/scene/controls/selectionControls/moveControls.tsx
index d371806..5b33c14 100644
--- a/app/src/modules/scene/controls/selectionControls/moveControls.tsx
+++ b/app/src/modules/scene/controls/selectionControls/moveControls.tsx
@@ -1,7 +1,7 @@
import * as THREE from "three";
-import { useEffect, useMemo, useRef } from "react";
+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, useSocketStore, useStartSimulation, useToggleView } from "../../../../store/store";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes";
@@ -9,6 +9,8 @@ import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifie
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
import { useProductStore } from "../../../../store/simulation/useProductStore";
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
+import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi";
+import { snapControls } from "../../../../utils/handleSnap";
function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
@@ -16,9 +18,28 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
+ const { selectedProduct } = useSelectedProduct();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const itemsData = useRef
([]);
+ const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("")
+
+ const email = localStorage.getItem('email')
+ const organization = (email!.split("@")[1]).split(".")[0];
+
+ const updateBackend = (
+ productName: string,
+ productId: string,
+ organization: string,
+ eventData: EventsSchema
+ ) => {
+ upsertProductOrEventApi({
+ productName: productName,
+ productId: productId,
+ organization: organization,
+ eventDatas: eventData
+ })
+ }
useEffect(() => {
if (!camera || !scene || toggleView || !itemsGroupRef.current) return;
@@ -35,6 +56,15 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
const onPointerMove = () => {
isMoving = true;
};
+ const onKeyUp = (event: KeyboardEvent) => {
+ // When any modifier is released, reset snap
+ const isModifierKey =
+ event.key === "Control" || event.key === "Shift";
+
+ if (isModifierKey) {
+ setKeyEvent("");
+ }
+ };
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && movedObjects.length > 0 && event.button === 0) {
@@ -56,18 +86,28 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
setMovedObjects([]);
itemsData.current = [];
}
+ setKeyEvent("")
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return;
+
+ if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
+ // update state here
+ setKeyEvent(keyCombination)
+ } else {
+ setKeyEvent("")
+ }
+
if (keyCombination === "G") {
if (selectedAssets.length > 0) {
moveAssets();
itemsData.current = floorItems.filter((item: { modelUuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modelUuid));
}
}
+
if (keyCombination === "ESCAPE") {
event.preventDefault();
@@ -90,6 +130,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
+ canvasElement?.addEventListener("keyup", onKeyUp);
}
return () => {
@@ -97,12 +138,11 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
+ canvasElement?.removeEventListener("keyup", onKeyUp);
};
- }, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects]);
+ }, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent]);
- const gridSize = 0.25;
- const moveSpeed = 0.25;
- const isGridSnap = false;
+ let moveSpeed = keyEvent === "Ctrl" || "Ctrl+Shift" ? 1 : 0.25;
useFrame(() => {
if (movedObjects.length > 0) {
@@ -113,10 +153,17 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
if (point) {
let targetX = point.x;
let targetZ = point.z;
+ if (keyEvent === "Ctrl") {
+ targetX = snapControls(targetX, "Ctrl");
+ targetZ = snapControls(targetZ, "Ctrl");
+ } else if (keyEvent === "Ctrl+Shift") {
+ targetX = snapControls(targetX, "Ctrl+Shift");
+ targetZ = snapControls(targetZ, "Ctrl+Shift");
+ } else if (keyEvent === "Shift") {
+ targetX = snapControls(targetX, "Shift");
+ targetZ = snapControls(targetZ, "Shift");
+ } else {
- if (isGridSnap) {
- targetX = Math.round(point.x / gridSize) * gridSize;
- targetZ = Math.round(point.z / gridSize) * gridSize;
}
const position = new THREE.Vector3();
@@ -190,10 +237,21 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
})
}
if (productData) {
- useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, {
+ const event = useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, {
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
})
+
+ if (event) {
+ updateBackend(
+ selectedProduct.productName,
+ selectedProduct.productId,
+ organization,
+ event
+ );
+ }
+
+ newFloorItem.eventData = eventData;
}
}
@@ -203,9 +261,6 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
return updatedItems;
});
- const email = localStorage.getItem("email");
- const organization = email ? email.split("@")[1].split(".")[0] : "default";
-
//REST
// await setFloorItemApi(
@@ -253,6 +308,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
+ setKeyEvent("")
}
return null;
diff --git a/app/src/modules/scene/controls/selectionControls/rotateControls.tsx b/app/src/modules/scene/controls/selectionControls/rotateControls.tsx
index 08667b4..58dab0c 100644
--- a/app/src/modules/scene/controls/selectionControls/rotateControls.tsx
+++ b/app/src/modules/scene/controls/selectionControls/rotateControls.tsx
@@ -8,6 +8,7 @@ import * as Types from "../../../../types/world/worldTypes";
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
import { useProductStore } from "../../../../store/simulation/useProductStore";
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
+import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi";
function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, boundingBoxRef }: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
@@ -15,10 +16,28 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
+ const { selectedProduct } = useSelectedProduct();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const itemsData = useRef([]);
+ const email = localStorage.getItem('email')
+ const organization = (email!.split("@")[1]).split(".")[0];
+
+ const updateBackend = (
+ productName: string,
+ productId: string,
+ organization: string,
+ eventData: EventsSchema
+ ) => {
+ upsertProductOrEventApi({
+ productName: productName,
+ productId: productId,
+ organization: organization,
+ eventDatas: eventData
+ })
+ }
+
const prevPointerPosition = useRef(null);
useEffect(() => {
@@ -190,10 +209,21 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
})
}
if (productData) {
- useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, {
+ const event = useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, {
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
})
+
+ if (event) {
+ updateBackend(
+ selectedProduct.productName,
+ selectedProduct.productId,
+ organization,
+ event
+ );
+ }
+
+ newFloorItem.eventData = eventData;
}
}
@@ -203,9 +233,6 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
return updatedItems;
});
- const email = localStorage.getItem("email");
- const organization = email ? email.split("@")[1].split(".")[0] : "default";
-
//REST
// await setFloorItemApi(
diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx
index c6ec316..0ed7e13 100644
--- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx
+++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx
@@ -1,28 +1,36 @@
import React, { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
-import useModuleStore from "../../../../../store/useModuleStore";
+import useModuleStore, { useSubModuleStore } from "../../../../../store/useModuleStore";
import { TransformControls } from "@react-three/drei";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import {
useSelectedEventSphere,
useSelectedEventData,
+ useIsDragging,
+ useIsRotating,
} from "../../../../../store/simulation/useSimulationStore";
+import { useThree } from "@react-three/fiber";
function PointsCreator() {
+ const { gl, raycaster, scene, pointer, camera } = useThree();
+ const { subModule } = useSubModuleStore();
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 { selectedEventData, setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
+ const { selectedEventData,setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
+ const { isDragging } = useIsDragging();
+ const { isRotating } = useIsRotating();
useEffect(() => {
if (selectedEventSphere) {
const eventData = getEventByModelUuid(
selectedEventSphere.userData.modelUuid
);
+
if (eventData) {
setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
} else {
@@ -72,6 +80,53 @@ function PointsCreator() {
}
};
+ useEffect(() => {
+ const canvasElement = gl.domElement;
+
+ let drag = false;
+ let isMouseDown = false;
+
+ const onMouseDown = () => {
+ isMouseDown = true;
+ drag = false;
+ };
+
+ const onMouseUp = () => {
+ if (selectedEventSphere && !drag) {
+ raycaster.setFromCamera(pointer, camera);
+ const intersects = raycaster
+ .intersectObjects(scene.children, true)
+ .filter(
+ (intersect) =>
+ intersect.object.name === ('Event-Sphere')
+ );
+ if (intersects.length === 0) {
+ clearSelectedEventSphere();
+ setTransformMode(null);
+ }
+ }
+ }
+
+ const onMouseMove = () => {
+ if (isMouseDown) {
+ drag = true;
+ }
+ };
+
+ if (subModule === 'mechanics') {
+ canvasElement.addEventListener("mousedown", onMouseDown);
+ canvasElement.addEventListener("mouseup", onMouseUp);
+ canvasElement.addEventListener("mousemove", onMouseMove);
+ }
+
+ return () => {
+ canvasElement.removeEventListener("mousedown", onMouseDown);
+ canvasElement.removeEventListener("mouseup", onMouseUp);
+ canvasElement.removeEventListener("mousemove", onMouseMove);
+ };
+
+ }, [gl, subModule, selectedEventSphere]);
+
return (
<>
{activeModule === "simulation" && (
@@ -80,7 +135,10 @@ function PointsCreator() {
{events.map((event, i) => {
if (event.type === "transfer") {
return (
-
+
{event.points.map((point, j) => (
{
if (selectedEventData?.data.type !== 'vehicle') {
- clearSelectedEventSphere();
+ // clearSelectedEventSphere();
}
setTransformMode(null);
}}
key={`${i}-${j}`}
position={new THREE.Vector3(...point.position)}
- // rotation={new THREE.Euler(...point.rotation)}
- userData={{ modelUuid: event.modelUuid, pointUuid: point.uuid }}
+ userData={{
+ modelUuid: event.modelUuid,
+ pointUuid: point.uuid,
+ }}
>
@@ -111,7 +171,10 @@ function PointsCreator() {
);
} else if (event.type === "vehicle") {
return (
-
+
{
- clearSelectedEventSphere();
- setTransformMode(null);
- }}
position={new THREE.Vector3(...event.point.position)}
- // rotation={new THREE.Euler(...event.point.rotation)}
- userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
+ userData={{
+ modelUuid: event.modelUuid,
+ pointUuid: event.point.uuid,
+ }}
>
@@ -137,7 +198,10 @@ function PointsCreator() {
);
} else if (event.type === "roboticArm") {
return (
-
+
{
- clearSelectedEventSphere();
setTransformMode(null);
}}
position={new THREE.Vector3(...event.point.position)}
- // rotation={new THREE.Euler(...event.point.rotation)}
- userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
+ userData={{
+ modelUuid: event.modelUuid,
+ pointUuid: event.point.uuid,
+ }}
>
@@ -163,7 +228,10 @@ function PointsCreator() {
);
} else if (event.type === "machine") {
return (
-
+
{
- clearSelectedEventSphere();
+ // clearSelectedEventSphere();
setTransformMode(null);
}}
position={new THREE.Vector3(...event.point.position)}
- // rotation={new THREE.Euler(...event.point.rotation)}
- userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
+ userData={{
+ modelUuid: event.modelUuid,
+ pointUuid: event.point.uuid,
+ }}
>
@@ -208,4 +278,4 @@ function PointsCreator() {
);
}
-export default PointsCreator;
+export default PointsCreator;
\ No newline at end of file
diff --git a/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx b/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx
new file mode 100644
index 0000000..6ef2b0e
--- /dev/null
+++ b/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx
@@ -0,0 +1,102 @@
+import { useFrame } from '@react-three/fiber';
+import React, { useEffect, useRef } from 'react';
+import { useMachineStore } from '../../../../../store/simulation/useMachineStore';
+import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
+
+
+interface MachineAnimatorProps {
+ currentPhase: string;
+ handleCallBack: () => void;
+ reset: () => void;
+ machineStatus: (modelId: string, status: string) => void;
+ processingTime: number;
+ machineUuid: string
+}
+
+const MachineAnimator = ({ currentPhase, handleCallBack, processingTime, machineUuid, machineStatus, reset }: MachineAnimatorProps) => {
+ const animationStarted = useRef(false);
+ const isPausedRef = useRef(false);
+ const startTimeRef = useRef(0);
+ const animationFrameId = useRef(null);
+ const pauseTimeRef = useRef(null);
+ const { isPaused } = usePauseButtonStore();
+ const { removeCurrentAction } = useMachineStore();
+ const { isReset, setReset } = useResetButtonStore();
+ const { isPlaying } = usePlayButtonStore();
+ const { speed } = useAnimationPlaySpeed();
+ const isPlayingRef = useRef(false);
+ const isResetRef = useRef(false)
+
+
+ useEffect(() => {
+ isPausedRef.current = isPaused;
+ }, [isPaused]);
+ useEffect(() => {
+ isPlayingRef.current = isPlaying;
+ }, [isPlaying]);
+ useEffect(() => {
+ isResetRef.current = isReset;
+ }, [isReset]);
+
+
+ useEffect(() => {
+
+ if (isReset || !isPlaying) {
+ reset();
+ setReset(false);
+ startTimeRef.current = 0;
+ isPausedRef.current = false;
+ pauseTimeRef.current = 0;
+ animationFrameId.current = null;
+ animationStarted.current = false;
+ removeCurrentAction(machineUuid)
+ }
+ }, [isReset, isPlaying])
+
+ useEffect(() => {
+ if (currentPhase === 'processing' && !animationStarted.current && machineUuid) {
+ animationStarted.current = true;
+ startTimeRef.current = performance.now();
+ animationFrameId.current = requestAnimationFrame(step);
+ }
+ }, [currentPhase]);
+
+ function step(time: number) {
+ if (!isPausedRef.current || !isResetRef.current) {
+ if (animationFrameId.current) {
+ cancelAnimationFrame(animationFrameId.current);
+ animationFrameId.current = null;
+ }
+ if (isPausedRef.current) {
+ if (!pauseTimeRef.current) {
+ pauseTimeRef.current = performance.now();
+ }
+ animationFrameId.current = requestAnimationFrame(step);
+ return;
+ }
+
+ if (pauseTimeRef.current) {
+ const pauseDuration = performance.now() - pauseTimeRef.current;
+ startTimeRef.current += pauseDuration;
+ pauseTimeRef.current = null;
+ }
+
+ const elapsed = time - startTimeRef.current;
+ const processedTime = processingTime * 1000;
+ if (elapsed < processedTime) {
+ machineStatus(machineUuid, "Machine is currently processing the task");
+ animationFrameId.current = requestAnimationFrame(step);
+ } else {
+ removeCurrentAction(machineUuid);
+ animationStarted.current = false;
+ handleCallBack();
+
+ }
+ }
+ }
+
+
+ return null;
+}
+
+export default MachineAnimator;
diff --git a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx
index edb825f..714bcdb 100644
--- a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx
+++ b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx
@@ -1,8 +1,65 @@
-import React from 'react'
+import React, { useEffect, useRef, useState } from 'react'
+import { useMachineStore } from '../../../../../store/simulation/useMachineStore';
+import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
+import MachineAnimator from '../animator/machineAnimator';
+
+function MachineInstance({ machineDetail }: any) {
+ const [currentPhase, setCurrentPhase] = useState('idle');
+ let isIncrememtable = useRef(true);
+ const { isPlaying } = usePlayButtonStore();
+ const { machines, addCurrentAction, setMachineState, setMachineActive } = useMachineStore();
+
+ const reset = () => {
+ setMachineState(machineDetail.modelUuid, 'idle');
+ setMachineActive(machineDetail.modelUuid, false);
+ isIncrememtable.current = true;
+ setCurrentPhase("idle");
+ }
+ const increment = () => {
+ if (isIncrememtable.current) {
+ addCurrentAction(machineDetail.modelUuid, "machine-action-2468-1357-8024")
+ isIncrememtable.current = false;
+ }
+ }
+ function machineStatus(modelId: string, status: string) {
+ // console.log(`${modelId} , ${status}`);
+
+ }
+
+ useEffect(() => {
+ if (isPlaying) {
+ if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && !machineDetail.currentAction) {
+ setTimeout(() => {
+ increment();
+ }, 2000);
+ machineStatus(machineDetail.modelUuid, 'Machine is idle and waiting for next instruction.')
+ } else if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && machineDetail.currentAction) {
+ setCurrentPhase("processing");
+ setMachineState(machineDetail.modelUuid, 'running');
+ setMachineActive(machineDetail.modelUuid, true);
+ machineStatus(machineDetail.modelUuid, "Machine started processing")
+ }
+ } else {
+ reset();
+ }
+ }, [currentPhase, isPlaying, machines])
+
+ function handleCallBack() {
+ if (currentPhase == "processing") {
+ setMachineState(machineDetail.modelUuid, 'idle');
+ setMachineActive(machineDetail.modelUuid, false);
+ setCurrentPhase("idle")
+ isIncrememtable.current = true;
+ machineStatus(machineDetail.modelUuid, "Machine has completed the processing")
+ }
+ }
+ // console.log('currentPhase: ', currentPhase);
+
+
-function MachineInstance() {
return (
<>
+
>
)
}
diff --git a/app/src/modules/simulation/machine/instances/machineInstances.tsx b/app/src/modules/simulation/machine/instances/machineInstances.tsx
index b0c2c9f..8536cac 100644
--- a/app/src/modules/simulation/machine/instances/machineInstances.tsx
+++ b/app/src/modules/simulation/machine/instances/machineInstances.tsx
@@ -1,11 +1,14 @@
import React from 'react'
import MachineInstance from './machineInstance/machineInstance'
+import { useMachineStore } from '../../../../store/simulation/useMachineStore';
function MachineInstances() {
+ const { machines } = useMachineStore();
return (
<>
-
-
+ {machines.map((val: MachineStatus) => (
+
+ ))}
>
)
diff --git a/app/src/modules/simulation/machine/machine.tsx b/app/src/modules/simulation/machine/machine.tsx
index 32c3b8e..3d24f61 100644
--- a/app/src/modules/simulation/machine/machine.tsx
+++ b/app/src/modules/simulation/machine/machine.tsx
@@ -1,7 +1,49 @@
-import React from 'react'
+import React, { useEffect } from 'react'
import MachineInstances from './instances/machineInstances'
+import { useMachineStore } from '../../../store/simulation/useMachineStore'
+import { useSelectedProduct } from '../../../store/simulation/useSimulationStore';
function Machine() {
+ const { addMachine, addCurrentAction, removeMachine, machines } = useMachineStore();
+ const { selectedProduct } = useSelectedProduct();
+
+ const machineSample: MachineEventSchema[] = [
+ {
+ modelUuid: "machine-1234-5678-9012",
+ modelName: "CNC Milling Machine",
+ position: [10, 0, 5],
+ rotation: [0, 0, 0],
+ state: "idle",
+ type: "machine",
+ point: {
+ uuid: "machine-point-9876-5432-1098",
+ position: [10, 0.5, 5.2],
+ rotation: [0, 0, 0],
+ action: {
+ actionUuid: "machine-action-2468-1357-8024",
+ actionName: "Metal Processing",
+ actionType: "process",
+ processTime: 10,
+ swapMaterial: "steel",
+ triggers: []
+ }
+ }
+ }
+ ];
+
+ useEffect(() => {
+ removeMachine(machineSample[0].modelUuid);
+ addMachine(selectedProduct.productId, machineSample[0]);
+
+ // addCurrentAction(machineSample[0].modelUuid, machineSample[0].point.action.actionUuid);
+ }, [])
+
+
+ useEffect(() => {
+
+ // console.log('machines: ', machines);
+ }, [machines])
+
return (
<>
diff --git a/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx b/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx
index 9eececc..715aa37 100644
--- a/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx
+++ b/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx
@@ -53,7 +53,17 @@ function AddOrRemoveEventsInProducts() {
const canvasElement = gl.domElement;
if (!canvasElement) return;
- let intersects = raycaster.intersectObjects(scene.children, true);
+ const intersects = raycaster
+ .intersectObjects(scene.children, true)
+ .filter(
+ (intersect) =>
+ !intersect.object.name.includes("Roof") &&
+ !intersect.object.name.includes("MeasurementReference") &&
+ !intersect.object.name.includes("agv-collider") &&
+ !(intersect.object.type === "GridHelper") &&
+ !(intersect.object?.parent?.name.includes('zones')) &&
+ !(intersect.object?.parent?.name.includes('Zone'))
+ );
if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) {
let currentObject = intersects[0].object;
@@ -116,6 +126,7 @@ function AddOrRemoveEventsInProducts() {
};
}, [gl, subModule, selectedProduct, selectedAsset]);
+
return (
<>>
)
diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx
index ee1ac42..921dd96 100644
--- a/app/src/modules/simulation/products/products.tsx
+++ b/app/src/modules/simulation/products/products.tsx
@@ -7,7 +7,7 @@ import { upsertProductOrEventApi } from '../../../services/simulation/UpsertProd
import { getAllProductsApi } from '../../../services/simulation/getallProductsApi';
function Products() {
- const { products, addProduct, setProducts } = useProductStore();
+ const { addProduct, setProducts } = useProductStore();
const { setSelectedProduct } = useSelectedProduct();
useEffect(() => {
@@ -27,9 +27,6 @@ function Products() {
})
}, [])
- useEffect(() => {
- }, [])
-
return (
<>
diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx
index c82b73d..10f90bf 100644
--- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx
@@ -1,68 +1,220 @@
-import React, { useEffect, useMemo, useRef, useState } from 'react'
-import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore';
-import { useFrame, useThree } from '@react-three/fiber';
-import * as THREE from "three"
-import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import { useFrame } from '@react-three/fiber';
+import * as THREE from 'three';
+import { Line } from '@react-three/drei';
+import {
+ useAnimationPlaySpeed,
+ usePauseButtonStore,
+ usePlayButtonStore,
+ useResetButtonStore
+} from '../../../../../store/usePlayButtonStore';
-function RoboticArmAnimator({ armUuid, HandleCallback, currentPhase, ikSolver, targetBone, robot, logStatus, groupRef, processes, armBotCurveRef, path }: any) {
- const { armBots } = useArmBotStore();
- const { scene } = useThree();
- const restSpeed = 0.1;
- const restPosition = new THREE.Vector3(0, 1, -1.6);
- const initialCurveRef = useRef(null);
- const initialStartPositionRef = useRef(null);
- const [initialProgress, setInitialProgress] = useState(0);
- const [progress, setProgress] = useState(0);
- const [needsInitialMovement, setNeedsInitialMovement] = useState(true);
- const [isInitializing, setIsInitializing] = useState(true);
- const { isPlaying } = usePlayButtonStore();
- const statusRef = useRef("idle");
- // Create a ref for initialProgress
- const initialProgressRef = useRef(0);
+function RoboticArmAnimator({
+ HandleCallback,
+ restPosition,
+ ikSolver,
+ targetBone,
+ armBot,
+ logStatus,
+ path
+}: any) {
+ const progressRef = useRef(0);
+ const curveRef = useRef(null);
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
+ const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]);
+ const [customCurvePoints, setCustomCurvePoints] = useState(null);
+ // Zustand stores
+ const { isPlaying } = usePlayButtonStore();
+ const { isPaused } = usePauseButtonStore();
+ const { isReset } = useResetButtonStore();
+ const { speed } = useAnimationPlaySpeed();
+
+ // Update path state whenever `path` prop changes
useEffect(() => {
-
- setCurrentPath(path)
- }, [path])
+ setCurrentPath(path);
+ }, [path]);
+ // Reset logic when `isPlaying` changes
useEffect(() => {
+ if (!isPlaying) {
+ setCurrentPath([]);
+ curveRef.current = null;
+ }
+ }, [isPlaying]);
- }, [currentPath])
+ // Handle circle points based on armBot position
+ useEffect(() => {
+ const points = generateRingPoints(1.6, 64)
+ setCirclePoints(points);
+ }, [armBot.position]);
+
+ function generateRingPoints(radius: any, segments: any) {
+ const points: [number, number, number][] = [];
+ for (let i = 0; i < segments; i++) {
+ // Calculate angle for current segment
+ const angle = (i / segments) * Math.PI * 2;
+ // Calculate x and z coordinates (y remains the same for a flat ring)
+ const x = Math.cos(angle) * radius;
+ const z = Math.sin(angle) * radius;
+ points.push([x, 1.5, z]);
+ }
+ return points;
+ }
+
+ const findNearestIndex = (nearestPoint: [number, number, number], points: [number, number, number][], epsilon = 1e-6) => {
+ for (let i = 0; i < points.length; i++) {
+ const [x, y, z] = points[i];
+ if (
+ Math.abs(x - nearestPoint[0]) < epsilon &&
+ Math.abs(y - nearestPoint[1]) < epsilon &&
+ Math.abs(z - nearestPoint[2]) < epsilon
+ ) {
+ return i; // Found the matching index
+ }
+ }
+ return -1; // Not found
+ };
+
+
+ // Handle nearest points and final path (including arc points)
+ useEffect(() => {
+ if (circlePoints.length > 0 && currentPath.length > 0) {
+ const start = currentPath[0];
+ const end = currentPath[currentPath.length - 1];
+
+ const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number];
+ const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number];
+
+ const findNearest = (target: [number, number, number]) => {
+ return circlePoints.reduce((nearest, point) => {
+ const distance = Math.hypot(
+ target[0] - point[0],
+ target[1] - point[1],
+ target[2] - point[2]
+ );
+ const nearestDistance = Math.hypot(
+ target[0] - nearest[0],
+ target[1] - nearest[1],
+ target[2] - nearest[2]
+ );
+ return distance < nearestDistance ? point : nearest;
+ }, circlePoints[0]);
+ };
+
+ const nearestToStart = findNearest(raisedStart);
+
+ const nearestToEnd = findNearest(raisedEnd);
+
+ const indexOfNearestStart = findNearestIndex(nearestToStart, circlePoints);
+
+ const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints);
+
+ // Find clockwise and counter-clockwise distances
+ const clockwiseDistance = (indexOfNearestEnd - indexOfNearestStart + 64) % 64;
+
+ const counterClockwiseDistance = (indexOfNearestStart - indexOfNearestEnd + 64) % 64;
+
+ const clockwiseIsShorter = clockwiseDistance <= counterClockwiseDistance;
+
+ // Collect arc points between start and end
+ let arcPoints: [number, number, number][] = [];
+
+ if (clockwiseIsShorter) {
+ if (indexOfNearestStart <= indexOfNearestEnd) {
+ arcPoints = circlePoints.slice(indexOfNearestStart, indexOfNearestEnd + 1);
+ } else {
+ // Wrap around
+ arcPoints = [
+ ...circlePoints.slice(indexOfNearestStart, 64),
+ ...circlePoints.slice(0, indexOfNearestEnd + 1)
+ ];
+ }
+ } else {
+ if (indexOfNearestStart >= indexOfNearestEnd) {
+ for (let i = indexOfNearestStart; i !== (indexOfNearestEnd - 1 + 64) % 64; i = (i - 1 + 64) % 64) {
+ arcPoints.push(circlePoints[i]);
+ }
+ } else {
+ for (let i = indexOfNearestStart; i !== (indexOfNearestEnd - 1 + 64) % 64; i = (i - 1 + 64) % 64) {
+ arcPoints.push(circlePoints[i]);
+ }
+ }
+ }
+
+ // Continue your custom path logic
+ const pathVectors = [
+ new THREE.Vector3(start[0], start[1], start[2]), // start
+ new THREE.Vector3(raisedStart[0], raisedStart[1], raisedStart[2]), // lift up
+ new THREE.Vector3(nearestToStart[0], raisedStart[1], nearestToStart[2]), // move to arc start
+ ...arcPoints.map(point => new THREE.Vector3(point[0], raisedStart[1], point[2])),
+ new THREE.Vector3(nearestToEnd[0], raisedEnd[1], nearestToEnd[2]), // move from arc end
+ new THREE.Vector3(raisedEnd[0], raisedEnd[1], raisedEnd[2]), // lowered end
+ new THREE.Vector3(end[0], end[1], end[2]) // end
+ ];
+
+ const customCurve = new THREE.CatmullRomCurve3(pathVectors, false, 'centripetal', 1);
+ const generatedPoints = customCurve.getPoints(100);
+ setCustomCurvePoints(generatedPoints);
+ }
+ }, [circlePoints, currentPath]);
+
+ // Frame update for animation
useFrame((_, delta) => {
- if (!ikSolver || !currentPath || currentPath.length === 0) return;
+ if (!ikSolver) return;
- const bone = ikSolver.mesh.skeleton.bones.find(
- (b: any) => b.name === targetBone
- );
+ const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
if (!bone) return;
- // Ensure currentPath is a valid array of 3D points, create a CatmullRomCurve3 from it
- const curve = new THREE.CatmullRomCurve3(
- currentPath.map(point => new THREE.Vector3(point[0], point[1], point[2]))
- );
+ if (isPlaying) {
+ if (!isPaused && customCurvePoints && currentPath.length > 0) {
+ const curvePoints = customCurvePoints;
+ const speedAdjustedProgress = progressRef.current + (speed * armBot.speed);
+ const index = Math.floor(speedAdjustedProgress);
+ if (index >= curvePoints.length) {
+ // Reached the end of the curve
+ HandleCallback();
+ setCurrentPath([]);
+ curveRef.current = null;
+ progressRef.current = 0;
+ } else {
+ const point = curvePoints[index];
+ bone.position.copy(point);
+ progressRef.current = speedAdjustedProgress;
+ }
+ } else if (isPaused) {
+ logStatus(armBot.modelUuid, 'Simulation Paused');
+ }
- const next = initialProgressRef.current + delta * 0.5;
- if (next >= 1) {
- // bone.position.copy(restPosition);
- HandleCallback(); // Call the callback when the path is completed
- initialProgressRef.current = 0; // Set ref to 1 when done
- } else {
- const point = curve.getPoint(next); // Get the interpolated point from the curve
- bone.position.copy(point); // Update the bone position along the curve
- initialProgressRef.current = next; // Update progress
+ ikSolver.update();
+ } else if (!isPlaying && currentPath.length === 0) {
+ // Not playing anymore, reset to rest
+ bone.position.copy(restPosition);
+ ikSolver.update();
}
-
- ikSolver.update();
});
-
return (
- <>>
- )
+ <>
+ {customCurvePoints && currentPath && isPlaying && (
+
+ [p.x, p.y, p.z] as [number, number, number])}
+ color="green"
+ lineWidth={5}
+ dashed={false}
+ />
+
+ )}
+
+
+
+
+ >
+ );
}
-export default RoboticArmAnimator;
\ No newline at end of file
+export default RoboticArmAnimator;
diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
index 6bde587..9199ede 100644
--- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
@@ -1,61 +1,141 @@
import React, { useEffect, useRef, useState } from 'react'
import IKInstance from '../ikInstance/ikInstance';
import RoboticArmAnimator from '../animator/roboticArmAnimator';
-import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
+import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore';
import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_4.glb";
import { useThree } from "@react-three/fiber";
import { useFloorItems } from '../../../../../store/store';
import useModuleStore from '../../../../../store/useModuleStore';
-import { Vector3 } from "three";
import * as THREE from "three";
+import { useSelectedAction, useSelectedProduct } from '../../../../../store/simulation/useSimulationStore';
+import { useProductStore } from '../../../../../store/simulation/useProductStore';
-interface Process {
- triggerId: string;
- startPoint?: Vector3;
- endPoint?: Vector3;
- speed: number;
-}
-function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) {
+function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
- const { isPlaying } = usePlayButtonStore();
const [currentPhase, setCurrentPhase] = useState<(string)>("init");
- const { scene } = useThree();
- const targetBone = "Target";
- const { activeModule } = useModuleStore();
- const [ikSolver, setIkSolver] = useState(null);
- const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore();
- const { floorItems } = useFloorItems();
- const groupRef = useRef(null);
- const [processes, setProcesses] = useState([]);
- const [armBotCurvePoints, setArmBotCurvePoints] = useState({ start: [], end: [] })
- const restPosition = new THREE.Vector3(0, 1, -1.6);
- let armBotCurveRef = useRef(null)
const [path, setPath] = useState<[number, number, number][]>([]);
+ const [ikSolver, setIkSolver] = useState(null);
+ const { scene } = useThree();
+ const restPosition = new THREE.Vector3(0, 1.75, -1.6);
+ const targetBone = "Target";
+ const groupRef = useRef(null);
+ const pauseTimeRef = useRef(null);
+ const isPausedRef = useRef(false);
+ let startTime: number;
+ //zustand
+ const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore();
+ const { products, getActionByUuid } = useProductStore();
+ const { selectedProduct } = useSelectedProduct();
+ const { floorItems } = useFloorItems();
+ const { activeModule } = useModuleStore();
+ const { isPlaying } = usePlayButtonStore();
+ const { isReset, setReset } = useResetButtonStore();
+ const { isPaused } = usePauseButtonStore();
+ const { selectedAction } = useSelectedAction();
+
+ function firstFrame() {
+ startTime = performance.now();
+ step();
+ }
+ function step() {
+ if (isPausedRef.current) {
+ if (!pauseTimeRef.current) {
+ pauseTimeRef.current = performance.now();
+ }
+ requestAnimationFrame(() => step());
+ return;
+ }
+ if (pauseTimeRef.current) {
+ const pauseDuration = performance.now() - pauseTimeRef.current;
+ startTime += pauseDuration;
+ pauseTimeRef.current = null;
+ }
+ const elapsedTime = performance.now() - startTime;
+ if (elapsedTime < 1500) {
+ // Wait until 1500ms has passed
+ requestAnimationFrame(step);
+ return;
+ }
+ if (currentPhase === "picking") {
+
+ setArmBotActive(armBot.modelUuid, true);
+ setArmBotState(armBot.modelUuid, "running");
+ setCurrentPhase("start-to-end");
+ startTime = 0
+ const startPoint = armBot.point.actions[0].process.startPoint;
+ const endPoint = armBot.point.actions[0].process.endPoint;
+ if (startPoint && endPoint) {
+ let curve = createCurveBetweenTwoPoints(
+ new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]),
+ new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]));
+ if (curve) {
+ logStatus(armBot.modelUuid, "picking the object");
+ setPath(curve.points.map(point => [point.x, point.y, point.z]))
+ }
+ }
+ logStatus(armBot.modelUuid, "Moving armBot from start point to end position.")
+ } else if (currentPhase === "dropping") {
+
+ setArmBotActive(armBot.modelUuid, true);
+ setArmBotState(armBot.modelUuid, "running");
+ setCurrentPhase("end-to-rest");
+ startTime = 0;
+ const endPoint = armBot.point.actions[0].process.endPoint;
+ if (endPoint) {
+
+ let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition);
+ if (curve) {
+ logStatus(armBot.modelUuid, "dropping the object");
+ setPath(curve.points.map(point => [point.x, point.y, point.z]));
+ }
+ }
+ logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
+ }
+
+ }
+ useEffect(() => {
+ isPausedRef.current = isPaused;
+ }, [isPaused]);
useEffect(() => {
- let armItems = floorItems?.filter((val: any) =>
- val.modelUuid === "3abf5d46-b59e-4e6b-9c02-a4634b64b82d"
- );
- // Get the first matching item
- let armItem = armItems?.[0];
- if (armItem) {
- const targetMesh = scene?.getObjectByProperty("uuid", armItem.modelUuid);
- if (targetMesh) {
- targetMesh.visible = activeModule !== "simulation"
- }
+ const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
+
+ if (targetMesh) {
+ targetMesh.visible = activeModule !== "simulation"
}
+
const targetBones = ikSolver?.mesh.skeleton.bones.find(
(b: any) => b.name === targetBone
);
+ if (isReset) {
+ logStatus(armBot.modelUuid, "Simulation Play Reset Successfully")
+ removeCurrentAction(armBot.modelUuid)
+ setArmBotActive(armBot.modelUuid, true)
+ setArmBotState(armBot.modelUuid, "running")
+ setCurrentPhase("init-to-rest");
+ isPausedRef.current = false
+ pauseTimeRef.current = null
+ isPausedRef.current = false
+ startTime = 0
+ if (targetBones) {
+ let curve = createCurveBetweenTwoPoints(targetBones.position, targetBones.position)
+ if (curve) {
+ setPath(curve.points.map(point => [point.x, point.y, point.z]));
+ }
+ }
+ setReset(false);
+ logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.")
+ }
if (isPlaying) {
- //Moving armBot from initial point to rest position.
- if (!robot?.isActive && robot?.state == "idle" && currentPhase == "init") {
- setArmBotActive(robot.modelUuid, true)
- setArmBotState(robot.modelUuid, "running")
+ //Moving armBot from initial point to rest position.
+ if (!armBot?.isActive && armBot?.state == "idle" && currentPhase == "init") {
+
+ setArmBotActive(armBot.modelUuid, true)
+ setArmBotState(armBot.modelUuid, "running")
setCurrentPhase("init-to-rest");
if (targetBones) {
let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition)
@@ -63,21 +143,26 @@ function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) {
setPath(curve.points.map(point => [point.x, point.y, point.z]));
}
}
- logStatus(robot.modelUuid, "Moving armBot from initial point to rest position.")
+ logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.")
}
//Waiting for trigger.
- else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && !robot.currentAction) {
- logStatus(robot.modelUuid, "Waiting to trigger CurrentAction")
- setTimeout(() => {
- addCurrentAction(robot.modelUuid, 'action-003');
+ else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) {
+ logStatus(armBot.modelUuid, "Waiting to trigger CurrentAction")
+ const timeoutId = setTimeout(() => {
+ addCurrentAction(armBot.modelUuid, selectedAction?.actionId);
+ console.log('selectedAction?.actionId: ', selectedAction?.actionId);
}, 3000);
+ return () => clearTimeout(timeoutId);
}
- else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && robot.currentAction) {
- if (robot.currentAction) {
- setArmBotActive(robot.modelUuid, true);
- setArmBotState(robot.modelUuid, "running");
+ else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && armBot.currentAction) {
+ if (armBot.currentAction) {
+
+ setArmBotActive(armBot.modelUuid, true);
+ setArmBotState(armBot.modelUuid, "running");
setCurrentPhase("rest-to-start");
- const startPoint = robot.point.actions[0].process.startPoint;
+ let actiondata = getActionByUuid(selectedProduct.productId, selectedAction.actionId)
+ console.log('actiondata: ', actiondata);
+ const startPoint = armBot.point.actions[0].process.startPoint;
if (startPoint) {
let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]));
if (curve) {
@@ -85,105 +170,109 @@ function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) {
}
}
}
- logStatus(robot.modelUuid, "Moving armBot from rest point to start position.")
+ logStatus(armBot.modelUuid, "Moving armBot from rest point to start position.")
}
- else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "picking" && robot.currentAction) {
- setArmBotActive(robot.modelUuid, true);
- setArmBotState(robot.modelUuid, "running");
- setCurrentPhase("start-to-end");
- const startPoint = robot.point.actions[0].process.startPoint;
- const endPoint = robot.point.actions[0].process.endPoint;
- if (startPoint && endPoint) {
- let curve = createCurveBetweenTwoPoints(
- new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]),
- new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2])
- );
- if (curve) {
- setTimeout(() => {
- logStatus(robot.modelUuid, "picking the object");
- setPath(curve.points.map(point => [point.x, point.y, point.z]));
- }, 1500)
- }
- }
- logStatus(robot.modelUuid, "Moving armBot from start point to end position.")
+ else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "picking" && armBot.currentAction) {
+ requestAnimationFrame(firstFrame);
+ // setArmBotActive(armBot.modelUuid, true);
+ // setArmBotState(armBot.modelUuid, "running");
+ // setCurrentPhase("start-to-end");
+ // const startPoint = armBot.point.actions[0].process.startPoint;
+ // const endPoint = armBot.point.actions[0].process.endPoint;
+ // if (startPoint && endPoint) {
+ // let curve = createCurveBetweenTwoPoints(
+ // new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]),
+ // new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]));
+ // if (curve) {
+ // setTimeout(() => {
+ // logStatus(armBot.modelUuid, "picking the object");
+ // setPath(curve.points.map(point => [point.x, point.y, point.z]));
+ // }, 1500)
+ // }
+ // }
+ // logStatus(armBot.modelUuid, "Moving armBot from start point to end position.")
}
- else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "dropping" && robot.currentAction) {
- setArmBotActive(robot.modelUuid, true);
- setArmBotState(robot.modelUuid, "running");
- setCurrentPhase("end-to-rest");
- const endPoint = robot.point.actions[0].process.endPoint;
- if (endPoint) {
- let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition
- );
- if (curve) {
- setTimeout(() => {
- logStatus(robot.modelUuid, "dropping the object");
- setPath(curve.points.map(point => [point.x, point.y, point.z]));
- }, 1500)
- }
- }
- logStatus(robot.modelUuid, "Moving armBot from end point to rest position.")
+ else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "dropping" && armBot.currentAction) {
+ requestAnimationFrame(firstFrame);
+ // setArmBotActive(armBot.modelUuid, true);
+ // setArmBotState(armBot.modelUuid, "running");
+ // setCurrentPhase("end-to-rest");
+ // const endPoint = armBot.point.actions[0].process.endPoint;
+ // if (endPoint) {
+ // let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition);
+ // if (curve) {
+ // setTimeout(() => {
+ // logStatus(armBot.modelUuid, "dropping the object");
+ // setPath(curve.points.map(point => [point.x, point.y, point.z]));
+ // }, 1500)
+ // }
+ // }
+ // logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
}
+ } else {
+ logStatus(armBot.modelUuid, "Simulation Play Stopped")
+ setArmBotActive(armBot.modelUuid, false)
+ setArmBotState(armBot.modelUuid, "idle")
+ setCurrentPhase("init");
+ setPath([])
+ removeCurrentAction(armBot.modelUuid)
+
}
- }, [currentPhase, robot, isPlaying, ikSolver])
+ }, [currentPhase, armBot, isPlaying, ikSolver, isReset])
function createCurveBetweenTwoPoints(p1: any, p2: any) {
const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5);
- mid.y += 0.5;
-
+ // mid.y += 0.5;
const points = [p1, mid, p2];
return new THREE.CatmullRomCurve3(points);
}
const HandleCallback = () => {
- if (robot.isActive && robot.state == "running" && currentPhase == "init-to-rest") {
- logStatus(robot.modelUuid, "Callback triggered: rest");
- setArmBotActive(robot.modelUuid, false)
- setArmBotState(robot.modelUuid, "idle")
+ if (armBot.isActive && armBot.state == "running" && currentPhase == "init-to-rest") {
+ logStatus(armBot.modelUuid, "Callback triggered: rest");
+ setArmBotActive(armBot.modelUuid, false)
+ setArmBotState(armBot.modelUuid, "idle")
setCurrentPhase("rest");
setPath([])
}
- else if (robot.isActive && robot.state == "running" && currentPhase == "rest-to-start") {
- logStatus(robot.modelUuid, "Callback triggered: pick.");
- setArmBotActive(robot.modelUuid, false)
- setArmBotState(robot.modelUuid, "idle")
+ else if (armBot.isActive && armBot.state == "running" && currentPhase == "rest-to-start") {
+ logStatus(armBot.modelUuid, "Callback triggered: pick.");
+ setArmBotActive(armBot.modelUuid, false)
+ setArmBotState(armBot.modelUuid, "idle")
setCurrentPhase("picking");
setPath([])
}
- else if (robot.isActive && robot.state == "running" && currentPhase == "start-to-end") {
- logStatus(robot.modelUuid, "Callback triggered: drop.");
- setArmBotActive(robot.modelUuid, false)
- setArmBotState(robot.modelUuid, "idle")
+ else if (armBot.isActive && armBot.state == "running" && currentPhase == "start-to-end") {
+ logStatus(armBot.modelUuid, "Callback triggered: drop.");
+ setArmBotActive(armBot.modelUuid, false)
+ setArmBotState(armBot.modelUuid, "idle")
setCurrentPhase("dropping");
setPath([])
}
- else if (robot.isActive && robot.state == "running" && currentPhase == "end-to-rest") {
- logStatus(robot.modelUuid, "Callback triggered: rest, cycle completed.");
- setArmBotActive(robot.modelUuid, false)
- setArmBotState(robot.modelUuid, "idle")
+ else if (armBot.isActive && armBot.state == "running" && currentPhase == "end-to-rest") {
+ logStatus(armBot.modelUuid, "Callback triggered: rest, cycle completed.");
+ setArmBotActive(armBot.modelUuid, false)
+ setArmBotState(armBot.modelUuid, "idle")
setCurrentPhase("rest");
setPath([])
- removeCurrentAction(robot.modelUuid)
+ removeCurrentAction(armBot.modelUuid)
}
}
const logStatus = (id: string, status: string) => {
- // console.log(id + "," + status);
- console.log( status);
+ //
+
}
return (
<>
-
-
-
+
+
>
)
}
-export default RoboticArmInstance;
\ No newline at end of file
+export default RoboticArmInstance;
diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx
index 645cbb5..e692ca5 100644
--- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx
@@ -3,20 +3,19 @@ import * as THREE from "three";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { clone } from "three/examples/jsm/utils/SkeletonUtils";
-import { useFrame, useLoader, useThree } from "@react-three/fiber";
+import { useLoader, useThree } from "@react-three/fiber";
import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver";
+import { TransformControls } from '@react-three/drei';
+
type IKInstanceProps = {
modelUrl: string;
ikSolver: any;
setIkSolver: any
- robot: any;
- groupRef: React.RefObject;
- processes: any;
- setArmBotCurvePoints: any
+ armBot: any;
+ groupRef: any;
};
-function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processes, setArmBotCurvePoints }: IKInstanceProps) {
-
- const { scene } = useThree();
+function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKInstanceProps) {
+ const { scene } = useThree()
const gltf = useLoader(GLTFLoader, modelUrl, (loader) => {
const draco = new DRACOLoader();
draco.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/");
@@ -25,6 +24,8 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processe
const cloned = useMemo(() => clone(gltf?.scene), [gltf]);
const targetBoneName = "Target";
const skinnedMeshName = "link_0";
+ const [selectedArm, setSelectedArm] = useState();
+
useEffect(() => {
if (!gltf) return;
const OOI: any = {};
@@ -66,15 +67,19 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processe
setIkSolver(solver);
const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05)
+ // groupRef.current.add(helper);
- // scene.add(helper)
+ setSelectedArm(OOI.Target_Bone);
+ scene.add(helper)
}, [gltf]);
return (
<>
-
+ {
+ setSelectedArm(groupRef.current?.getObjectByName(targetBoneName))
+ }}>
+ {/* {selectedArm && } */}
>
)
}
diff --git a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx
index 1089fa5..0acc1d9 100644
--- a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
import RoboticArmInstance from './armInstance/roboticArmInstance';
import { useArmBotStore } from '../../../../store/simulation/useArmBotStore';
@@ -8,9 +7,8 @@ function RoboticArmInstances() {
return (
<>
{armBots?.map((robot: ArmBotStatus) => (
-
+
))}
-
>
)
}
diff --git a/app/src/modules/simulation/roboticArm/roboticArm.tsx b/app/src/modules/simulation/roboticArm/roboticArm.tsx
index 69e6da0..2cb9ea6 100644
--- a/app/src/modules/simulation/roboticArm/roboticArm.tsx
+++ b/app/src/modules/simulation/roboticArm/roboticArm.tsx
@@ -2,18 +2,26 @@ import { useEffect } from "react";
import RoboticArmInstances from "./instances/roboticArmInstances";
import { useArmBotStore } from "../../../store/simulation/useArmBotStore";
import { useFloorItems } from "../../../store/store";
+import { useSelectedEventData, useSelectedEventSphere, useSelectedProduct } from "../../../store/simulation/useSimulationStore";
+import { useProductStore } from "../../../store/simulation/useProductStore";
+import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
+import ArmBotUI from "../ui/arm/armBotUI";
function RoboticArm() {
- const { armBots, addArmBot, removeArmBot } = useArmBotStore();
+ const { armBots, addArmBot, clearArmBots } = useArmBotStore();
+ const { products, getProductById } = useProductStore();
+ const { selectedProduct } = useSelectedProduct();
+ const { selectedEventSphere } = useSelectedEventSphere();
+ const { selectedEventData } = useSelectedEventData();
+ const { isPlaying } = usePlayButtonStore();
const { floorItems } = useFloorItems();
-
const armBotStatusSample: RoboticArmEventSchema[] = [
{
state: "idle",
- modelUuid: "3abf5d46-b59e-4e6b-9c02-a4634b64b82d",
+ modelUuid: "8790b4d5-fb6e-49e0-8161-04945fe3fdc4",
modelName: "ArmBot-X200",
- position: [0.20849215906958463, 0, 0.32079278127773675],
+ position: [4.317833205016363, 0, -3.2829924989068715],
rotation: [-1.3768690876192207e-15, 1.4883085074751308, 1.5407776675834467e-15],
type: "roboticArm",
speed: 1.5,
@@ -29,19 +37,9 @@ function RoboticArm() {
process: {
startPoint: [-1, 2, 1],
endPoint: [-2, 1, -1],
+ // startPoint: [-2, 1, -1],
+ // endPoint: [-1, 2, 1],
},
- // process: {
- // "startPoint": [
- // 0.37114476008711866,
- // 1.9999999999999998,
- // 1.8418816116721384
- // ],
- // "endPoint": [
- // -0.42197069459490777,
- // 1,
- // -3.159515927851809
- // ]
- // },
triggers: [
{
triggerUuid: "trigger-001",
@@ -95,7 +93,7 @@ function RoboticArm() {
position: [95.94347308985614, 0, 6.742905194869091],
rotation: [0, 0, 0],
type: "roboticArm",
- speed: 1.5,
+ speed: 0.1,
point: {
uuid: "point-123",
position: [0, 1.5, 0],
@@ -106,8 +104,10 @@ function RoboticArm() {
actionName: "Pick Component",
actionType: "pickAndPlace",
process: {
- startPoint: [2.52543010919071, 0, 8.433681161200905],
- endPoint: [95.3438373267953, 0, 9.0279187421610025],
+ // startPoint: [2.52543010919071, 0, 8.433681161200905],
+ // endPoint: [95.3438373267953, 0, 9.0279187421610025],
+ startPoint: null,
+ endPoint: null,
},
triggers: [
{
@@ -158,20 +158,32 @@ function RoboticArm() {
];
useEffect(() => {
-
- removeArmBot(armBotStatusSample[0].modelUuid);
- addArmBot('123', armBotStatusSample[0]);
- // addArmBot('123', armBotStatusSample[1]);
- // addCurrentAction('armbot-xyz-001', 'action-001');
- }, []);
+ if (selectedProduct.productId) {
+ const product = getProductById(selectedProduct.productId);
+ if (product) {
+ clearArmBots();
+ product.eventDatas.forEach(events => {
+ if (events.type === 'roboticArm') {
+ addArmBot(selectedProduct.productId, events);
+ }
+ });
+ }
+ }
+ }, [selectedProduct, products]);
useEffect(() => {
-
- }, [armBots]);
+ }, [armBots])
+
+ useEffect(() => {
+
+ }, [selectedEventData, selectedEventSphere, isPlaying]);
return (
<>
+ {selectedEventSphere && selectedEventData?.data.type === "roboticArm" &&
+ < ArmBotUI />
+ }
>
);
}
diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx
index 5ca0ec5..efacd53 100644
--- a/app/src/modules/simulation/simulation.tsx
+++ b/app/src/modules/simulation/simulation.tsx
@@ -23,20 +23,20 @@ function Simulation() {
}, [events])
useEffect(() => {
- // console.log('products: ', products);
+ console.log('products: ', products);
}, [products])
return (
<>
+
+
{activeModule === 'simulation' &&
<>
-
-
diff --git a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx
index efefa0c..f9fc30f 100644
--- a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx
+++ b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from "react";
-import { useThree } from "@react-three/fiber";
+import { useFrame, useThree } from "@react-three/fiber";
import * as THREE from "three";
import { useSubModuleStore } from "../../../../store/useModuleStore";
import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore";
@@ -7,21 +7,28 @@ import { useProductStore } from "../../../../store/simulation/useProductStore";
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
import { handleAddEventToProduct } from "../../events/points/functions/handleAddEventToProduct";
+import { QuadraticBezierLine } from "@react-three/drei";
+import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi";
+import { useDeleteTool } from "../../../../store/store";
interface ConnectionLine {
id: string;
- start: THREE.Vector3;
- end: THREE.Vector3;
- mid: THREE.Vector3;
+ startPointUuid: string;
+ endPointUuid: string;
trigger: TriggerSchema;
}
function TriggerConnector() {
- const { gl, raycaster, scene } = useThree();
+ const { gl, raycaster, scene, pointer, camera } = useThree();
const { subModule } = useSubModuleStore();
- const { products, getPointByUuid, getIsEventInProduct, getActionByUuid, addTrigger, addEvent, getEventByModelUuid } = useProductStore();
+ const { products, getPointByUuid, getIsEventInProduct, getActionByUuid, addTrigger, removeTrigger, addEvent, getEventByModelUuid, getProductById } = useProductStore();
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
const { selectedProduct } = useSelectedProduct();
+ const [hoveredLineKey, setHoveredLineKey] = useState(null);
+ const groupRefs = useRef>({});
+ const [helperlineColor, setHelperLineColor] = useState("red");
+ const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3; end: THREE.Vector3; mid: THREE.Vector3; } | null>(null);
+ const { deleteTool } = useDeleteTool();
const [firstSelectedPoint, setFirstSelectedPoint] = useState<{
productId: string;
@@ -32,52 +39,99 @@ function TriggerConnector() {
const [connections, setConnections] = useState([]);
+ const email = localStorage.getItem('email')
+ const organization = (email!.split("@")[1]).split(".")[0];
+
+ const updateBackend = (
+ productName: string,
+ productId: string,
+ organization: string,
+ eventData: EventsSchema
+ ) => {
+ upsertProductOrEventApi({
+ productName: productName,
+ productId: productId,
+ organization: organization,
+ eventDatas: eventData
+ })
+ }
+
useEffect(() => {
const newConnections: ConnectionLine[] = [];
- products.forEach(product => {
- product.eventDatas.forEach(event => {
- if ('points' in event) {
- event.points.forEach(point => {
- if ('action' in point && point.action?.triggers) {
- point.action.triggers.forEach(trigger => {
- if (trigger.triggeredAsset) {
- const targetPoint = getPointByUuid(
- product.productId,
- trigger.triggeredAsset.triggeredModel.modelUuid,
- trigger.triggeredAsset.triggeredPoint.pointUuid
- );
+ const product = getProductById(selectedProduct.productId);
+ if (!product || products.length === 0) return;
- if (targetPoint) {
- const startPos = new THREE.Vector3(...point.position);
- const endPos = new THREE.Vector3(...targetPoint.position);
- const midPos = new THREE.Vector3()
- .addVectors(startPos, endPos)
- .multiplyScalar(0.5)
- .add(new THREE.Vector3(0, 2, 0));
-
- newConnections.push({
- id: `${point.uuid}-${targetPoint.uuid}-${trigger.triggerUuid}`,
- start: startPos,
- end: endPos,
- mid: midPos,
- trigger
- });
- }
- }
+ product.eventDatas.forEach(event => {
+ // Handle Conveyor points
+ if (event.type === "transfer" && 'points' in event) {
+ event.points.forEach(point => {
+ if (point.action?.triggers) {
+ point.action.triggers.forEach(trigger => {
+ if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
+ newConnections.push({
+ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
+ startPointUuid: point.uuid,
+ endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
+ trigger
+ });
+ }
+ });
+ }
+ });
+ }
+ // Handle Vehicle point
+ else if (event.type === "vehicle" && 'point' in event) {
+ const point = event.point;
+ if (point.action?.triggers) {
+ point.action.triggers.forEach(trigger => {
+ if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
+ newConnections.push({
+ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
+ startPointUuid: point.uuid,
+ endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
+ trigger
});
}
});
}
- });
+ }
+ // Handle Robotic Arm points
+ else if (event.type === "roboticArm" && 'point' in event) {
+ const point = event.point;
+ point.actions?.forEach(action => {
+ action.triggers?.forEach(trigger => {
+ if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
+ newConnections.push({
+ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
+ startPointUuid: point.uuid,
+ endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
+ trigger
+ });
+ }
+ });
+ });
+ }
+ // Handle Machine point
+ else if (event.type === "machine" && 'point' in event) {
+ const point = event.point;
+ if (point.action?.triggers) {
+ point.action.triggers.forEach(trigger => {
+ if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
+ newConnections.push({
+ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
+ startPointUuid: point.uuid,
+ endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
+ trigger
+ });
+ }
+ });
+ }
+ }
});
setConnections(newConnections);
- }, [products]);
-
- useEffect(() => {
- console.log('connections: ', connections);
- }, connections)
+ }, [products, selectedProduct.productId]);
useEffect(() => {
const canvasElement = gl.domElement;
@@ -111,15 +165,31 @@ function TriggerConnector() {
if (drag) return;
evt.preventDefault();
- const intersects = raycaster.intersectObjects(scene.children, true);
- if (intersects.length === 0) return;
+ const intersects = raycaster
+ .intersectObjects(scene.children, true)
+ .filter(
+ (intersect) =>
+ intersect.object.name === ('Event-Sphere')
+ );
+ if (intersects.length === 0) {
+ setFirstSelectedPoint(null);
+ return;
+ };
const currentObject = intersects[0].object;
- if (!currentObject || currentObject.name !== 'Event-Sphere') return;
+ if (!currentObject || currentObject.name !== 'Event-Sphere') {
+ setFirstSelectedPoint(null);
+ return;
+ };
const modelUuid = currentObject.userData.modelUuid;
const pointUuid = currentObject.userData.pointUuid;
+ if (firstSelectedPoint && firstSelectedPoint.pointUuid === pointUuid) {
+ setFirstSelectedPoint(null);
+ return;
+ }
+
if (selectedProduct && getIsEventInProduct(selectedProduct.productId, modelUuid)) {
const point = getPointByUuid(
@@ -128,7 +198,12 @@ function TriggerConnector() {
pointUuid
);
- if (!point) return;
+ const event = getEventByModelUuid(selectedProduct.productId, modelUuid);
+
+ if (!point || !event) {
+ setFirstSelectedPoint(null);
+ return;
+ };
let actionUuid: string | undefined;
if ('action' in point && point.action) {
@@ -152,12 +227,12 @@ function TriggerConnector() {
delay: 0,
triggeredAsset: {
triggeredModel: {
- modelName: currentObject.parent?.parent?.name || 'Unknown',
+ modelName: event.modelName || 'Unknown',
modelUuid: modelUuid
},
triggeredPoint: {
- pointName: currentObject.name,
- pointUuid: pointUuid
+ pointName: 'Point',
+ pointUuid: point.uuid
},
triggeredAction: actionUuid ? {
actionName: getActionByUuid(selectedProduct.productId, actionUuid)?.actionName || 'Action',
@@ -167,7 +242,16 @@ function TriggerConnector() {
};
if (firstSelectedPoint.actionUuid) {
- addTrigger(firstSelectedPoint.actionUuid, trigger);
+ const event = addTrigger(selectedProduct.productId, firstSelectedPoint.actionUuid, trigger);
+
+ if (event) {
+ updateBackend(
+ selectedProduct.productName,
+ selectedProduct.productId,
+ organization,
+ event
+ );
+ }
}
setFirstSelectedPoint(null);
}
@@ -184,7 +268,12 @@ function TriggerConnector() {
pointUuid
);
- if (!point) return;
+ const event = getEventByModelUuid(selectedProduct.productId, modelUuid);
+
+ if (!point || !event) {
+ setFirstSelectedPoint(null);
+ return;
+ };
let actionUuid: string | undefined;
if ('action' in point && point.action) {
@@ -200,12 +289,12 @@ function TriggerConnector() {
delay: 0,
triggeredAsset: {
triggeredModel: {
- modelName: currentObject.parent?.parent?.name || 'Unknown',
+ modelName: event.modelName || 'Unknown',
modelUuid: modelUuid
},
triggeredPoint: {
- pointName: currentObject.name,
- pointUuid: pointUuid
+ pointName: 'Point',
+ pointUuid: point.uuid
},
triggeredAction: actionUuid ? {
actionName: getActionByUuid(selectedProduct.productId, actionUuid)?.actionName || 'Action',
@@ -215,13 +304,24 @@ function TriggerConnector() {
};
if (firstSelectedPoint.actionUuid) {
- addTrigger(firstSelectedPoint.actionUuid, trigger);
+ const event = addTrigger(selectedProduct.productId, firstSelectedPoint.actionUuid, trigger);
+
+ if (event) {
+ updateBackend(
+ selectedProduct.productName,
+ selectedProduct.productId,
+ organization,
+ event
+ );
+ }
}
setFirstSelectedPoint(null);
+ } else if (firstSelectedPoint) {
+ setFirstSelectedPoint(null);
}
};
- if (subModule === 'simulations') {
+ if (subModule === 'simulations' && !deleteTool) {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
@@ -235,11 +335,149 @@ function TriggerConnector() {
canvasElement.removeEventListener('contextmenu', handleRightClick);
};
- }, [gl, subModule, selectedProduct, firstSelectedPoint]);
+ }, [gl, subModule, selectedProduct, firstSelectedPoint, deleteTool]);
+
+
+ useFrame(() => {
+ if (firstSelectedPoint) {
+ raycaster.setFromCamera(pointer, camera);
+ const intersects = raycaster.intersectObjects(scene.children, true).filter(
+ (intersect) =>
+ !intersect.object.name.includes("Roof") &&
+ !intersect.object.name.includes("agv-collider") &&
+ !intersect.object.name.includes("MeasurementReference") &&
+ !intersect.object.parent?.name.includes("Zone") &&
+ !(intersect.object.type === "GridHelper") &&
+ !(intersect.object.type === "Line2")
+ );
+
+ let point: THREE.Vector3 | null = null;
+
+ if (intersects.length > 0) {
+ point = intersects[0].point;
+ if (point.y < 0.05) {
+ point = new THREE.Vector3(point.x, 0.05, point.z);
+ }
+ }
+
+ const sphereIntersects = raycaster.intersectObjects(scene.children, true).filter((intersect) => intersect.object.name === ('Event-Sphere'));
+
+ if (sphereIntersects.length > 0 && sphereIntersects[0].object.uuid === firstSelectedPoint.pointUuid) {
+ setHelperLineColor('red');
+ setCurrentLine(null);
+ return;
+ }
+
+ const startPoint = getWorldPositionFromScene(firstSelectedPoint.pointUuid);
+
+ if (point && startPoint) {
+ if (sphereIntersects.length > 0) {
+ point = sphereIntersects[0].object.getWorldPosition(new THREE.Vector3());
+ }
+ const distance = startPoint.distanceTo(point);
+ const heightFactor = Math.max(0.5, distance * 0.2);
+ const midPoint = new THREE.Vector3(
+ (startPoint.x + point.x) / 2,
+ Math.max(startPoint.y, point.y) + heightFactor,
+ (startPoint.z + point.z) / 2
+ );
+
+ const endPoint = point;
+
+ setCurrentLine({
+ start: startPoint,
+ mid: midPoint,
+ end: endPoint,
+ });
+
+ setHelperLineColor(sphereIntersects.length > 0 ? "#6cf542" : "red");
+ } else {
+ setCurrentLine(null);
+ }
+ } else {
+ setCurrentLine(null);
+ }
+
+ })
+
+ const getWorldPositionFromScene = (pointUuid: string): THREE.Vector3 | null => {
+ const pointObj = scene.getObjectByProperty("uuid", pointUuid);
+ if (!pointObj) return null;
+
+ const worldPosition = new THREE.Vector3();
+ pointObj.getWorldPosition(worldPosition);
+ return worldPosition;
+ };
+
+ const removeConnection = (connection: ConnectionLine) => {
+ if (connection.trigger.triggerUuid) {
+ const event = removeTrigger(selectedProduct.productId, connection.trigger.triggerUuid);
+ if (event) {
+ updateBackend(
+ selectedProduct.productName,
+ selectedProduct.productId,
+ organization,
+ event
+ );
+ }
+ }
+ };
return (
- <>
- >
+
+ {connections.map((connection) => {
+ const startPoint = getWorldPositionFromScene(connection.startPointUuid);
+ const endPoint = getWorldPositionFromScene(connection.endPointUuid);
+
+ if (!startPoint || !endPoint) return null;
+
+ const distance = startPoint.distanceTo(endPoint);
+ const heightFactor = Math.max(0.5, distance * 0.2);
+ const midPoint = new THREE.Vector3(
+ (startPoint.x + endPoint.x) / 2,
+ Math.max(startPoint.y, endPoint.y) + heightFactor,
+ (startPoint.z + endPoint.z) / 2
+ );
+
+ return (
+ (groupRefs.current[connection.id] = el!)}
+ start={startPoint.toArray()}
+ end={endPoint.toArray()}
+ mid={midPoint.toArray()}
+ color={deleteTool && hoveredLineKey === connection.id ? "red" : "#42a5f5"}
+ lineWidth={4}
+ dashed={deleteTool && hoveredLineKey === connection.id ? false : true}
+ dashSize={0.75}
+ dashScale={20}
+ onPointerOver={() => setHoveredLineKey(connection.id)}
+ onPointerOut={() => setHoveredLineKey(null)}
+ onClick={() => {
+ if (deleteTool) {
+ setHoveredLineKey(null);
+ setCurrentLine(null);
+ removeConnection(connection);
+ }
+ }}
+ userData={connection.trigger}
+ />
+ );
+ })}
+
+ {currentLine && (
+
+ )}
+
);
}
diff --git a/app/src/modules/simulation/ui/arm/PickDropPoints.tsx b/app/src/modules/simulation/ui/arm/PickDropPoints.tsx
index 4544a46..c44eee8 100644
--- a/app/src/modules/simulation/ui/arm/PickDropPoints.tsx
+++ b/app/src/modules/simulation/ui/arm/PickDropPoints.tsx
@@ -9,7 +9,7 @@ interface PickDropProps {
actionType: "pick" | "drop";
actionUuid: string;
gltfScene: THREE.Group;
- selectedPoint: THREE.Mesh | null;
+
handlePointerDown: (e: ThreeEvent) => void;
isSelected: boolean;
}
@@ -21,12 +21,10 @@ const PickDropPoints: React.FC = ({
actionType,
actionUuid,
gltfScene,
- selectedPoint,
handlePointerDown,
isSelected,
}) => {
const groupRef = useRef(null);
-
return (
= ({
: new THREE.Vector3(0, 0, 0)
}
onPointerDown={(e) => {
- e.stopPropagation(); // Important to prevent event bubbling
+
+ e.stopPropagation(); // Prevent event bubbling
if (!isSelected) return;
handlePointerDown(e);
}}
userData={{ modelUuid, pointUuid, actionType, actionUuid }}
>
{
+ const cloned = gltfScene.clone();
+ cloned.traverse((child: any) => {
+ if (child.isMesh) {
+ child.userData = { modelUuid, pointUuid, actionType, actionUuid };
+ }
+ });
+ return cloned;
+ })()}
+ position={[0, 0, 0]}
scale={[0.5, 0.5, 0.5]}
/>
diff --git a/app/src/modules/simulation/ui/arm/armBotUI.tsx b/app/src/modules/simulation/ui/arm/armBotUI.tsx
new file mode 100644
index 0000000..8a2d18f
--- /dev/null
+++ b/app/src/modules/simulation/ui/arm/armBotUI.tsx
@@ -0,0 +1,190 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { useSelectedAction, useSelectedEventData, useSelectedEventSphere, useSelectedProduct } from '../../../../store/simulation/useSimulationStore';
+import { useArmBotStore } from '../../../../store/simulation/useArmBotStore';
+import { useGLTF } from '@react-three/drei';
+import { useThree } from '@react-three/fiber';
+import { useProductStore } from '../../../../store/simulation/useProductStore';
+import PickDropPoints from './PickDropPoints';
+import useDraggableGLTF from './useDraggableGLTF';
+import * as THREE from 'three';
+
+import armPick from "../../../../assets/gltf-glb/arm_ui_pick.glb";
+import armDrop from "../../../../assets/gltf-glb/arm_ui_drop.glb";
+import useModuleStore from '../../../../store/useModuleStore';
+
+type Positions = {
+ pick: [number, number, number];
+ drop: [number, number, number];
+ default: [number, number, number];
+};
+
+const ArmBotUI = () => {
+ const { getEventByModelUuid } = useProductStore();
+ const { selectedEventData } = useSelectedEventData();
+ const { selectedProduct } = useSelectedProduct();
+ const { armBots, updateStartPoint, updateEndPoint } = useArmBotStore();
+ const { scene } = useThree();
+ const { selectedAction } = useSelectedAction();
+
+ const armUiPick = useGLTF(armPick) as any;
+ const armUiDrop = useGLTF(armDrop) as any;
+
+ const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 0, 0]);
+ const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 0, 0]);
+ const [selectedArmBotData, setSelectedArmBotData] = useState(null);
+
+ // Fetch and setup selected ArmBot data
+ useEffect(() => {
+ if (selectedEventData?.data.type === "roboticArm") {
+ const selectedArmBot = getEventByModelUuid(selectedProduct.productId, selectedEventData.data.modelUuid);
+
+ if (selectedArmBot?.type === "roboticArm") {
+ setSelectedArmBotData(selectedArmBot);
+ const defaultPositions = getDefaultPositions(selectedArmBot.modelUuid);
+ const matchingAction = armBots?.flatMap((robot: ArmBotStatus) => robot.point.actions)
+ .find((action) => action.actionUuid === selectedAction.actionId);
+ if (matchingAction) {
+ const startPoint = matchingAction.process.startPoint;
+ const pickPosition = (!startPoint || (Array.isArray(startPoint) && startPoint.every(v => v === 0)))
+ ? defaultPositions.pick
+ : startPoint;
+
+ const endPoint = matchingAction.process.endPoint;
+ const dropPosition = (!endPoint || (Array.isArray(endPoint) && endPoint.every(v => v === 0)))
+ ? defaultPositions.drop
+ : endPoint;
+
+ setStartPosition(pickPosition);
+ setEndPosition(dropPosition);
+ }
+ }
+ }
+ }, [selectedEventData, selectedProduct, getEventByModelUuid, selectedAction]);
+
+
+ function getDefaultPositions(modelUuid: string): Positions {
+ const modelData = getEventByModelUuid(selectedProduct.productId, modelUuid);
+
+ if (modelData?.type === "roboticArm") {
+ const baseX = modelData.point.position?.[0] || 0;
+ const baseY = modelData.point.position?.[1] || 0;;
+ const baseZ = modelData.point.position?.[2] || 0;
+ return {
+ pick: [baseX, baseY, baseZ + 0.5],
+ drop: [baseX, baseY, baseZ - 0.5],
+ default: [baseX, baseY, baseZ],
+ };
+ }
+
+ return {
+ pick: [0.5, 1.5, 0],
+ drop: [-0.5, 1.5, 0],
+ default: [0, 1.5, 0],
+ };
+ }
+
+ function getLocalPosition(parentUuid: string, worldPosArray: [number, number, number] | null): [number, number, number] | null {
+ if (worldPosArray) {
+ const worldPos = new THREE.Vector3(...worldPosArray);
+ const parentObject = scene.getObjectByProperty('uuid', parentUuid);
+
+ if (parentObject) {
+ const localPos = worldPos.clone();
+ parentObject.worldToLocal(localPos);
+ return [localPos.x, localPos.y, localPos.z];
+ }
+ }
+ return null;
+ }
+
+ const updatePointToState = (obj: THREE.Object3D) => {
+ const { modelUuid, actionType, actionUuid } = obj.userData;
+ const newPosition = new THREE.Vector3();
+ obj.getWorldPosition(newPosition);
+ const worldPositionArray = newPosition.toArray() as [number, number, number];
+
+ if (selectedEventData?.data.type === "roboticArm") {
+ const selectedArmBot = getEventByModelUuid(selectedProduct.productId, selectedEventData.data.modelUuid);
+
+ const armBot = selectedArmBot?.modelUuid === modelUuid ? selectedArmBot : null;
+ if (!armBot) return;
+
+ if (armBot.type === "roboticArm") {
+ armBot?.point?.actions?.map((action: any) => {
+ if (action.actionUuid === actionUuid) {
+ const updatedProcess = { ...action.process };
+
+ if (actionType === "pick") {
+ updatedProcess.startPoint = getLocalPosition(modelUuid, worldPositionArray);
+ setStartPosition(updatedProcess.startPoint)
+ updateStartPoint(modelUuid, actionUuid, updatedProcess.startPoint);
+ } else if (actionType === "drop") {
+ updatedProcess.endPoint = getLocalPosition(modelUuid, worldPositionArray);
+ setEndPosition(updatedProcess.endPoint)
+ updateEndPoint(modelUuid, actionUuid, updatedProcess.endPoint);
+ }
+ return {
+ ...action,
+ process: updatedProcess,
+ };
+ }
+ return action;
+ });
+
+ }
+ }
+ }
+
+ useEffect(() => {
+
+
+ }, [armBots])
+
+ const { handlePointerDown } = useDraggableGLTF(updatePointToState);
+
+ if (!selectedArmBotData || !Array.isArray(selectedArmBotData.point?.actions)) {
+ return null; // avoid rendering if no data yet
+ }
+ return (
+ <>
+ {selectedArmBotData.point.actions.map((action: any) => {
+ if (action.actionUuid === selectedAction.actionId) {
+ return (
+
+
+
+
+
+
+ );
+ } else {
+ return null; // important! must return something
+ }
+ })}
+ >
+ );
+
+};
+
+export default ArmBotUI;
diff --git a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts
index b7e9272..e450bfd 100644
--- a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts
+++ b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts
@@ -1,11 +1,11 @@
-import { useRef } from "react";
+import { useRef, useState } 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 { camera, gl, controls } = useThree();
const activeObjRef = useRef(null);
const planeRef = useRef(
new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
@@ -15,6 +15,7 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
+ const [objectWorldPos, setObjectWorldPos] = useState(new THREE.Vector3());
const handlePointerDown = (e: ThreeEvent) => {
e.stopPropagation();
@@ -33,10 +34,10 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
activeObjRef.current = obj;
initialPositionRef.current.copy(obj.position);
+
// Get world position
- const objectWorldPos = new THREE.Vector3();
- obj.getWorldPosition(objectWorldPos);
+ setObjectWorldPos(obj.getWorldPosition(objectWorldPos));
// Set plane at the object's Y level
planeRef.current.set(new THREE.Vector3(0, 1, 0), -objectWorldPos.y);
@@ -61,52 +62,56 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
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);
+ 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();
-
+
+ // OnPointerDown
+ initialPositionRef.current.copy(objectWorldPos);
+
+ // OnPointerMove
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
- );
+ const { x: initialX, y: initialY } = initialPositionRef.current;
+ const { x: objectX, z: objectZ } = objectWorldPos;
+
+ const deltaX = intersection.x - initialX;
+
+ targetPosition.set(objectX, initialY + deltaX, objectZ);
} 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);
};
@@ -126,6 +131,3 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
return { handlePointerDown };
}
-
-
-
diff --git a/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx b/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx
index 5dec724..2a124cd 100644
--- a/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx
+++ b/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx
@@ -1,12 +1,16 @@
import React, { useEffect, useRef, useState } from 'react';
+import * as Types from "../../../../types/world/worldTypes";
import startPoint from "../../../../assets/gltf-glb/arrow_green.glb";
-import startEnd from "../../../../assets/gltf-glb/arrow_red.glb";
import * as THREE from "three";
+import startEnd from "../../../../assets/gltf-glb/arrow_red.glb";
import { useGLTF } from '@react-three/drei';
import { useFrame, useThree } from '@react-three/fiber';
-import { useSelectedEventSphere } from '../../../../store/simulation/useSimulationStore';
+import { useSelectedEventSphere, useIsDragging, useIsRotating } from '../../../../store/simulation/useSimulationStore';
import { useVehicleStore } from '../../../../store/simulation/useVehicleStore';
-import * as Types from "../../../../types/world/worldTypes";
+import { useProductStore } from '../../../../store/simulation/useProductStore';
+import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore';
+import { upsertProductOrEventApi } from '../../../../services/simulation/UpsertProductOrEventApi';
+
const VehicleUI = () => {
const { scene: startScene } = useGLTF(startPoint) as any;
const { scene: endScene } = useGLTF(startEnd) as any;
@@ -14,67 +18,60 @@ const VehicleUI = () => {
const endMarker = useRef(null);
const prevMousePos = useRef({ x: 0, y: 0 });
const { selectedEventSphere } = useSelectedEventSphere();
- const { vehicles, updateVehicle } = useVehicleStore();
+ const { selectedProduct } = useSelectedProduct();
+ const { getVehicleById } = useVehicleStore();
+ const { updateEvent } = useProductStore();
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 0, 0]);
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 0, 0]);
const [startRotation, setStartRotation] = useState<[number, number, number]>([0, 0, 0]);
const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]);
- const [isDragging, setIsDragging] = useState<"start" | "end" | null>(null);
- const [isRotating, setIsRotating] = useState<"start" | "end" | null>(null);
+ const { isDragging, setIsDragging } = useIsDragging();
+ const { isRotating, setIsRotating } = useIsRotating();
const { raycaster } = useThree();
const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0));
const state: Types.ThreeState = useThree();
const controls: any = state.controls;
+ const email = localStorage.getItem('email')
+ const organization = (email!.split("@")[1]).split(".")[0];
+
+ const updateBackend = (
+ productName: string,
+ productId: string,
+ organization: string,
+ eventData: EventsSchema
+ ) => {
+ upsertProductOrEventApi({
+ productName: productName,
+ productId: productId,
+ organization: organization,
+ eventDatas: eventData
+ })
+ }
+
useEffect(() => {
if (!selectedEventSphere) return;
- const selectedVehicle = vehicles.find(
- (vehicle: any) => vehicle.modelUuid === selectedEventSphere.userData.modelUuid
- );
+ const selectedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid);
if (selectedVehicle?.point?.action) {
const { pickUpPoint, unLoadPoint } = selectedVehicle.point.action;
if (pickUpPoint) {
- const pickupPosition = new THREE.Vector3(
- pickUpPoint.position.x,
- pickUpPoint.position.y,
- pickUpPoint.position.z
- );
- const pickupRotation = new THREE.Vector3(
- pickUpPoint.rotation.x,
- pickUpPoint.rotation.y,
- pickUpPoint.rotation.z
- );
- pickupPosition.y = 0;
- setStartPosition([pickupPosition.x, 0, pickupPosition.z]);
- setStartRotation([pickupRotation.x, pickupRotation.y, pickupRotation.z]);
+ setStartPosition([pickUpPoint.position.x, 0, pickUpPoint.position.z]);
+ setStartRotation([pickUpPoint.rotation.x, pickUpPoint.rotation.y, pickUpPoint.rotation.z]);
} else {
const defaultLocal = new THREE.Vector3(0, 0, 1.5);
const defaultWorld = selectedEventSphere.localToWorld(defaultLocal);
- defaultWorld.y = 0;
setStartPosition([defaultWorld.x, 0, defaultWorld.z]);
setStartRotation([0, 0, 0]);
}
if (unLoadPoint) {
- const unLoadPosition = new THREE.Vector3(
- unLoadPoint.position.x,
- unLoadPoint.position.y,
- unLoadPoint.position.z
- );
- const unLoadRotation = new THREE.Vector3(
- unLoadPoint.rotation.x,
- unLoadPoint.rotation.y,
- unLoadPoint.position.z
- );
- unLoadPosition.y = 0; // Force y to 0
- setEndPosition([unLoadPosition.x, 0, unLoadPosition.z]);
- setEndRotation([unLoadRotation.x, unLoadRotation.y, unLoadRotation.z]);
+ setEndPosition([unLoadPoint.position.x, 0, unLoadPoint.position.z]);
+ setEndRotation([unLoadPoint.rotation.x, unLoadPoint.rotation.y, unLoadPoint.rotation.z]);
} else {
const defaultLocal = new THREE.Vector3(0, 0, -1.5);
const defaultWorld = selectedEventSphere.localToWorld(defaultLocal);
- defaultWorld.y = 0; // Force y to 0
setEndPosition([defaultWorld.x, 0, defaultWorld.z]);
setEndRotation([0, 0, 0]);
}
@@ -87,7 +84,6 @@ const VehicleUI = () => {
const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint);
if (intersects) {
- intersectPoint.y = 0; // Force y to 0
if (isDragging === "start") {
setStartPosition([intersectPoint.x, 0, intersectPoint.z]);
}
@@ -108,12 +104,12 @@ const VehicleUI = () => {
if (marker) {
const rotationSpeed = 10;
- marker.rotation.y += deltaX * rotationSpeed;
if (isRotating === 'start') {
- setStartRotation([marker.rotation.x, marker.rotation.y, marker.rotation.z])
+ const y = startRotation[1] + deltaX * rotationSpeed;
+ setStartRotation([0, y, 0]);
} else {
-
- setEndRotation([marker.rotation.x, marker.rotation.y, marker.rotation.z])
+ const y = endRotation[1] + deltaX * rotationSpeed;
+ setEndRotation([0, y, 0]);
}
}
});
@@ -142,43 +138,34 @@ const VehicleUI = () => {
setIsRotating(null);
if (selectedEventSphere?.userData.modelUuid) {
- const updatedVehicle = vehicles.find(
- (vehicle) => vehicle.modelUuid === selectedEventSphere.userData.modelUuid
- );
+ const updatedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid);
if (updatedVehicle) {
- updateVehicle(selectedEventSphere.userData.modelUuid, {
+ const event = updateEvent(selectedProduct.productId, selectedEventSphere.userData.modelUuid, {
point: {
...updatedVehicle.point,
action: {
...updatedVehicle.point?.action,
pickUpPoint: {
- position: {
- x: startPosition[0],
- y: startPosition[1],
- z: startPosition[2],
- },
- rotation: {
- x: startRotation[0],
- y: startRotation[1],
- z: startRotation[2],
- },
+ position: { x: startPosition[0], y: startPosition[1], z: startPosition[2], },
+ rotation: { x: 0, y: startRotation[1], z: 0, },
},
unLoadPoint: {
- position: {
- x: endPosition[0],
- y: endPosition[1],
- z: endPosition[2],
- },
- rotation: {
- x: endRotation[0],
- y: endRotation[1],
- z: endRotation[2],
- },
+ position: { x: endPosition[0], y: endPosition[1], z: endPosition[2], },
+ rotation: { x: 0, y: endRotation[1], z: 0, },
},
},
},
- });
+ })
+
+ if (event) {
+ updateBackend(
+ selectedProduct.productName,
+ selectedProduct.productId,
+ organization,
+ event
+ );
+ }
}
}
};
@@ -208,6 +195,7 @@ const VehicleUI = () => {
object={startScene}
ref={startMarker}
position={startPosition}
+ rotation={startRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "start", "start");
@@ -224,6 +212,7 @@ const VehicleUI = () => {
object={endScene}
ref={endMarker}
position={endPosition}
+ rotation={endRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "end", "end");
diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx
index 2f0b235..aeadc4b 100644
--- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx
+++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx
@@ -11,7 +11,7 @@ interface VehicleAnimatorProps {
reset: () => void;
currentPhase: string;
agvUuid: number;
- agvDetail: any;
+ agvDetail: VehicleStatus;
}
function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail, reset }: VehicleAnimatorProps) {
@@ -25,14 +25,14 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
const completedRef = useRef(false);
const isPausedRef = useRef(false);
const pauseTimeRef = useRef(null);
+ const [progress, setProgress] = useState(0);
const [restRotation, setRestingRotation] = useState(true);
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
- const [progress, setProgress] = useState(0);
const { scene } = useThree();
let startTime: number;
let fixedInterval: number;
let coveredDistance = progressRef.current;
- let objectRotation = (agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 }) as { x: number; y: number; z: number };
+ let objectRotation = (agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 }) as { x: number; y: number; z: number } | undefined;
useEffect(() => {
@@ -66,11 +66,12 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
setReset(false);
setRestingRotation(true);
decrementVehicleLoad(agvDetail.modelUuid, 0);
+ isPausedRef.current = false;
+ pauseTimeRef.current = 0;
const object = scene.getObjectByProperty('uuid', agvUuid);
- console.log('currentPhase: ', currentPhase);
if (object) {
object.position.set(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]);
- object.rotation.set(objectRotation.x, objectRotation.y, objectRotation.z);
+ object.rotation.set(agvDetail.rotation[0], agvDetail.rotation[1], agvDetail.rotation[2]);
}
}
}, [isReset, isPlaying])
@@ -132,7 +133,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
}
if (progressRef.current >= totalDistance) {
- if (restRotation) {
+ if (restRotation && objectRotation) {
const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(objectRotation.x, objectRotation.y, objectRotation.z));
object.quaternion.slerp(targetQuaternion, delta * 2);
const angleDiff = object.quaternion.angleTo(targetQuaternion);
diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx
index 6a81d3a..412a4ba 100644
--- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx
+++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx
@@ -30,7 +30,8 @@ function VehicleInstance({ agvDetail }: any) {
);
function vehicleStatus(modelId: string, status: string) {
- // console.log(`${modelId} , ${status});
+ // console.log(`${modelId} , ${status}`);
+
}
// Function to reset everything
@@ -44,7 +45,7 @@ function VehicleInstance({ agvDetail }: any) {
const increment = () => {
if (isIncrememtable.current) {
- incrementVehicleLoad(agvDetail.modelUuid, 2);
+ incrementVehicleLoad(agvDetail.modelUuid, 10);
isIncrememtable.current = false;
}
}
@@ -69,6 +70,7 @@ function VehicleInstance({ agvDetail }: any) {
increment();
}, 5000);
+
if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) {
const toDrop = computePath(
agvDetail.point.action.pickUpPoint.position,
diff --git a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx
index 91111cf..fcc840d 100644
--- a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx
+++ b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx
@@ -9,10 +9,8 @@ function VehicleInstances() {
return (
<>
- {vehicles.map((val: any, i: any) =>
-
-
-
+ {vehicles.map((val: VehicleStatus) =>
+
)}
>
diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx
index 7badec5..3fbc8ef 100644
--- a/app/src/modules/simulation/vehicle/vehicles.tsx
+++ b/app/src/modules/simulation/vehicle/vehicles.tsx
@@ -1,220 +1,48 @@
-import React, { useEffect, useState } from "react";
+import React, { useEffect } from "react";
import VehicleInstances from "./instances/vehicleInstances";
import { useVehicleStore } from "../../../store/simulation/useVehicleStore";
-import { useFloorItems } from "../../../store/store";
-import { useSelectedEventData, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore";
+import { useSelectedEventData, useSelectedEventSphere, useSelectedProduct } from "../../../store/simulation/useSimulationStore";
import VehicleUI from "../ui/vehicle/vehicleUI";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
-function Vehicles() {
+import { useProductStore } from "../../../store/simulation/useProductStore";
- const { vehicles, addVehicle } = useVehicleStore();
+function Vehicles() {
+ const { products, getProductById } = useProductStore();
+ const { selectedProduct } = useSelectedProduct();
+ const { vehicles, addVehicle, clearvehicles } = useVehicleStore();
const { selectedEventSphere } = useSelectedEventSphere();
const { selectedEventData } = useSelectedEventData();
- const { floorItems } = useFloorItems();
const { isPlaying } = usePlayButtonStore();
- const [vehicleStatusSample, setVehicleStatusSample] = useState<
- VehicleEventSchema[]
- >([
- {
- modelUuid: "9356f710-4727-4b50-bdb2-9c1e747ecc74",
- modelName: "AGV",
- position: [97.9252965204558, 0, 37.96138815638661],
- rotation: [0, 0, 0],
- state: "idle",
- type: "vehicle",
- speed: 2.5,
- point: {
- uuid: "point-789",
- position: [0, 1, 0],
- rotation: [0, 0, 0],
- action: {
- actionUuid: "action-456",
- actionName: "Deliver to Zone A",
- actionType: "travel",
- unLoadDuration: 10,
- loadCapacity: 2,
- steeringAngle:0,
- pickUpPoint: { position: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, rotation: { x: 0, y: 0, z: 0 } },
- unLoadPoint: { position: { x: 105.71483985219794, y: 0, z: 28.66321267938962 }, rotation: { x: 0, y: 0, z: 0 } },
- triggers: [
- {
- triggerUuid: "trig-001",
- triggerName: "Start Travel",
- triggerType: "onComplete",
- delay: 0,
- triggeredAsset: {
- triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
- triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
- triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
- }
- },
- {
- triggerUuid: "trig-002",
- triggerName: "Complete Travel",
- triggerType: "onComplete",
- delay: 2,
- triggeredAsset: null
- }
- ]
- }
- }
- },
- {
- modelUuid: "b06960bb-3d2e-41f7-a646-335f389c68b4",
- modelName: "AGV",
- position: [89.61609306554463, 0, 33.634136622267356],
- rotation: [0, 0, 0],
- state: "idle",
- type: "vehicle",
- speed: 2.5,
- point: {
- uuid: "point-789",
- position: [0, 1, 0],
- rotation: [0, 0, 0],
- action: {
- actionUuid: "action-456",
- actionName: "Deliver to Zone A",
- actionType: "travel",
- unLoadDuration: 10,
- loadCapacity: 2,
- steeringAngle:0,
- pickUpPoint: null,
- unLoadPoint: null,
- triggers: [
- {
- triggerUuid: "trig-001",
- triggerName: "Start Travel",
- triggerType: "onStart",
- delay: 0,
- triggeredAsset: {
- triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
- triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
- triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
- }
- },
- {
- triggerUuid: "trig-002",
- triggerName: "Complete Travel",
- triggerType: "onComplete",
- delay: 2,
- triggeredAsset: null
- }
- ]
- }
- }
- },
- // {
- // modelUuid: "cd7d0584-0684-42b4-b051-9e882c1914aa",
- // modelName: "AGV",
- // position: [105.90938758014703, 0, 31.584209911095215],
- // rotation: [0, 0, 0],
- // state: "idle",
- // type: "vehicle",
- // speed: 2.5,
- // point: {
- // uuid: "point-789",
- // position: [0, 1, 0],
- // rotation: [0, 0, 0],
- // action: {
- // actionUuid: "action-456",
- // actionName: "Deliver to Zone A",
- // actionType: "travel",
- // unLoadDuration: 10,
- // loadCapacity: 2,
- // steeringAngle:0,
- // pickUpPoint: null,
- // unLoadPoint: null,
- // triggers: [
- // {
- // triggerUuid: "trig-001",
- // triggerName: "Start Travel",
- // triggerType: "onStart",
- // delay: 0,
- // triggeredAsset: {
- // triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
- // triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
- // triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
- // }
- // },
- // {
- // triggerUuid: "trig-002",
- // triggerName: "Complete Travel",
- // triggerType: "onComplete",
- // delay: 2,
- // triggeredAsset: null
- // }
- // ]
- // }
- // }
- // },
- // {
- // modelUuid: "e729a4f1-11d2-4778-8d6a-468f1b4f6b79",
- // modelName: "forklift",
- // position: [98.85729337188162, 0, 38.36616546567653],
- // rotation: [0, 0, 0],
- // state: "idle",
- // type: "vehicle",
- // speed: 2.5,
- // point: {
- // uuid: "point-789",
- // position: [0, 1, 0],
- // rotation: [0, 0, 0],
- // action: {
- // actionUuid: "action-456",
- // actionName: "Deliver to Zone A",
- // actionType: "travel",
- // unLoadDuration: 15,
- // loadCapacity: 5,
- // steeringAngle:0,
- // pickUpPoint: null,
- // unLoadPoint: null,
- // triggers: [
- // {
- // triggerUuid: "trig-001",
- // triggerName: "Start Travel",
- // triggerType: "onStart",
- // delay: 0,
- // triggeredAsset: {
- // triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
- // triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
- // triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
- // }
- // },
- // {
- // triggerUuid: "trig-002",
- // triggerName: "Complete Travel",
- // triggerType: "onComplete",
- // delay: 2,
- // triggeredAsset: null
- // }
- // ]
- // }
- // }
- // }
- ]);
useEffect(() => {
- // console.log('vehicles: ', vehicles);
+ if (selectedProduct.productId) {
+ const product = getProductById(selectedProduct.productId);
+ if (product) {
+ clearvehicles();
+ product.eventDatas.forEach(events => {
+ if (events.type === 'vehicle') {
+ addVehicle(selectedProduct.productId, events);
+ }
+ });
+ }
+ }
+ }, [selectedProduct, products]);
+
+ useEffect(() => {
+ //
}, [vehicles])
- useEffect(() => {
- addVehicle("123", vehicleStatusSample[0]);
- addVehicle('123', vehicleStatusSample[1]);
- // addVehicle('123', vehicleStatusSample[2]);
- }, []);
-
-
return (
<>
+
+
{selectedEventSphere && selectedEventData?.data.type === "vehicle" && !isPlaying &&
< VehicleUI />
}
+
>
);
}
-export default Vehicles;
-
-
-
+export default Vehicles;
\ No newline at end of file
diff --git a/app/src/modules/visualization/RealTimeVisulization.tsx b/app/src/modules/visualization/RealTimeVisulization.tsx
index caf37a1..a8f44b4 100644
--- a/app/src/modules/visualization/RealTimeVisulization.tsx
+++ b/app/src/modules/visualization/RealTimeVisulization.tsx
@@ -66,8 +66,7 @@ const RealTimeVisulization: React.FC = () => {
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
const { setRightSelect } = useRightSelected();
- const { editWidgetOptions, setEditWidgetOptions } =
- useEditWidgetOptionsStore();
+ const { editWidgetOptions, setEditWidgetOptions } = useEditWidgetOptionsStore();
const { rightClickSelected, setRightClickSelected } = useRightClickSelected();
const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false);
const { setFloatingWidget } = useFloatingWidget();
@@ -76,8 +75,6 @@ const RealTimeVisulization: React.FC = () => {
const { setSelectedChartId } = useWidgetStore();
const [waitingPanels, setWaitingPanels] = useState(null);
- console.log("waitingPanels: ", waitingPanels);
-
OuterClick({
contextClassName: [
"chart-container",
@@ -145,109 +142,109 @@ const RealTimeVisulization: React.FC = () => {
});
}, [selectedZone]);
- const handleDrop = async (event: React.DragEvent) => {
- event.preventDefault();
- try {
- const email = localStorage.getItem("email") ?? "";
- const organization = email?.split("@")[1]?.split(".")[0];
+ // const handleDrop = async (event: React.DragEvent) => {
+ // event.preventDefault();
+ // try {
+ // const email = localStorage.getItem("email") ?? "";
+ // const organization = email?.split("@")[1]?.split(".")[0];
- const data = event.dataTransfer.getData("text/plain");
- if (widgetSubOption === "3D") return;
- if (!data || selectedZone.zoneName === "") return;
+ // const data = event.dataTransfer.getData("text/plain");
+ // if (widgetSubOption === "3D") return;
+ // if (!data || selectedZone.zoneName === "") return;
- const droppedData = JSON.parse(data);
- const canvasElement = document.getElementById("real-time-vis-canvas");
- if (!canvasElement) throw new Error("Canvas element not found");
+ // const droppedData = JSON.parse(data);
+ // const canvasElement = document.getElementById("real-time-vis-canvas");
+ // if (!canvasElement) throw new Error("Canvas element not found");
- const rect = canvasElement.getBoundingClientRect();
- const relativeX = event.clientX - rect.left;
- const relativeY = event.clientY - rect.top;
+ // const rect = canvasElement.getBoundingClientRect();
+ // const relativeX = event.clientX - rect.left;
+ // const relativeY = event.clientY - rect.top;
- // Widget dimensions
- const widgetWidth = droppedData.width ?? 125;
- const widgetHeight = droppedData.height ?? 100;
+ // // Widget dimensions
+ // const widgetWidth = droppedData.width ?? 125;
+ // const widgetHeight = droppedData.height ?? 100;
- // Center the widget at cursor
- const centerOffsetX = widgetWidth / 2;
- const centerOffsetY = widgetHeight / 2;
+ // // Center the widget at cursor
+ // const centerOffsetX = widgetWidth / 2;
+ // const centerOffsetY = widgetHeight / 2;
- const adjustedX = relativeX - centerOffsetX;
- const adjustedY = relativeY - centerOffsetY;
+ // const adjustedX = relativeX - centerOffsetX;
+ // const adjustedY = relativeY - centerOffsetY;
- const finalPosition = determinePosition(rect, adjustedX, adjustedY);
- const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
+ // const finalPosition = determinePosition(rect, adjustedX, adjustedY);
+ // const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
- let finalY = 0;
- let finalX = 0;
+ // let finalY = 0;
+ // let finalX = 0;
- if (activeProp1 === "top") {
- finalY = adjustedY;
- } else {
- finalY = rect.height - (adjustedY + widgetHeight);
- }
+ // if (activeProp1 === "top") {
+ // finalY = adjustedY;
+ // } else {
+ // finalY = rect.height - (adjustedY + widgetHeight);
+ // }
- if (activeProp2 === "left") {
- finalX = adjustedX;
- } else {
- finalX = rect.width - (adjustedX + widgetWidth);
- }
+ // if (activeProp2 === "left") {
+ // finalX = adjustedX;
+ // } else {
+ // finalX = rect.width - (adjustedX + widgetWidth);
+ // }
- // Clamp to boundaries
- finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
- finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
+ // // Clamp to boundaries
+ // finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
+ // finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
- const boundedPosition = {
- ...finalPosition,
- [activeProp1]: finalY,
- [activeProp2]: finalX,
- [activeProp1 === "top" ? "bottom" : "top"]: "auto",
- [activeProp2 === "left" ? "right" : "left"]: "auto",
- };
+ // const boundedPosition = {
+ // ...finalPosition,
+ // [activeProp1]: finalY,
+ // [activeProp2]: finalX,
+ // [activeProp1 === "top" ? "bottom" : "top"]: "auto",
+ // [activeProp2 === "left" ? "right" : "left"]: "auto",
+ // };
- const newObject = {
- ...droppedData,
- id: generateUniqueId(),
- position: boundedPosition,
- };
+ // const newObject = {
+ // ...droppedData,
+ // id: generateUniqueId(),
+ // position: boundedPosition,
+ // };
- const existingZone =
- useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
- if (!existingZone) {
- useDroppedObjectsStore
- .getState()
- .setZone(selectedZone.zoneName, selectedZone.zoneId);
- }
+ // const existingZone =
+ // useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
+ // if (!existingZone) {
+ // useDroppedObjectsStore
+ // .getState()
+ // .setZone(selectedZone.zoneName, selectedZone.zoneId);
+ // }
- const addFloatingWidget = {
- organization,
- widget: newObject,
- zoneId: selectedZone.zoneId,
- };
+ // const addFloatingWidget = {
+ // organization,
+ // widget: newObject,
+ // zoneId: selectedZone.zoneId,
+ // };
- if (visualizationSocket) {
- visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
- }
+ // if (visualizationSocket) {
+ // visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
+ // }
- useDroppedObjectsStore
- .getState()
- .addObject(selectedZone.zoneName, newObject);
+ // useDroppedObjectsStore
+ // .getState()
+ // .addObject(selectedZone.zoneName, newObject);
- const droppedObjectsStore = useDroppedObjectsStore.getState();
- const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
+ // const droppedObjectsStore = useDroppedObjectsStore.getState();
+ // const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
- if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
- console.log(
- `Objects for Zone ${selectedZone.zoneId}:`,
- currentZone.objects
- );
- setFloatingWidget(currentZone.objects);
- } else {
- console.warn("Zone not found or zoneId mismatch");
- }
- } catch (error) {
- console.error("Error in handleDrop:", error);
- }
- };
+ // if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
+ // console.log(
+ // `Objects for Zone ${selectedZone.zoneId}:`,
+ // currentZone.objects
+ // );
+ // setFloatingWidget(currentZone.objects);
+ // } else {
+ // console.warn("Zone not found or zoneId mismatch");
+ // }
+ // } catch (error) {
+ // console.error("Error in handleDrop:", error);
+ // }
+ // };
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
@@ -304,16 +301,7 @@ const RealTimeVisulization: React.FC = () => {
}
`}
-
+
{openConfirmationPopup && (
@@ -324,20 +312,6 @@ const RealTimeVisulization: React.FC = () => {
/>
)}
-
handleDrop(event)}
- onDragOver={(event) => event.preventDefault()}
- >
-
-
{activeModule === "visualization" && selectedZone.zoneName !== "" && (
)}
diff --git a/app/src/modules/visualization/functions/handleUiDrop.ts b/app/src/modules/visualization/functions/handleUiDrop.ts
new file mode 100644
index 0000000..a70742e
--- /dev/null
+++ b/app/src/modules/visualization/functions/handleUiDrop.ts
@@ -0,0 +1,122 @@
+import { generateUniqueId } from "../../../functions/generateUniqueId";
+import { useDroppedObjectsStore } from "../../../store/visualization/useDroppedObjectsStore";
+import { determinePosition } from "./determinePosition";
+import { getActiveProperties } from "./getActiveProperties";
+
+interface HandleDropProps {
+ widgetSubOption: any;
+ visualizationSocket: any;
+ selectedZone: any;
+ setFloatingWidget: (value: any) => void;
+ event: React.DragEvent
+}
+
+export const createHandleDrop = ({
+ widgetSubOption,
+ visualizationSocket,
+ selectedZone,
+ setFloatingWidget,
+ event,
+}: HandleDropProps) => {
+ event.preventDefault();
+ try {
+ const email = localStorage.getItem("email") ?? "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+
+ const data = event.dataTransfer.getData("text/plain");
+ if (widgetSubOption === "3D") return;
+ if (!data || selectedZone.zoneName === "") return;
+
+ const droppedData = JSON.parse(data);
+ const canvasElement = document.getElementById("real-time-vis-canvas");
+ if (!canvasElement) throw new Error("Canvas element not found");
+
+ const rect = canvasElement.getBoundingClientRect();
+ const relativeX = event.clientX - rect.left;
+ const relativeY = event.clientY - rect.top;
+
+ // Widget dimensions
+ const widgetWidth = droppedData.width ?? 125;
+ const widgetHeight = droppedData.height ?? 100;
+
+ // Center the widget at cursor
+ const centerOffsetX = widgetWidth / 2;
+ const centerOffsetY = widgetHeight / 2;
+
+ const adjustedX = relativeX - centerOffsetX;
+ const adjustedY = relativeY - centerOffsetY;
+
+ const finalPosition = determinePosition(rect, adjustedX, adjustedY);
+ const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
+
+ let finalY = 0;
+ let finalX = 0;
+
+ if (activeProp1 === "top") {
+ finalY = adjustedY;
+ } else {
+ finalY = rect.height - (adjustedY + widgetHeight);
+ }
+
+ if (activeProp2 === "left") {
+ finalX = adjustedX;
+ } else {
+ finalX = rect.width - (adjustedX + widgetWidth);
+ }
+
+ // Clamp to boundaries
+ finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
+ finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
+
+ const boundedPosition = {
+ ...finalPosition,
+ [activeProp1]: finalY,
+ [activeProp2]: finalX,
+ [activeProp1 === "top" ? "bottom" : "top"]: "auto",
+ [activeProp2 === "left" ? "right" : "left"]: "auto",
+ };
+
+ const newObject = {
+ ...droppedData,
+ id: generateUniqueId(),
+ position: boundedPosition,
+ };
+
+ const existingZone =
+ useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
+ if (!existingZone) {
+ useDroppedObjectsStore
+ .getState()
+ .setZone(selectedZone.zoneName, selectedZone.zoneId);
+ }
+
+ const addFloatingWidget = {
+ organization,
+ widget: newObject,
+ zoneId: selectedZone.zoneId,
+ };
+
+ if (visualizationSocket) {
+ visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
+ }
+
+ useDroppedObjectsStore
+ .getState()
+ .addObject(selectedZone.zoneName, newObject);
+
+ const droppedObjectsStore = useDroppedObjectsStore.getState();
+ const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
+
+ if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
+ console.log(
+ `Objects for Zone ${selectedZone.zoneId}:`,
+ currentZone.objects
+ );
+ setFloatingWidget(currentZone.objects);
+ } else {
+ console.warn("Zone not found or zoneId mismatch");
+ }
+ } catch (error) {
+ console.error("Error in handleDrop:", error);
+ }
+};
diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx
index bdefb8b..17a6315 100644
--- a/app/src/pages/Project.tsx
+++ b/app/src/pages/Project.tsx
@@ -13,6 +13,7 @@ import {
useWallItems,
useZones,
useLoadingProgress,
+ useWidgetSubOption,
} from "../store/store";
import { useNavigate } from "react-router-dom";
import { usePlayButtonStore } from "../store/usePlayButtonStore";
@@ -22,12 +23,19 @@ import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
import { useSelectedUserStore } from "../store/useCollabStore";
import FollowPerson from "../components/templates/FollowPerson";
-import ProductionCapacity from "../components/ui/analysis/ProductionCapacity";
-import ThroughputSummary from "../components/ui/analysis/ThroughputSummary";
-import ROISummary from "../components/ui/analysis/ROISummary";
+import Scene from "../modules/scene/scene";
+import { createHandleDrop } from "../modules/visualization/functions/handleUiDrop";
+import { useSelectedZoneStore } from "../store/visualization/useZoneStore";
+import { useFloatingWidget } from "../store/visualization/useDroppedObjectsStore";
+import { useLogger } from "../components/ui/log/LoggerContext";
+import RenderOverlay from "../components/templates/Overlay";
+import LogList from "../components/ui/log/LogList";
+import Footer from "../components/footer/Footer";
const Project: React.FC = () => {
let navigate = useNavigate();
+ const echo = useLogger();
+
const { activeModule, setActiveModule } = useModuleStore();
const { loadingProgress } = useLoadingProgress();
const { setUserName } = useUserName();
@@ -50,42 +58,79 @@ const Project: React.FC = () => {
setOrganization(Organization);
setUserName(name);
}
+ echo.info("Log in success full");
} else {
navigate("/");
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
- const { isPlaying } = usePlayButtonStore();
+ // global store
const { toggleThreeD } = useThreeDStore();
+
+ // simulation store
+ const { isPlaying } = usePlayButtonStore();
+
+ // collaboration store
const { selectedUser } = useSelectedUserStore();
+ const { isLogListVisible } = useLogger();
+
+ // real-time visualization store
+ const { widgetSubOption } = useWidgetSubOption();
+ const { visualizationSocket } = useSocketStore();
+ const { selectedZone } = useSelectedZoneStore();
+ const { setFloatingWidget } = useFloatingWidget();
return (
- {/*
*/}
-
- {loadingProgress > 0 &&
}
- {!isPlaying && (
+ {!selectedUser && (
<>
- {toggleThreeD &&
}
-
-
+
+ {loadingProgress > 0 &&
}
+ {!isPlaying && (
+ <>
+ {toggleThreeD &&
}
+
+
+ >
+ )}
+
+ {activeModule === "market" &&
}
+ {activeModule !== "market" &&
}
+ {isPlaying && activeModule === "simulation" &&
}
>
)}
- {/*
-
- */}
- {activeModule === "market" &&
}
-
- {activeModule !== "market" &&
}
- {isPlaying && activeModule === "simulation" &&
}
- {/* {
} */}
+
+ createHandleDrop({
+ widgetSubOption,
+ visualizationSocket,
+ selectedZone,
+ setFloatingWidget,
+ event,
+ })
+ }
+ onDragOver={(event) => event.preventDefault()}
+ >
+
+
{selectedUser &&
}
+ {isLogListVisible && (
+
+
+
+ )}
+
);
};
diff --git a/app/src/services/simulation/renameProductApi.ts b/app/src/services/simulation/renameProductApi.ts
new file mode 100644
index 0000000..afa493c
--- /dev/null
+++ b/app/src/services/simulation/renameProductApi.ts
@@ -0,0 +1,26 @@
+let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
+
+export const renameProductApi = async (body: { productName: string, productId: string, organization: string }) => {
+ try {
+ const response = await fetch(`${url_Backend_dwinzo}/api/v2/productRename`, {
+ method: "PATCH",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to rename product");
+ }
+
+ 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");
+ }
+ }
+};
diff --git a/app/src/store/simulation/useArmBotStore.ts b/app/src/store/simulation/useArmBotStore.ts
index 642762f..3c54596 100644
--- a/app/src/store/simulation/useArmBotStore.ts
+++ b/app/src/store/simulation/useArmBotStore.ts
@@ -10,6 +10,7 @@ interface ArmBotStore {
modelUuid: string,
updates: Partial>
) => void;
+ clearArmBots: () => void;
addCurrentAction: (modelUuid: string, actionUuid: string) => void;
removeCurrentAction: (modelUuid: string) => void;
@@ -39,14 +40,17 @@ export const useArmBotStore = create()(
addArmBot: (productId, event) => {
set((state) => {
- state.armBots.push({
- ...event,
- productId,
- isActive: false,
- idleTime: 0,
- activeTime: 0,
- state: 'idle',
- });
+ const exists = state.armBots.some(a => a.modelUuid === event.modelUuid);
+ if (!exists) {
+ state.armBots.push({
+ ...event,
+ productId,
+ isActive: false,
+ idleTime: 0,
+ activeTime: 0,
+ state: 'idle',
+ });
+ }
});
},
@@ -65,6 +69,12 @@ export const useArmBotStore = create()(
});
},
+ clearArmBots: () => {
+ set((state) => {
+ state.armBots = [];
+ });
+ },
+
addCurrentAction: (modelUuid, actionUuid) => {
set((state) => {
const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
diff --git a/app/src/store/simulation/useConveyorStore.ts b/app/src/store/simulation/useConveyorStore.ts
index 15dbf34..862ce79 100644
--- a/app/src/store/simulation/useConveyorStore.ts
+++ b/app/src/store/simulation/useConveyorStore.ts
@@ -10,6 +10,7 @@ interface ConveyorStore {
modelUuid: string,
updates: Partial>
) => void;
+ clearConveyors: () => void;
setConveyorActive: (modelUuid: string, isActive: boolean) => void;
setConveyorState: (modelUuid: string, newState: ConveyorStatus['state']) => void;
@@ -30,14 +31,17 @@ export const useConveyorStore = create()(
addConveyor: (productId, event) => {
set((state) => {
- state.conveyors.push({
- ...event,
- productId,
- isActive: false,
- idleTime: 0,
- activeTime: 0,
- state: 'idle',
- });
+ const exists = state.conveyors.some(c => c.modelUuid === event.modelUuid);
+ if (!exists) {
+ state.conveyors.push({
+ ...event,
+ productId,
+ isActive: false,
+ idleTime: 0,
+ activeTime: 0,
+ state: 'idle',
+ });
+ }
});
},
@@ -56,6 +60,12 @@ export const useConveyorStore = create()(
});
},
+ clearConveyors: () => {
+ set((state) => {
+ state.conveyors = [];
+ });
+ },
+
setConveyorActive: (modelUuid, isActive) => {
set((state) => {
const conveyor = state.conveyors.find(c => c.modelUuid === modelUuid);
diff --git a/app/src/store/simulation/useEventsStore.ts b/app/src/store/simulation/useEventsStore.ts
index 2d92fc2..5580eb1 100644
--- a/app/src/store/simulation/useEventsStore.ts
+++ b/app/src/store/simulation/useEventsStore.ts
@@ -7,7 +7,7 @@ type EventsStore = {
// Event-level actions
addEvent: (event: EventsSchema) => void;
removeEvent: (modelUuid: string) => void;
- updateEvent: (modelUuid: string, updates: Partial) => void;
+ updateEvent: (modelUuid: string, updates: Partial) => EventsSchema | undefined;
// Point-level actions
addPoint: (modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema) => void;
@@ -49,7 +49,9 @@ export const useEventsStore = create()(
// Event-level actions
addEvent: (event) => {
set((state) => {
- state.events.push(event);
+ if (!state.events.some(e => 'modelUuid' in e && e.modelUuid === event.modelUuid)) {
+ state.events.push(event);
+ }
});
},
@@ -60,12 +62,15 @@ export const useEventsStore = create()(
},
updateEvent: (modelUuid, updates) => {
+ let updatedEvent: EventsSchema | undefined;
set((state) => {
const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
if (event) {
Object.assign(event, updates);
+ updatedEvent = JSON.parse(JSON.stringify(event));
}
});
+ return updatedEvent;
},
// Point-level actions
@@ -73,9 +78,14 @@ export const useEventsStore = create()(
set((state) => {
const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
if (event && 'points' in event) {
- (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema);
+ const existingPoint = (event as ConveyorEventSchema).points.find(p => p.uuid === point.uuid);
+ if (!existingPoint) {
+ (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema);
+ }
} else if (event && 'point' in event) {
- (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any;
+ if (!(event as any).point || (event as any).point.uuid !== point.uuid) {
+ (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any;
+ }
}
});
},
@@ -110,14 +120,15 @@ export const useEventsStore = create()(
const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
if (event && 'points' in event) {
const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid);
- if (point) {
+ if (point && (!point.action || point.action.actionUuid !== action.actionUuid)) {
point.action = action as any;
}
} else if (event && 'point' in event && (event as any).point.uuid === pointUuid) {
- if ('action' in (event as any).point) {
- (event as any).point.action = action;
- } else if ('actions' in (event as any).point) {
- (event as any).point.actions.push(action);
+ const point = (event as any).point;
+ if ('action' in point && (!point.action || point.action.actionUuid !== action.actionUuid)) {
+ point.action = action;
+ } else if ('actions' in point && !point.actions.some((a: any) => a.actionUuid === action.actionUuid)) {
+ point.actions.push(action);
}
}
});
@@ -180,18 +191,22 @@ export const useEventsStore = create()(
if ('points' in event) {
for (const point of (event as ConveyorEventSchema).points) {
if (point.action && point.action.actionUuid === actionUuid) {
- point.action.triggers.push(trigger);
+ if (!point.action.triggers.some(t => t.triggerUuid === trigger.triggerUuid)) {
+ point.action.triggers.push(trigger);
+ }
return;
}
}
} else if ('point' in event) {
- const point = (event as any).point;
+ const point: MachinePointSchema | VehiclePointSchema = (event as any).point;
if ('action' in point && point.action.actionUuid === actionUuid) {
- point.action.triggers.push(trigger);
+ if (!point.action.triggers.some(t => t.triggerUuid === trigger.triggerUuid)) {
+ point.action.triggers.push(trigger);
+ }
return;
} else if ('actions' in point) {
- const action = point.actions.find((a: any) => a.actionUuid === actionUuid);
- if (action) {
+ const action = (point as RoboticArmPointSchema).actions.find((a) => a.actionUuid === actionUuid);
+ if (action && !action.triggers.some(t => t.triggerUuid === trigger.triggerUuid)) {
action.triggers.push(trigger);
return;
}
diff --git a/app/src/store/simulation/useMachineStore.ts b/app/src/store/simulation/useMachineStore.ts
index cc927f7..9a564ac 100644
--- a/app/src/store/simulation/useMachineStore.ts
+++ b/app/src/store/simulation/useMachineStore.ts
@@ -4,23 +4,23 @@ import { immer } from 'zustand/middleware/immer';
interface MachineStore {
machines: MachineStatus[];
- // Actions
addMachine: (productId: string, machine: MachineEventSchema) => void;
removeMachine: (modelUuid: string) => void;
updateMachine: (
modelUuid: string,
updates: Partial>
) => void;
+ clearMachines: () => void;
+
+ addCurrentAction: (modelUuid: string, actionUuid: string) => void;
+ removeCurrentAction: (modelUuid: string) => void;
- // Status updates
setMachineActive: (modelUuid: string, isActive: boolean) => void;
setMachineState: (modelUuid: string, newState: MachineStatus['state']) => void;
- // Time tracking
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
- // Helpers
getMachineById: (modelUuid: string) => MachineStatus | undefined;
getMachinesByProduct: (productId: string) => MachineStatus[];
getMachinesBystate: (state: string) => MachineStatus[];
@@ -32,17 +32,19 @@ export const useMachineStore = create()(
immer((set, get) => ({
machines: [],
- // Actions
addMachine: (productId, machine) => {
set((state) => {
- state.machines.push({
- ...machine,
- productId,
- isActive: false,
- idleTime: 0,
- activeTime: 0,
- state: 'idle',
- });
+ const exists = state.machines.some(m => m.modelUuid === machine.modelUuid);
+ if (!exists) {
+ state.machines.push({
+ ...machine,
+ productId,
+ isActive: false,
+ idleTime: 0,
+ activeTime: 0,
+ state: 'idle',
+ });
+ }
});
},
@@ -61,7 +63,36 @@ export const useMachineStore = create()(
});
},
- // Status updates
+ clearMachines: () => {
+ set((state) => {
+ state.machines = [];
+ });
+ },
+
+ addCurrentAction: (modelUuid) => {
+ set((state) => {
+ const armBot = state.machines.find(a => a.modelUuid === modelUuid);
+ if (armBot) {
+ const action = armBot.point.action;
+ if (action) {
+ armBot.currentAction = {
+ actionUuid: action.actionUuid,
+ actionName: action.actionName,
+ };
+ }
+ }
+ });
+ },
+
+ removeCurrentAction: (modelUuid) => {
+ set((state) => {
+ const armBot = state.machines.find(a => a.modelUuid === modelUuid);
+ if (armBot) {
+ armBot.currentAction = undefined;
+ }
+ });
+ },
+
setMachineActive: (modelUuid, isActive) => {
set((state) => {
const machine = state.machines.find(m => m.modelUuid === modelUuid);
@@ -80,7 +111,6 @@ export const useMachineStore = create()(
});
},
- // Time tracking
incrementActiveTime: (modelUuid, incrementBy) => {
set((state) => {
const machine = state.machines.find(m => m.modelUuid === modelUuid);
@@ -99,7 +129,6 @@ export const useMachineStore = create()(
});
},
- // Helpers
getMachineById: (modelUuid) => {
return get().machines.find(m => m.modelUuid === modelUuid);
},
diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts
index 4f2b546..8b83cdd 100644
--- a/app/src/store/simulation/useProductStore.ts
+++ b/app/src/store/simulation/useProductStore.ts
@@ -33,27 +33,30 @@ type ProductsStore = {
pointUuid: string,
action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']
) => EventsSchema | undefined;
- removeAction: (actionUuid: string) => EventsSchema | undefined;
+ removeAction: (productId: string, actionUuid: string) => EventsSchema | undefined;
updateAction: (
+ productId: string,
actionUuid: string,
updates: Partial
) => EventsSchema | undefined;
// Trigger-level actions
addTrigger: (
+ productId: string,
actionUuid: string,
trigger: TriggerSchema
- ) => void;
- removeTrigger: (triggerUuid: string) => void;
+ ) => EventsSchema | undefined;
+ removeTrigger: (productId: string, triggerUuid: string) => EventsSchema | undefined;
updateTrigger: (
+ productId: string,
triggerUuid: string,
updates: Partial
- ) => void;
+ ) => EventsSchema | undefined;
// Renaming functions
renameProduct: (productId: string, newName: string) => void;
- renameAction: (actionUuid: string, newName: string) => EventsSchema | undefined;
- renameTrigger: (triggerUuid: string, newName: string) => void;
+ renameAction: (productId: string, actionUuid: string, newName: string) => EventsSchema | undefined;
+ renameTrigger: (productId: string, triggerUuid: string, newName: string) => void;
// Helper functions
getProductById: (productId: string) => { productName: string; productId: string; eventDatas: EventsSchema[] } | undefined;
@@ -71,12 +74,15 @@ export const useProductStore = create()(
// Product-level actions
addProduct: (productName, productId) => {
set((state) => {
- const newProduct = {
- productName,
- productId: productId,
- eventDatas: []
- };
- state.products.push(newProduct);
+ const existingProduct = state.products.find(p => p.productId === productId);
+ if (!existingProduct) {
+ const newProduct = {
+ productName,
+ productId: productId,
+ eventDatas: []
+ };
+ state.products.push(newProduct);
+ }
});
},
@@ -106,7 +112,10 @@ export const useProductStore = create()(
set((state) => {
const product = state.products.find(p => p.productId === productId);
if (product) {
- product.eventDatas.push(event);
+ const existingEvent = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === event.modelUuid);
+ if (!existingEvent) {
+ product.eventDatas.push(event);
+ }
}
});
},
@@ -120,7 +129,7 @@ export const useProductStore = create()(
});
},
- deleteEvent: (modelUuid: string) => {
+ deleteEvent: (modelUuid) => {
set((state) => {
for (const product of state.products) {
product.eventDatas = product.eventDatas.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid);
@@ -150,9 +159,15 @@ export const useProductStore = create()(
if (product) {
const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
if (event && 'points' in event) {
- (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema);
+ const existingPoint = (event as ConveyorEventSchema).points.find(p => p.uuid === point.uuid);
+ if (!existingPoint) {
+ (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema);
+ }
} else if (event && 'point' in event) {
- (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any;
+ const existingPoint = (event as any).point?.uuid === point.uuid;
+ if (!existingPoint) {
+ (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any;
+ }
}
}
});
@@ -198,17 +213,22 @@ export const useProductStore = create()(
const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
if (event && 'points' in event) {
const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid);
- if (point) {
+ if (point && (!point.action || point.action.actionUuid !== action.actionUuid)) {
point.action = action as any;
updatedEvent = JSON.parse(JSON.stringify(event));
}
} else if (event && 'point' in event && (event as any).point.uuid === pointUuid) {
if ('action' in (event as any).point) {
- (event as any).point.action = action;
- updatedEvent = JSON.parse(JSON.stringify(event));
+ if (!(event as any).point.action || (event as any).point.action.actionUuid !== action.actionUuid) {
+ (event as any).point.action = action;
+ updatedEvent = JSON.parse(JSON.stringify(event));
+ }
} else if ('actions' in (event as any).point) {
- (event as any).point.actions.push(action);
- updatedEvent = JSON.parse(JSON.stringify(event));
+ const existingAction = (event as any).point.actions.find((a: any) => a.actionUuid === action.actionUuid);
+ if (!existingAction) {
+ (event as any).point.actions.push(action);
+ updatedEvent = JSON.parse(JSON.stringify(event));
+ }
}
}
}
@@ -216,10 +236,11 @@ export const useProductStore = create()(
return updatedEvent;
},
- removeAction: (actionUuid: string) => {
+ removeAction: (productId, actionUuid) => {
let updatedEvent: EventsSchema | undefined;
set((state) => {
- for (const product of state.products) {
+ const product = state.products.find(p => p.productId === productId);
+ if (product) {
for (const event of product.eventDatas) {
if ('points' in event) {
// Handle ConveyorEventSchema
@@ -248,10 +269,11 @@ export const useProductStore = create()(
return updatedEvent;
},
- updateAction: (actionUuid, updates) => {
+ updateAction: (productId, actionUuid, updates) => {
let updatedEvent: EventsSchema | undefined;
set((state) => {
- for (const product of state.products) {
+ const product = state.products.find(p => p.productId === productId);
+ if (product) {
for (const event of product.eventDatas) {
if ('points' in event) {
for (const point of (event as ConveyorEventSchema).points) {
@@ -283,26 +305,40 @@ export const useProductStore = create()(
},
// Trigger-level actions
- addTrigger: (actionUuid, trigger) => {
+ addTrigger: (productId, actionUuid, trigger) => {
+ let updatedEvent: EventsSchema | undefined;
set((state) => {
- for (const product of state.products) {
+ const product = state.products.find(p => p.productId === productId);
+ if (product) {
for (const event of product.eventDatas) {
if ('points' in event) {
for (const point of (event as ConveyorEventSchema).points) {
if (point.action && point.action.actionUuid === actionUuid) {
- point.action.triggers.push(trigger);
+ const existingTrigger = point.action.triggers.find(t => t.triggerUuid === trigger.triggerUuid);
+ if (!existingTrigger) {
+ point.action.triggers.push(trigger);
+ updatedEvent = JSON.parse(JSON.stringify(event));
+ }
return;
}
}
} else if ('point' in event) {
const point = (event as any).point;
if ('action' in point && point.action.actionUuid === actionUuid) {
- point.action.triggers.push(trigger);
+ const existingTrigger = point.action.triggers.find((t: any) => t.triggerUuid === trigger.triggerUuid);
+ if (!existingTrigger) {
+ point.action.triggers.push(trigger);
+ updatedEvent = JSON.parse(JSON.stringify(event));
+ }
return;
} else if ('actions' in point) {
const action = point.actions.find((a: any) => a.actionUuid === actionUuid);
if (action) {
- action.triggers.push(trigger);
+ const existingTrigger = action.triggers.find((t: any) => t.triggerUuid === trigger.triggerUuid);
+ if (!existingTrigger) {
+ action.triggers.push(trigger);
+ updatedEvent = JSON.parse(JSON.stringify(event));
+ }
return;
}
}
@@ -310,26 +346,41 @@ export const useProductStore = create()(
}
}
});
+ return updatedEvent;
},
- removeTrigger: (triggerUuid) => {
+ removeTrigger: (productId, triggerUuid) => {
+ let updatedEvent: EventsSchema | undefined;
set((state) => {
- for (const product of state.products) {
+ const product = state.products.find(p => p.productId === productId);
+ if (product) {
for (const event of product.eventDatas) {
if ('points' in event) {
for (const point of (event as ConveyorEventSchema).points) {
if (point.action && 'triggers' in point.action) {
- point.action.triggers = point.action.triggers.filter(t => t.triggerUuid !== triggerUuid);
+ const Trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid);
+ if (Trigger) {
+ point.action.triggers = point.action.triggers.filter(t => t.triggerUuid !== triggerUuid);
+ updatedEvent = JSON.parse(JSON.stringify(event));
+ }
}
}
} else if ('point' in event) {
const point = (event as any).point;
if ('action' in point && 'triggers' in point.action) {
- point.action.triggers = point.action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid);
+ const Trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
+ if (Trigger) {
+ point.action.triggers = point.action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid);
+ updatedEvent = JSON.parse(JSON.stringify(event));
+ }
} else if ('actions' in point) {
for (const action of point.actions) {
if ('triggers' in action) {
- action.triggers = action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid);
+ const Trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
+ if (Trigger) {
+ action.triggers = action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid);
+ updatedEvent = JSON.parse(JSON.stringify(event));
+ }
}
}
}
@@ -337,11 +388,14 @@ export const useProductStore = create()(
}
}
});
+ return updatedEvent;
},
- updateTrigger: (triggerUuid, updates) => {
+ updateTrigger: (productId, triggerUuid, updates) => {
+ let updatedEvent: EventsSchema | undefined;
set((state) => {
- for (const product of state.products) {
+ const product = state.products.find(p => p.productId === productId);
+ if (product) {
for (const event of product.eventDatas) {
if ('points' in event) {
for (const point of (event as ConveyorEventSchema).points) {
@@ -349,6 +403,7 @@ export const useProductStore = create()(
const trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid);
if (trigger) {
Object.assign(trigger, updates);
+ updatedEvent = JSON.parse(JSON.stringify(event));
return;
}
}
@@ -359,6 +414,7 @@ export const useProductStore = create()(
const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
if (trigger) {
Object.assign(trigger, updates);
+ updatedEvent = JSON.parse(JSON.stringify(event));
return;
}
} else if ('actions' in point) {
@@ -367,6 +423,7 @@ export const useProductStore = create()(
const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid);
if (trigger) {
Object.assign(trigger, updates);
+ updatedEvent = JSON.parse(JSON.stringify(event));
return;
}
}
@@ -376,6 +433,7 @@ export const useProductStore = create()(
}
}
});
+ return updatedEvent;
},
// Renaming functions
@@ -388,10 +446,11 @@ export const useProductStore = create()(
});
},
- renameAction: (actionUuid, newName) => {
+ renameAction: (productId, actionUuid, newName) => {
let updatedEvent: EventsSchema | undefined;
set((state) => {
- for (const product of state.products) {
+ const product = state.products.find(p => p.productId === productId);
+ if (product) {
for (const event of product.eventDatas) {
if ('points' in event) {
for (const point of (event as ConveyorEventSchema).points) {
@@ -422,9 +481,10 @@ export const useProductStore = create()(
return updatedEvent;
},
- renameTrigger: (triggerUuid, newName) => {
+ renameTrigger: (productId, triggerUuid, newName) => {
set((state) => {
- for (const product of state.products) {
+ const product = state.products.find(p => p.productId === productId);
+ if (product) {
for (const event of product.eventDatas) {
if ('points' in event) {
for (const point of (event as ConveyorEventSchema).points) {
diff --git a/app/src/store/simulation/useSimulationStore.ts b/app/src/store/simulation/useSimulationStore.ts
index 5085688..6e4321f 100644
--- a/app/src/store/simulation/useSimulationStore.ts
+++ b/app/src/store/simulation/useSimulationStore.ts
@@ -114,4 +114,36 @@ export const useSelectedAction = create()(
});
},
}))
+);
+
+interface IsDraggingState {
+ isDragging: "start" | "end" | null;
+ setIsDragging: (state: "start" | "end" | null) => void;
+}
+
+export const useIsDragging = create()(
+ immer((set) => ({
+ isDragging: null,
+ setIsDragging: (state) => {
+ set((s) => {
+ s.isDragging = state;
+ });
+ },
+ }))
+);
+
+interface IsRotatingState {
+ isRotating: "start" | "end" | null;
+ setIsRotating: (state: "start" | "end" | null) => void;
+}
+
+export const useIsRotating = create()(
+ immer((set) => ({
+ isRotating: null,
+ setIsRotating: (state) => {
+ set((s) => {
+ s.isRotating = state;
+ });
+ },
+ }))
);
\ No newline at end of file
diff --git a/app/src/store/simulation/useStorageUnitStore.ts b/app/src/store/simulation/useStorageUnitStore.ts
index d729708..aec2f12 100644
--- a/app/src/store/simulation/useStorageUnitStore.ts
+++ b/app/src/store/simulation/useStorageUnitStore.ts
@@ -4,26 +4,22 @@ import { immer } from 'zustand/middleware/immer';
interface StorageUnitStore {
storageUnits: StorageUnitStatus[];
- // Actions
addStorageUnit: (productId: string, storageUnit: StorageEventSchema) => void;
removeStorageUnit: (modelUuid: string) => void;
updateStorageUnit: (
modelUuid: string,
updates: Partial>
) => void;
+ clearStorageUnits: () => void;
- // Status updates
setStorageUnitActive: (modelUuid: string, isActive: boolean) => void;
setStorageUnitState: (modelUuid: string, newState: StorageUnitStatus['state']) => void;
- // Load updates
updateStorageUnitLoad: (modelUuid: string, incrementBy: number) => void;
- // Time tracking
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
- // Helpers
getStorageUnitById: (modelUuid: string) => StorageUnitStatus | undefined;
getStorageUnitsByProduct: (productId: string) => StorageUnitStatus[];
getStorageUnitsBystate: (state: string) => StorageUnitStatus[];
@@ -37,18 +33,20 @@ export const useStorageUnitStore = create()(
immer((set, get) => ({
storageUnits: [],
- // Actions
addStorageUnit: (productId, storageUnit) => {
set((state) => {
- state.storageUnits.push({
- ...storageUnit,
- productId,
- isActive: false,
- idleTime: 0,
- activeTime: 0,
- currentLoad: 0,
- state: 'idle',
- });
+ const exists = state.storageUnits.some(s => s.modelUuid === storageUnit.modelUuid);
+ if (!exists) {
+ state.storageUnits.push({
+ ...storageUnit,
+ productId,
+ isActive: false,
+ idleTime: 0,
+ activeTime: 0,
+ currentLoad: 0,
+ state: 'idle',
+ });
+ }
});
},
@@ -67,7 +65,12 @@ export const useStorageUnitStore = create()(
});
},
- // Status updates
+ clearStorageUnits: () => {
+ set(() => ({
+ storageUnits: [],
+ }));
+ },
+
setStorageUnitActive: (modelUuid, isActive) => {
set((state) => {
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
@@ -86,7 +89,6 @@ export const useStorageUnitStore = create()(
});
},
- // Load updates
updateStorageUnitLoad: (modelUuid, incrementBy) => {
set((state) => {
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
@@ -96,7 +98,6 @@ export const useStorageUnitStore = create()(
});
},
- // Time tracking
incrementActiveTime: (modelUuid, incrementBy) => {
set((state) => {
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
@@ -115,7 +116,6 @@ export const useStorageUnitStore = create()(
});
},
- // Helpers
getStorageUnitById: (modelUuid) => {
return get().storageUnits.find(s => s.modelUuid === modelUuid);
},
diff --git a/app/src/store/simulation/useVehicleStore.ts b/app/src/store/simulation/useVehicleStore.ts
index a5ea3be..ecef809 100644
--- a/app/src/store/simulation/useVehicleStore.ts
+++ b/app/src/store/simulation/useVehicleStore.ts
@@ -20,6 +20,7 @@ interface VehiclesStore {
modelUuid: string,
updates: Partial>
) => void;
+ clearvehicles: () => void;
setVehicleActive: (modelUuid: string, isActive: boolean) => void;
updateSteeringAngle: (modelUuid: string, steeringAngle: number) => void;
@@ -41,15 +42,18 @@ export const useVehicleStore = create()(
addVehicle: (productId, event) => {
set((state) => {
- state.vehicles.push({
- ...event,
- productId,
- isActive: false,
- idleTime: 0,
- activeTime: 0,
- currentLoad: 0,
- distanceTraveled: 0,
- });
+ const exists = state.vehicles.some(v => v.modelUuid === event.modelUuid);
+ if (!exists) {
+ state.vehicles.push({
+ ...event,
+ productId,
+ isActive: false,
+ idleTime: 0,
+ activeTime: 0,
+ currentLoad: 0,
+ distanceTraveled: 0,
+ });
+ }
});
},
@@ -68,6 +72,12 @@ export const useVehicleStore = create()(
});
},
+ clearvehicles: () => {
+ set((state) => {
+ state.vehicles = [];
+ });
+ },
+
setVehicleActive: (modelUuid, isActive) => {
set((state) => {
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
diff --git a/app/src/store/visualization/useDroppedObjectsStore.ts b/app/src/store/visualization/useDroppedObjectsStore.ts
index 5c4527b..bbe4cde 100644
--- a/app/src/store/visualization/useDroppedObjectsStore.ts
+++ b/app/src/store/visualization/useDroppedObjectsStore.ts
@@ -1,5 +1,4 @@
import { create } from "zustand";
-import { addingFloatingWidgets } from "../../services/visulization/zone/addFloatingWidgets";
import { useSocketStore } from "../store";
import useChartStore from "./useChartStore";
diff --git a/app/src/styles/abstracts/variables.scss b/app/src/styles/abstracts/variables.scss
index f4a6495..29976dc 100644
--- a/app/src/styles/abstracts/variables.scss
+++ b/app/src/styles/abstracts/variables.scss
@@ -22,7 +22,11 @@ $text-button-color-dark: #f3f3fd;
// background colors
// ---------- light mode ----------
$background-color: linear-gradient(-45deg, #fcfdfd71 0%, #fcfdfd79 100%);
-$background-color-solid-gradient: linear-gradient(-45deg, #fcfdfd 0%, #fcfdfd 100%);
+$background-color-solid-gradient: linear-gradient(
+ -45deg,
+ #fcfdfd 0%,
+ #fcfdfd 100%
+);
$background-color-solid: #fcfdfd;
$background-color-secondary: #fcfdfd4d;
$background-color-accent: #6f42c1;
@@ -45,7 +49,11 @@ $background-radial-gray-gradient: radial-gradient(
// ---------- dark mode ----------
$background-color-dark: linear-gradient(-45deg, #333333b3 0%, #2d2437b3 100%);
-$background-color-solid-gradient-dark: linear-gradient(-45deg, #333333 0%, #2d2437 100%);
+$background-color-solid-gradient-dark: linear-gradient(
+ -45deg,
+ #333333 0%,
+ #2d2437 100%
+);
$background-color-solid-dark: #19191d;
$background-color-secondary-dark: #19191d99;
$background-color-accent-dark: #6f42c1;
@@ -104,6 +112,21 @@ $color3: #b186ff;
$color4: #8752e8;
$color5: #c7a8ff;
+// log indication colors
+// ------------ text -------------
+$log-default-text-color: #6f42c1;
+$log-info-text-color: #488ef6;
+$log-warn-text-color: #f3a50c;
+$log-error-text-color: #f65648;
+$log-success-text-color: #43c06d;
+
+// ------------ background -------------
+$log-default-backgroung-color: #6e42c133;
+$log-info-background-color: #488ef633;
+$log-warn-background-color: #f3a50c33;
+$log-error-background-color: #f6564833;
+$log-success-background-color: #43c06d33;
+
// old variables
$accent-color: #6f42c1;
$accent-color-dark: #c4abf1;
diff --git a/app/src/styles/base/base.scss b/app/src/styles/base/base.scss
index bf53164..4555ed9 100644
--- a/app/src/styles/base/base.scss
+++ b/app/src/styles/base/base.scss
@@ -37,11 +37,9 @@
// old colors
--accent-color: #{$accent-color};
- --highlight-accent-color: #{$highlight-accent-color};
--accent-gradient-color: #{$acent-gradient};
--faint-gradient-color: #{$faint-gradient};
--background-color-gray: #{$background-color-gray};
- --border-color: #{$border-color};
--shadow-main-light: #{$shadow-color};
--box-shadow-light: 0px 2px 4px var(--shadow-main-light);
--box-shadow-medium: 0px 4px 8px var(--shadow-main-light);
@@ -75,7 +73,7 @@
--background-radial-gray-gradient: #{$background-radial-gray-gradient-dark};
// border colors
- --border-color: #{$border-color};
+ --border-color: #{$border-color-dark};
--input-border-color: #{$input-border-color-dark};
--border-color-accent: #{$border-color-accent-dark};
@@ -89,11 +87,9 @@
// old colors
--accent-color: #{$accent-color-dark};
- --highlight-accent-color: #{$highlight-accent-color-dark};
--accent-gradient-color: #{$acent-gradient-dark};
--faint-gradient-color: #{$faint-gradient-dark};
--background-color-gray: #{$background-color-gray-dark};
- --border-color: #{$border-color-dark};
--shadow-main-dark: #{$shadow-color};
--box-shadow-light: 0px 2px 4px var(--shadow-main-dark);
--box-shadow-medium: 0px 4px 8px var(--shadow-main-dark);
diff --git a/app/src/styles/base/global.scss b/app/src/styles/base/global.scss
index d90e6fb..f13344e 100644
--- a/app/src/styles/base/global.scss
+++ b/app/src/styles/base/global.scss
@@ -1,11 +1,30 @@
@use "../abstracts/variables" as *;
@use "../abstracts/mixins" as *;
-section, .section{
- padding: 4px;
- outline: 1px solid var(--border-color);
- outline-offset: -1px;
- border-radius: #{$border-radius-large};
- background: var(--background-color);
- margin: 4px 0;
+section,
+.section {
+ padding: 4px;
+ outline: 1px solid var(--border-color);
+ outline-offset: -1px;
+ border-radius: #{$border-radius-large};
+ background: var(--background-color);
+ margin: 4px 0;
+}
+
+.scene-container {
+ width: calc(100% - (320px + 270px + 90px));
+ height: calc(100% - (250px));
+ position: absolute;
+ top: 50%;
+ left: calc(270px + 45px);
+ overflow: hidden;
+ z-index: 1;
+ transform: translate(0, -50%);
+ transition: all 0.2s;
+ box-shadow: $box-shadow-medium;
+ background: var(--background-color-solid);
+ canvas {
+ outline: none;
+ border: none;
+ }
}
diff --git a/app/src/styles/base/reset.scss b/app/src/styles/base/reset.scss
index ab77f9a..82d286e 100644
--- a/app/src/styles/base/reset.scss
+++ b/app/src/styles/base/reset.scss
@@ -12,10 +12,3 @@ input[type="password"]::-webkit-clear-button, /* For Chrome/Safari clear button
input[type="password"]::-webkit-inner-spin-button { /* Just in case */
display: none;
}
-
-button{
- border: none;
- outline: none;
- background: none;
- cursor: pointer;
-}
\ No newline at end of file
diff --git a/app/src/styles/components/analysis/ROISummary.scss b/app/src/styles/components/analysis/ROISummary.scss
deleted file mode 100644
index 96b4a5d..0000000
--- a/app/src/styles/components/analysis/ROISummary.scss
+++ /dev/null
@@ -1,311 +0,0 @@
-.roiSummary-container {
- .roiSummary-wrapper {
- background-color: var(--background-color);
-
- .product-info {
- display: flex;
- }
-
- .playBack {
- display: flex;
- background-color: var(--background-color);
- border-radius: 12px;
- padding: 6px;
-
- .info {
- span {
- font-size: var(--font-size-xlarge);
-
- &:first-child {
- color: #31C756;
- }
-
- &:last-child {
- color: var(--text-color);
- }
- }
- }
- }
-
- .roi-details {
- display: flex;
- align-items: center;
- gap: 12px;
-
- .progress-wrapper {
- width: 250px;
- display: flex;
- flex-direction: column;
- gap: 6px;
-
- .content {
- display: flex;
- flex-direction: column;
- gap: 3px;
- align-items: center;
-
- .key {
- font-size: var(--font-size-xlarge);
- color: var(--accent-color);
- }
- }
- }
-
- .roi-progress {
- width: 100%;
- }
-
- .metrics {
- display: flex;
- flex-direction: column;
- gap: 6px;
-
- .metric-item {
- width: 100%;
- border-radius: 6px;
- border: 1px solid #00FF56;
- background: #436D51;
- display: flex;
- flex-direction: column;
- padding: 4px 6px;
-
- &:last-child {
- align-items: center;
- }
-
- .metric-label {
- font-size: 10px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .metric-value {
- text-align: center;
- line-height: 20px;
- }
- }
-
- .metric-wrapper {
- display: flex;
- gap: 6px;
-
- .metric-item {
- background-color: var(--background-color);
- border: 1px solid var(--Grays-Gray-6, #F2F2F7);
- }
- }
- }
- }
-
- .cost-breakdown {
- background-color: var(--background-color);
- border: 1px solid var(--text-disabled);
- border-radius: 8px;
- padding: 16px;
-
- .breakdown-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- margin-bottom: 16px;
-
- .section-wrapper {
- display: flex;
- gap: 4px;
- align-items: center;
- }
-
- .section-number {
- font-size: 20px;
- color: #00aaff;
- }
-
- .section-title {
- font-size: var(--font-size-regular);
- color: var(--text-color);
- }
-
- .expand-icon {
- font-size: 16px;
- color: var(--text-color);
- cursor: pointer;
- transform: rotate(90deg);
- transition: transform 0.2s linear;
- }
-
- .expand-icon.open {
- transform: rotate(0deg);
-
- }
- }
-
- .breakdown-table {
- width: 100%;
- border-collapse: collapse;
- border-radius: 8px;
-
- th,
- td {
- padding: 8px;
- text-align: left;
- border-top: 1px solid var(--text-disabled);
- border-bottom: 1px solid var(--text-disabled);
- }
-
- th:first-child,
- td:first-child {
- border-left: 1px solid var(--text-disabled);
- }
-
- th:last-child,
- td:last-child {
- border-right: 1px solid var(--text-disabled);
- }
-
- th {
- background-color: var(--background-color);
- color: #333;
- }
-
- .total-row,
- .net-profit-row {
- font-weight: bold;
- color: #333;
- }
- }
- }
-
- .tips-section {
- background-color: var(--background-color);
- border-radius: 8px;
- display: flex;
- flex-direction: column;
- gap: 6px;
- padding: 12px;
-
- .tip-header {
- display: flex;
- align-items: center;
-
- .tip-title {
- color: var(--text-color);
- font-weight: 600;
- }
- }
-
- .tip-description {
- span {
- font-size: var(--font-size-xlarge);
- color: #34C759;
-
- &:first-child {
- color: #34C759;
- }
-
- &:nth-child(2) {
- color: #488EF6;
- }
- }
- }
- }
-
- .get-tips-button {
- border: none;
- border-radius: 5px;
- cursor: pointer;
- font-size: 14px;
- margin-top: 8px;
- display: inline-block;
- display: flex;
- justify-content: flex-end;
- background: none;
-
- .btn {
- background-color: var(--accent-color);
- color: var(--background-color);
- padding: 4px 6px;
- border-radius: 5px;
- display: inline-block;
- font-size: 14px;
- text-align: center;
- }
- }
- }
-
- .semi-circle-wrapper {
- width: 100%;
- height: 125px;
- overflow-y: hidden;
- position: relative;
- .semi-circle {
- width: 100%;
- height: 250px;
- border-radius: 50%;
- position: relative;
- transition: background 0.5s ease;
- }
- .progress-cover {
- position: absolute;
- width: 75%;
- height: 75%;
- top: 12.5%;
- left: 12.5%;
- background: #000000cc;
- border-radius: 50%;
- }
- }
-
-
-
- .label-wrapper {
- .label {
- font-size: var(--font-size-xxxlarge);
- }
-
- position: absolute;
- bottom: 0%;
- left: 50%;
- transform: translate(-50%, 0%);
- font-weight: bold;
- font-size: 1.2rem;
- color: #333;
- display: flex;
- justify-content: center;
- align-items: center;
- flex-direction: column;
- }
-}
-
-// Breakdown Table Open/Close Logic
-
-.breakdown-table-wrapper {
- &.closed {
- max-height: 0;
- padding: 0;
- }
-
- &.open {
- max-height: 500px;
- }
-
-
-
-
-
- .breakdown-table {
- width: 100%;
- border-collapse: collapse;
-
- th,
- td {
- padding: 10px;
- border: 1px solid #ddd;
- text-align: left;
- }
-
-
-
- }
-}
\ No newline at end of file
diff --git a/app/src/styles/components/analysis/analysis.scss b/app/src/styles/components/analysis/analysis.scss
deleted file mode 100644
index 030a79f..0000000
--- a/app/src/styles/components/analysis/analysis.scss
+++ /dev/null
@@ -1,279 +0,0 @@
-.analysis {
- position: fixed;
- top: 0;
- left: 0;
- display: flex;
- justify-content: space-between;
- align-items: start;
- width: 100%;
- height: 100vh;
- // pointer-events: none;
- z-index: 10000;
-
- .analysis-wrapper {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
-}
-
-.analysis-card {
- min-width: 333px;
- background: var(--background-color);
- border-radius: 20px;
-
- padding: 8px;
-
- .analysis-card-wrapper {
- width: 100%;
- 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;
- width: 100%;
- height: 4px;
- border-radius: 13px;
- overflow: hidden;
- background: #FBEBD7;
-
- .bar-fill {
- position: absolute;
- height: 100%;
- top: 0;
- left: 0;
- background: #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: 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: #F3C64D;
- }
-
- &:nth-child(2) {
- background: #67B3F4;
- }
-
- &:nth-child(3) {
- background: #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: #F3C64D;
- }
- }
-
- &:nth-child(2) {
- .indicator {
-
- background: #67B3F4;
- }
- }
-
- &:nth-child(3) {
- .indicator {
-
- background: #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/components/button.scss b/app/src/styles/components/button.scss
index e69de29..dad9120 100644
--- a/app/src/styles/components/button.scss
+++ b/app/src/styles/components/button.scss
@@ -0,0 +1,24 @@
+@use "../abstracts/variables" as *;
+@use "../abstracts/mixins" as *;
+
+.labeled-button-container {
+ @include flex-space-between;
+ padding: 6px 12px;
+
+ button {
+ padding: 2px 32px;
+ border: none;
+ border-radius: #{$border-radius-large};
+ color: var(--text-button-color);
+ background: var(--background-color-button);
+ transition: all 0.2s;
+ cursor: pointer;
+ }
+}
+
+button {
+ border: none;
+ outline: none;
+ background: none;
+ cursor: pointer;
+}
diff --git a/app/src/styles/components/footer/footer.scss b/app/src/styles/components/footer/footer.scss
new file mode 100644
index 0000000..b2d85d0
--- /dev/null
+++ b/app/src/styles/components/footer/footer.scss
@@ -0,0 +1,71 @@
+@use "../../abstracts/variables" as *;
+@use "../../abstracts/mixins" as *;
+
+.footer-wrapper {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ z-index: 1;
+ display: flex;
+ justify-content: space-between;
+ padding: 2px 12px;
+
+ .selection-wrapper {
+ display: flex;
+ gap: 6px;
+
+ .selector-wrapper {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+ background: var(--background-color);
+ padding: 3px 6px;
+ border-radius: 12px;
+ color: var(--text-color);
+
+ .selector {
+ color: var(--text-color);
+ }
+ }
+ }
+
+ .logs-wrapper {
+ display: flex;
+ gap: 6px;
+
+ .logs-detail,
+ .version {
+ border-radius: 12px;
+ background: var(--background-color);
+ padding: 3px 6px;
+ color: var(--text-color);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .logs-detail {
+ padding: 2px 12px;
+ cursor: pointer;
+ .log-icon {
+ @include flex-center;
+ }
+ .log-message {
+ max-width: 40vw;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+
+ .version {
+ font-size: var(--font-size-tiny);
+ display: flex;
+ gap: 6px;
+ .icon{
+ @include flex-center;
+ }
+ }
+ }
+}
diff --git a/app/src/styles/components/input.scss b/app/src/styles/components/input.scss
index e3b9585..d4c6544 100644
--- a/app/src/styles/components/input.scss
+++ b/app/src/styles/components/input.scss
@@ -639,21 +639,6 @@ input[type="number"] {
}
}
-.labeled-button-container {
- @include flex-space-between;
- padding: 6px 12px;
-
- button {
- padding: 2px 32px;
- border: none;
- border-radius: #{$border-radius-large};
- color: var(--text-button-color);
- background: var(--background-color-button);
- transition: all 0.2s;
- cursor: pointer;
- }
-}
-
.value-field-container {
margin-bottom: 6px;
padding: 6px 12px;
diff --git a/app/src/styles/components/layouts.scss b/app/src/styles/components/layouts.scss
deleted file mode 100644
index e69de29..0000000
diff --git a/app/src/styles/components/logs/logs.scss b/app/src/styles/components/logs/logs.scss
new file mode 100644
index 0000000..f07708b
--- /dev/null
+++ b/app/src/styles/components/logs/logs.scss
@@ -0,0 +1,105 @@
+@use "../../abstracts/variables" as *;
+@use "../../abstracts/mixins" as *;
+
+.log-list-container {
+ width: 100vw;
+ height: 100vh;
+ background: var(--background-color-secondary);
+
+ .log-list-wrapper {
+ height: 50%;
+ min-width: 50%;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 5;
+ background: var(--background-color);
+ padding: 14px 12px;
+ border-radius: 15px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ backdrop-filter: blur(50px);
+ outline: 1px solid var(--border-color);
+
+ .log-header {
+ display: flex;
+ justify-content: space-between;
+
+ .log-header-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .close {
+ @include flex-center;
+ height: 28px;
+ width: 28px;
+ cursor: pointer;
+ svg {
+ scale: 1.6;
+ }
+ }
+ }
+
+ .log-nav-wrapper {
+ display: flex;
+ gap: 6px;
+
+ .log-nav {
+ padding: 8px 16px;
+ border-radius: 19px;
+ }
+
+ .log-nav.active {
+ background-color: var(--background-color-accent);
+ color: var(--text-button-color);
+ }
+ }
+
+ .log-entry-wrapper {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ background: var(--background-color);
+ padding: 18px 10px;
+ border-radius: 16px;
+ outline: 1px solid var(--border-color);
+ outline-offset: -1px;
+
+ .log-entry {
+ padding: 4px;
+ border-radius: 4px;
+ font-size: var(--font-size-small);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+
+ .log-icon {
+ @include flex-center;
+ }
+ .log-entry-message-container {
+ @include flex-space-between;
+ gap: 12px;
+ width: 100%;
+ .message-time {
+ font-size: var(--font-size-tiny);
+ font-weight: 300;
+ opacity: 0.8;
+ text-wrap: nowrap;
+ }
+ .log-entry-message{
+ width: 100%;
+ }
+ }
+
+ &:nth-child(odd) {
+ background: var(--background-color);
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/styles/components/simulation/analysis.scss b/app/src/styles/components/simulation/analysis.scss
new file mode 100644
index 0000000..9aed5c8
--- /dev/null
+++ b/app/src/styles/components/simulation/analysis.scss
@@ -0,0 +1,557 @@
+@use "../../abstracts/variables" as *;
+@use "../../abstracts/mixins" as *;
+
+.analysis {
+ position: fixed;
+ top: 0;
+ left: 0;
+ display: flex;
+ justify-content: space-between;
+ align-items: start;
+ width: 100%;
+ height: 100vh;
+ pointer-events: none;
+ padding: 10px;
+ z-index: 2;
+
+ .analysis-wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+ .analysis-card {
+ min-width: 333px;
+ border-radius: 20px;
+ padding: 8px;
+ pointer-events: all;
+
+ .analysis-card-wrapper {
+ width: 100%;
+ background: var(--background-color);
+ border-radius: 14px;
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ backdrop-filter: blur(10px);
+ outline: 1px solid var(--border-color);
+ outline-offset: -1px;
+
+ .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: 100%;
+ height: 4px;
+ border-radius: 13px;
+ overflow: hidden;
+ background: #fbebd7;
+
+ .bar-fill {
+ position: absolute;
+ height: 100%;
+ top: 0;
+ left: 0;
+ background: #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-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: 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: #f3c64d;
+ }
+
+ &:nth-child(2) {
+ background: #67b3f4;
+ }
+
+ &:nth-child(3) {
+ background: #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: #f3c64d;
+ }
+ }
+
+ &:nth-child(2) {
+ .indicator {
+ background: #67b3f4;
+ }
+ }
+
+ &:nth-child(3) {
+ .indicator {
+ background: #7981f5;
+ }
+ }
+
+ label {
+ font-size: var(--font-size-small);
+ position: relative;
+ }
+
+ .indicator {
+ display: inline-block;
+ width: 5px;
+ height: 5px;
+ border-radius: 50%;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ .roiSummary-wrapper {
+ max-width: 470px;
+ background-color: var(--background-color);
+
+ .product-info {
+ display: flex;
+ }
+
+ .playBack {
+ display: flex;
+ background-color: var(--background-color);
+ border-radius: 12px;
+ padding: 6px;
+
+ .info {
+ span {
+ font-size: var(--font-size-xlarge);
+
+ &:first-child {
+ color: #31c756;
+ }
+
+ &:last-child {
+ color: var(--text-color);
+ }
+ }
+ }
+ }
+
+ .roi-details {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+
+ .progress-wrapper {
+ width: 250px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+
+ .content {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ align-items: center;
+
+ .key {
+ font-size: var(--font-size-xlarge);
+ color: var(--accent-color);
+ }
+ }
+ }
+
+ .roi-progress {
+ width: 100%;
+ }
+
+ .metrics {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+
+ .metric-item {
+ width: 100%;
+ border-radius: #{$border-radius-xxx};
+ border: 1px solid #00ff56;
+ background: #17eb5e42;
+ display: flex;
+ flex-direction: column;
+ padding: 4px 8px;
+
+ &:last-child {
+ align-items: center;
+ }
+
+ .metric-label {
+ opacity: 0.8;
+ font-size: 10px;
+ font-weight: 300;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .metric-value {
+ text-align: center;
+ line-height: 20px;
+ }
+ }
+
+ .metric-wrapper {
+ display: flex;
+ gap: 6px;
+
+ .metric-item {
+ border-radius: #{$border-radius-large};
+ background-color: var(--background-color);
+ border: 1px solid var(--border-color);
+ }
+ }
+ }
+ }
+
+ .cost-breakdown {
+ background-color: var(--background-color);
+ border: 1px solid var(--border-color);
+ border-radius: #{$border-radius-extra-large};
+ padding: 16px;
+
+ .breakdown-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+
+ .section-wrapper {
+ display: flex;
+ gap: 4px;
+ align-items: center;
+ }
+
+ .section-number {
+ color: #00aaff;
+ }
+
+ .section-title {
+ font-size: var(--font-size-regular);
+ color: var(--text-color);
+ }
+
+ .expand-icon {
+ font-size: 16px;
+ color: var(--text-color);
+ cursor: pointer;
+ transform: rotate(90deg);
+ transition: transform 0.2s linear;
+ }
+
+ .expand-icon.open {
+ transform: rotate(0deg);
+ }
+ }
+
+ .breakdown-table {
+ width: 100%;
+ border-collapse: collapse;
+ border-radius: 8px;
+ overflow: hidden;
+ outline: 1px solid var(--border-color);
+ outline-offset: -1px;
+ margin-top: 12px;
+
+ th,
+ td {
+ color: var(--text-color);
+ padding: 8px;
+ text-align: left;
+ border: 1px solid var(--border-color);
+ }
+ th {
+ background-color: var(--background-color);
+ }
+ }
+ }
+
+ .tips-section {
+ background-color: var(--background-color);
+ border-radius: #{$border-radius-large};
+ outline: 1px solid var(--border-color);
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding: 12px;
+
+ .tip-header {
+ display: flex;
+ align-items: center;
+
+ .tip-title {
+ color: var(--text-color);
+ font-weight: 600;
+ }
+ }
+
+ .tip-description {
+ span {
+ font-size: var(--font-size-xlarge);
+ color: #34c759;
+
+ &:first-child {
+ color: #34c759;
+ }
+
+ &:nth-child(2) {
+ color: #488ef6;
+ }
+ }
+ }
+ }
+
+ .get-tips-button {
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ font-size: 14px;
+ margin-top: 8px;
+ display: inline-block;
+ display: flex;
+ justify-content: flex-end;
+ background: none;
+
+ .btn {
+ color: var(--text-button-color);
+ background: var(--background-color-button);
+ padding: 4px 12px;
+ border-radius: #{$border-radius-large};
+ display: inline-block;
+ text-align: center;
+ }
+ }
+ }
+
+ .semi-circle-wrapper {
+ width: 100%;
+ height: 125px;
+ overflow-y: hidden;
+ position: relative;
+ .semi-circle {
+ width: 100%;
+ height: 250px;
+ border-radius: 50%;
+ position: relative;
+ }
+ .progress-cover {
+ position: absolute;
+ width: 75%;
+ height: 75%;
+ top: 12.5%;
+ left: 12.5%;
+ border-radius: 50%;
+ }
+ }
+
+ .label-wrapper {
+ .label {
+ font-size: var(--font-size-xxxlarge);
+ }
+
+ position: absolute;
+ bottom: 0%;
+ left: 50%;
+ transform: translate(-50%, 0%);
+ font-weight: bold;
+ font-size: 1.2rem;
+ color: #333;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ }
+}
+
+.breakdown-table-wrapper {
+ &.closed {
+ max-height: 0;
+ padding: 0;
+ }
+
+ &.open {
+ max-height: 500px;
+ }
+
+ .breakdown-table {
+ width: 100%;
+ border-collapse: collapse;
+
+ th,
+ td {
+ padding: 10px;
+ border: 1px solid #ddd;
+ text-align: left;
+ }
+ }
+}
+
+// Breakdown Table Open/Close Logic
+
diff --git a/app/src/styles/components/simulation/simulation.scss b/app/src/styles/components/simulation/simulation.scss
index 0fc3df8..88f85dc 100644
--- a/app/src/styles/components/simulation/simulation.scss
+++ b/app/src/styles/components/simulation/simulation.scss
@@ -32,10 +32,17 @@
}
.controls-container {
- @include flex-center;
+ @include flex-space-between;
gap: 12px;
justify-content: space-between;
-
+ .header{
+ @include flex-center;
+ gap: 6px;
+ padding: 0 8px;
+ svg{
+ scale: 1.3;
+ }
+ }
.production-details,
.controls-wrapper {
@include flex-center;
@@ -72,7 +79,7 @@
.expand-icon-container {
@include flex-center;
- padding: 6px 8px;
+ padding: 0 8px;
cursor: pointer;
}
@@ -307,6 +314,22 @@
font-size: var(--font-size-tiny);
}
+ .timmer {
+ width: auto;
+ position: absolute;
+ bottom: 0;
+ font-size: var(--font-size-tiny);
+ }
+
+ .start-displayer {
+ left: 8px;
+ }
+
+ .end-displayer {
+ width: auto;
+ right: 8px;
+ }
+
.start-displayer {
bottom: 4px;
left: 16px;
@@ -347,6 +370,12 @@
}
.simulation-player-container.open {
+
+ .start-displayer,
+ .end-displayer {
+ display: none;
+ }
+
.timmer {
display: none;
}
diff --git a/app/src/styles/components/tools.scss b/app/src/styles/components/tools.scss
index e476c3c..29d37b4 100644
--- a/app/src/styles/components/tools.scss
+++ b/app/src/styles/components/tools.scss
@@ -15,7 +15,7 @@
transition: width 0.2s;
background: var(--background-color);
backdrop-filter: blur(8px);
- z-index: #{$z-index-default};
+ z-index: 2;
outline: 1px solid var(--border-color);
outline-offset: -1px;
@@ -124,6 +124,8 @@
padding: 4px;
border-radius: #{$border-radius-medium};
background: var(--background-color);
+ outline: 1px solid var(--border-color);
+ outline-offset: -1px;
gap: 5px;
position: relative;
diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss
index c7f1694..f8c022f 100644
--- a/app/src/styles/layout/sidebar.scss
+++ b/app/src/styles/layout/sidebar.scss
@@ -58,12 +58,8 @@
fill: var(--icon-default-color-active);
}
&:hover {
- rect {
- stroke: var(--icon-default-color);
- }
- circle {
- fill: var(--icon-default-color);
- }
+ filter: saturate(0.8);
+ background: var(--background-color-accent);
}
}
}
@@ -420,7 +416,7 @@
outline: none;
path {
stroke: var(--text-button-color);
- stroke-width: 1.3;
+ strokeWidth: 1.3;
}
}
}
@@ -686,7 +682,7 @@
path {
stroke: var(--accent-color);
- stroke-width: 1.5px;
+ strokeWidth: 1.5px;
}
&:hover {
@@ -714,10 +710,10 @@
.add-button {
@include flex-center;
- padding: 2px 4px;
+ padding: 4px 8px;
background: var(--background-color-button);
color: var(--text-button-color);
- border-radius: #{$border-radius-small};
+ border-radius: #{$border-radius-large};
cursor: pointer;
outline: none;
border: none;
@@ -832,10 +828,10 @@
transform: translateX(4px);
&:hover {
- background: var(--accent-color);
+ background: var(--background-color-accent);
path {
- stroke: var(--primary-color);
+ stroke: var(--text-button-color);
}
}
}
@@ -1003,10 +999,10 @@
border-radius: 8px 0 0 8px;
&:hover {
- background: var(--accent-color);
+ background: var(--background-color-accent);
path {
- stroke: var(--primary-color);
+ stroke: var(--text-button-color);
}
}
}
@@ -1067,7 +1063,13 @@
.dropdown-content-container {
padding: 6px 12px;
}
-
+ .value-field-container {
+ padding: 6px;
+ .dropdown {
+ min-width: 44px;
+ text-align: center;
+ }
+ }
.input-range-container {
.input-container {
width: 75%;
diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss
index 66a60e7..3adfc0f 100644
--- a/app/src/styles/main.scss
+++ b/app/src/styles/main.scss
@@ -13,7 +13,6 @@
@use 'components/button';
@use 'components/form';
@use 'components/input';
-@use 'components/layouts';
@use 'components/lists';
@use 'components/moduleToggle';
@use 'components/templates';
@@ -22,11 +21,12 @@
@use 'components/visualization/ui/styledWidgets';
@use 'components/visualization/floating/common';
@use 'components/marketPlace/marketPlace';
-@use 'components/simulation/simulation';
@use 'components/menu/menu';
@use 'components/confirmationPopUp';
-@use 'components/analysis/analysis';
-@use 'components/analysis/ROISummary.scss';
+@use 'components/simulation/simulation';
+@use 'components/simulation/analysis';
+@use 'components/logs/logs';
+@use 'components/footer/footer.scss';
// layout
@use 'layout/loading';
diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss
index fa1b86c..abc626a 100644
--- a/app/src/styles/pages/realTimeViz.scss
+++ b/app/src/styles/pages/realTimeViz.scss
@@ -3,8 +3,6 @@
// Main Container
.realTime-viz {
- background: #131313;
- box-shadow: $box-shadow-medium;
width: calc(100% - (320px + 270px + 90px));
height: calc(100% - (250px));
position: absolute;
@@ -12,8 +10,8 @@
left: calc(270px + 45px);
transform: translate(0, -50%);
border-radius: #{$border-radius-medium};
- transition: all 0.2s;
- z-index: #{$z-index-default};
+ z-index: 2;
+ pointer-events: none;
.realTime-viz-wrapper {
width: 100%;
@@ -39,10 +37,6 @@
z-index: 1;
}
- .scene-container {
- overflow: hidden;
- }
-
.icon {
display: flex;
align-items: center;
@@ -74,6 +68,8 @@
z-index: 3;
transform: translate(-50%, -10%);
transition: transform 0.5s linear;
+ pointer-events: all;
+
&::-webkit-scrollbar {
display: none;
}
@@ -367,6 +363,7 @@
border-radius: 2px;
transition: transform 0.3s ease;
box-shadow: #{$box-shadow-medium};
+ pointer-events: all;
svg {
stroke: var(--icon-default-color) !important;
@@ -426,10 +423,8 @@
path {
stroke: #f65648;
- stroke-width: 1.3;
+ strokeWidth: 1.3;
}
-
-
}
}
@@ -778,17 +773,10 @@
}
}
-
-
-
.panel-content {
background: var(--background-color);
-
}
-
-
-
/* RIGHT */
.panel-content.right-opening {
animation: rightExpand 0.5s ease-in-out forwards;
@@ -913,9 +901,6 @@
}
}
-
-
-
// Add button
.extra-Bs-addopening {
@@ -926,7 +911,6 @@
animation: slideUp 0.3s ease forwards;
}
-
@keyframes slideDown {
from {
opacity: 0;
diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts
index cb71864..11d5156 100644
--- a/app/src/types/simulationTypes.d.ts
+++ b/app/src/types/simulationTypes.d.ts
@@ -13,7 +13,7 @@ interface TriggerSchema {
delay: number;
triggeredAsset: {
triggeredModel: { modelName: string, modelUuid: string };
- triggeredPoint: { pointName: string, pointUuid: string };
+ triggeredPoint: { pointName: string, pointUuid: string } | null;
triggeredAction: { actionName: string, actionUuid: string } | null;
} | null;
}
@@ -88,6 +88,7 @@ interface StoragePointSchema {
actionType: "store";
materials: { materialName: string; materialId: string; }[];
storageCapacity: number;
+ triggers: TriggerSchema[];
};
}
@@ -143,6 +144,10 @@ interface MachineStatus extends MachineEventSchema {
isActive: boolean;
idleTime: number;
activeTime: number;
+ currentAction?: {
+ actionUuid: string;
+ actionName: string;
+ };
}
interface ArmBotStatus extends RoboticArmEventSchema {
diff --git a/app/src/utils/handleSnap.ts b/app/src/utils/handleSnap.ts
new file mode 100644
index 0000000..bd6a74d
--- /dev/null
+++ b/app/src/utils/handleSnap.ts
@@ -0,0 +1,22 @@
+export function snapControls(value: number, event: string): number {
+ const CTRL_DISTANCE = 1; // Snap to whole numbers when Ctrl is pressed
+ const SHIFT_DISTANCE = 0.01; // Snap to half-step increments when Shift is pressed
+ const CTRL_SHIFT_DISTANCE = 0.1; // Snap to fine increments when both Ctrl and Shift are pressed
+
+ switch (event) {
+ case "Ctrl":
+ return Math.round(value / CTRL_DISTANCE) * CTRL_DISTANCE;
+
+ case "Shift":
+ return Math.round(value / SHIFT_DISTANCE) * SHIFT_DISTANCE;
+
+ case "Ctrl+Shift":
+ const base = Math.floor(value / CTRL_DISTANCE) * CTRL_DISTANCE;
+ const offset =
+ Math.round((value - base) / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE;
+ return base + offset;
+
+ default:
+ return value; // No snapping if no modifier key is pressed
+ }
+}