diff --git a/app/src/assets/gltf-glb/default.glb b/app/src/assets/gltf-glb/default.glb
new file mode 100644
index 0000000..fd19420
Binary files /dev/null and b/app/src/assets/gltf-glb/default.glb differ
diff --git a/app/src/assets/gltf-glb/material1.glb b/app/src/assets/gltf-glb/material1.glb
new file mode 100644
index 0000000..405bdb0
Binary files /dev/null and b/app/src/assets/gltf-glb/material1.glb differ
diff --git a/app/src/assets/gltf-glb/material2.glb b/app/src/assets/gltf-glb/material2.glb
new file mode 100644
index 0000000..9123a7c
Binary files /dev/null and b/app/src/assets/gltf-glb/material2.glb differ
diff --git a/app/src/assets/gltf-glb/material3.glb b/app/src/assets/gltf-glb/material3.glb
new file mode 100644
index 0000000..8e9c191
Binary files /dev/null and b/app/src/assets/gltf-glb/material3.glb differ
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx
index a6a1fa9..4f96674 100644
--- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx
+++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx
@@ -103,7 +103,7 @@ function ConveyorMechanics() {
const handleSpawnCountChange = (value: string) => {
if (!selectedEventData || !selectedPointData) return;
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
- spawnCount: value === "inherit" ? "inherit" : parseFloat(value),
+ spawnCount: parseFloat(value),
});
if (event) {
@@ -119,7 +119,7 @@ function ConveyorMechanics() {
const handleSpawnIntervalChange = (value: string) => {
if (!selectedEventData || !selectedPointData) return;
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
- spawnInterval: value === "inherit" ? "inherit" : parseFloat(value),
+ spawnInterval: parseFloat(value),
});
if (event) {
@@ -149,7 +149,7 @@ function ConveyorMechanics() {
const handleDelayChange = (value: string) => {
if (!selectedEventData || !selectedPointData) return;
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
- delay: value === "inherit" ? "inherit" : parseFloat(value),
+ delay: parseFloat(value),
});
if (event) {
@@ -271,7 +271,7 @@ function ConveyorMechanics() {
-
+
>
diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx
index d5ca04f..1ab9d59 100644
--- a/app/src/modules/builder/groups/floorItemsGroup.tsx
+++ b/app/src/modules/builder/groups/floorItemsGroup.tsx
@@ -1,25 +1,25 @@
import { useFrame, useThree } from "@react-three/fiber";
import {
- useActiveTool,
- useAsset3dWidget,
- useCamMode,
- useDeletableFloorItem,
- useDeleteTool,
- useFloorItems,
- useLoadingProgress,
- useRenderDistance,
- useSelectedFloorItem,
- useSelectedItem,
- useSocketStore,
- useToggleView,
- useTransformMode,
+ useActiveTool,
+ useAsset3dWidget,
+ useCamMode,
+ useDeletableFloorItem,
+ useDeleteTool,
+ useFloorItems,
+ useLoadingProgress,
+ useRenderDistance,
+ useSelectedFloorItem,
+ useSelectedItem,
+ useSocketStore,
+ useToggleView,
+ useTransformMode,
} from "../../../store/store";
import assetVisibility from "../geomentries/assets/assetVisibility";
import { useEffect } from "react";
import * as THREE from "three";
import * as Types from "../../../types/world/worldTypes";
import assetManager, {
- cancelOngoingTasks,
+ cancelOngoingTasks,
} from "../geomentries/assets/assetManager";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
@@ -33,421 +33,420 @@ import useModuleStore from "../../../store/useModuleStore";
import { useEventsStore } from "../../../store/simulation/useEventsStore";
const assetManagerWorker = new Worker(
- new URL(
- "../../../services/factoryBuilder/webWorkers/assetManagerWorker.js",
- import.meta.url
- )
+ new URL(
+ "../../../services/factoryBuilder/webWorkers/assetManagerWorker.js",
+ import.meta.url
+ )
);
const gltfLoaderWorker = new Worker(
- new URL(
- "../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
- import.meta.url
- )
+ new URL(
+ "../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
+ import.meta.url
+ )
);
const FloorItemsGroup = ({
- itemsGroup,
- hoveredDeletableFloorItem,
- AttachedObject,
- floorGroup,
- tempLoader,
- isTempLoader,
- plane,
+ itemsGroup,
+ hoveredDeletableFloorItem,
+ AttachedObject,
+ floorGroup,
+ tempLoader,
+ isTempLoader,
+ plane,
}: any) => {
- const state: Types.ThreeState = useThree();
- const { raycaster, controls }: any = state;
- const { renderDistance } = useRenderDistance();
- const { toggleView } = useToggleView();
- const { floorItems, setFloorItems } = useFloorItems();
- const { camMode } = useCamMode();
- const { deleteTool } = useDeleteTool();
- const { setDeletableFloorItem } = useDeletableFloorItem();
- const { transformMode } = useTransformMode();
- const { setSelectedFloorItem } = useSelectedFloorItem();
- const { activeTool } = useActiveTool();
- const { selectedItem, setSelectedItem } = useSelectedItem();
- const { setLoadingProgress } = useLoadingProgress();
- const { activeModule } = useModuleStore();
- const { socket } = useSocketStore();
- const loader = new GLTFLoader();
- const dracoLoader = new DRACOLoader();
- const { addEvent } = useEventsStore();
+ const state: Types.ThreeState = useThree();
+ const { raycaster, controls }: any = state;
+ const { renderDistance } = useRenderDistance();
+ const { toggleView } = useToggleView();
+ const { floorItems, setFloorItems } = useFloorItems();
+ const { camMode } = useCamMode();
+ const { deleteTool } = useDeleteTool();
+ const { setDeletableFloorItem } = useDeletableFloorItem();
+ const { transformMode } = useTransformMode();
+ const { setSelectedFloorItem } = useSelectedFloorItem();
+ const { activeTool } = useActiveTool();
+ const { selectedItem, setSelectedItem } = useSelectedItem();
+ const { setLoadingProgress } = useLoadingProgress();
+ const { activeModule } = useModuleStore();
+ const { socket } = useSocketStore();
+ const loader = new GLTFLoader();
+ const dracoLoader = new DRACOLoader();
+ const { addEvent } = useEventsStore();
- dracoLoader.setDecoderPath(
- "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"
- );
- loader.setDRACOLoader(dracoLoader);
+ dracoLoader.setDecoderPath(
+ "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"
+ );
+ loader.setDRACOLoader(dracoLoader);
- useEffect(() => {
- const email = localStorage.getItem("email");
- const organization = email!.split("@")[1].split(".")[0];
+ useEffect(() => {
+ const email = localStorage.getItem("email");
+ const organization = email!.split("@")[1].split(".")[0];
- let totalAssets = 0;
- let loadedAssets = 0;
+ let totalAssets = 0;
+ let loadedAssets = 0;
- const updateLoadingProgress = (progress: number) => {
- if (progress < 100) {
- setLoadingProgress(progress);
- } else if (progress === 100) {
- setTimeout(() => {
- setLoadingProgress(100);
- setTimeout(() => {
- setLoadingProgress(0);
- }, 1500);
- }, 1000);
- }
- };
+ const updateLoadingProgress = (progress: number) => {
+ if (progress < 100) {
+ setLoadingProgress(progress);
+ } else if (progress === 100) {
+ setTimeout(() => {
+ setLoadingProgress(100);
+ setTimeout(() => {
+ setLoadingProgress(0);
+ }, 1500);
+ }, 1000);
+ }
+ };
- getFloorAssets(organization).then((data) => {
- if (data.length > 0) {
- const uniqueItems = (data as Types.FloorItems).filter(
- (item, index, self) =>
- index === self.findIndex((t) => t.modelfileID === item.modelfileID)
- );
- totalAssets = uniqueItems.length;
- if (totalAssets === 0) {
- updateLoadingProgress(100);
- return;
- }
- gltfLoaderWorker.postMessage({ floorItems: data });
- } else {
- gltfLoaderWorker.postMessage({ floorItems: [] });
- loadInitialFloorItems(
- itemsGroup,
- setFloorItems,
- addEvent,
- renderDistance
- );
- updateLoadingProgress(100);
- }
- });
-
- gltfLoaderWorker.onmessage = async (event) => {
- if (event.data.message === "gltfLoaded" && event.data.modelBlob) {
- const blobUrl = URL.createObjectURL(event.data.modelBlob);
-
- loader.load(blobUrl, (gltf) => {
- URL.revokeObjectURL(blobUrl);
- THREE.Cache.remove(blobUrl);
- THREE.Cache.add(event.data.modelID, gltf);
-
- loadedAssets++;
- const progress = Math.round((loadedAssets / totalAssets) * 100);
- updateLoadingProgress(progress);
-
- if (loadedAssets === totalAssets) {
- loadInitialFloorItems(
- itemsGroup,
- setFloorItems,
- addEvent,
- renderDistance
- );
- updateLoadingProgress(100);
- }
+ getFloorAssets(organization).then((data) => {
+ if (data.length > 0) {
+ const uniqueItems = (data as Types.FloorItems).filter(
+ (item, index, self) =>
+ index === self.findIndex((t) => t.modelfileID === item.modelfileID)
+ );
+ totalAssets = uniqueItems.length;
+ if (totalAssets === 0) {
+ updateLoadingProgress(100);
+ return;
+ }
+ gltfLoaderWorker.postMessage({ floorItems: data });
+ } else {
+ gltfLoaderWorker.postMessage({ floorItems: [] });
+ loadInitialFloorItems(
+ itemsGroup,
+ setFloorItems,
+ addEvent,
+ renderDistance
+ );
+ updateLoadingProgress(100);
+ }
});
- }
- };
- }, []);
- useEffect(() => {
- assetManagerWorker.onmessage = async (event) => {
- cancelOngoingTasks(); // Cancel the ongoing process
- await assetManager(event.data, itemsGroup, loader);
- };
- }, [assetManagerWorker]);
+ gltfLoaderWorker.onmessage = async (event) => {
+ if (event.data.message === "gltfLoaded" && event.data.modelBlob) {
+ const blobUrl = URL.createObjectURL(event.data.modelBlob);
- useEffect(() => {
- if (toggleView) return;
+ loader.load(blobUrl, (gltf) => {
+ URL.revokeObjectURL(blobUrl);
+ THREE.Cache.remove(blobUrl);
+ THREE.Cache.add(event.data.modelID, gltf);
- const uuids: string[] = [];
- itemsGroup.current?.children.forEach((child: any) => {
- uuids.push(child.uuid);
- });
- const cameraPosition = state.camera.position;
+ loadedAssets++;
+ const progress = Math.round((loadedAssets / totalAssets) * 100);
+ updateLoadingProgress(progress);
- assetManagerWorker.postMessage({
- floorItems,
- cameraPosition,
- uuids,
- renderDistance,
- });
- }, [camMode, renderDistance]);
+ if (loadedAssets === totalAssets) {
+ loadInitialFloorItems(
+ itemsGroup,
+ setFloorItems,
+ addEvent,
+ renderDistance
+ );
+ updateLoadingProgress(100);
+ }
+ });
+ }
+ };
+ }, []);
- useEffect(() => {
- const controls: any = state.controls;
- const camera: any = state.camera;
+ useEffect(() => {
+ assetManagerWorker.onmessage = async (event) => {
+ cancelOngoingTasks(); // Cancel the ongoing process
+ await assetManager(event.data, itemsGroup, loader);
+ };
+ }, [assetManagerWorker]);
- if (controls) {
- let intervalId: NodeJS.Timeout | null = null;
-
- const handleChange = () => {
+ useEffect(() => {
if (toggleView) return;
const uuids: string[] = [];
itemsGroup.current?.children.forEach((child: any) => {
- uuids.push(child.uuid);
+ uuids.push(child.uuid);
});
- const cameraPosition = camera.position;
+ const cameraPosition = state.camera.position;
assetManagerWorker.postMessage({
- floorItems,
- cameraPosition,
- uuids,
- renderDistance,
+ floorItems,
+ cameraPosition,
+ uuids,
+ renderDistance,
});
- };
+ }, [camMode, renderDistance]);
- const startInterval = () => {
- if (!intervalId) {
- intervalId = setInterval(handleChange, 50);
+ useEffect(() => {
+ const controls: any = state.controls;
+ const camera: any = state.camera;
+
+ if (controls) {
+ let intervalId: NodeJS.Timeout | null = null;
+
+ const handleChange = () => {
+ if (toggleView) return;
+
+ const uuids: string[] = [];
+ itemsGroup.current?.children.forEach((child: any) => {
+ uuids.push(child.uuid);
+ });
+ const cameraPosition = camera.position;
+
+ assetManagerWorker.postMessage({
+ floorItems,
+ cameraPosition,
+ uuids,
+ renderDistance,
+ });
+ };
+
+ const startInterval = () => {
+ if (!intervalId) {
+ intervalId = setInterval(handleChange, 50);
+ }
+ };
+
+ const stopInterval = () => {
+ handleChange();
+ if (intervalId) {
+ clearInterval(intervalId);
+ intervalId = null;
+ }
+ };
+
+ controls.addEventListener("rest", handleChange);
+ controls.addEventListener("rest", stopInterval);
+ controls.addEventListener("control", startInterval);
+ controls.addEventListener("controlend", stopInterval);
+
+ return () => {
+ controls.removeEventListener("rest", handleChange);
+ controls.removeEventListener("rest", stopInterval);
+ controls.removeEventListener("control", startInterval);
+ controls.removeEventListener("controlend", stopInterval);
+ if (intervalId) {
+ clearInterval(intervalId);
+ }
+ };
}
- };
+ }, [state.controls, floorItems, toggleView, renderDistance]);
- const stopInterval = () => {
- handleChange();
- if (intervalId) {
- clearInterval(intervalId);
- intervalId = null;
- }
- };
+ useEffect(() => {
+ const canvasElement = state.gl.domElement;
+ let drag = false;
+ let isLeftMouseDown = false;
- controls.addEventListener("rest", handleChange);
- controls.addEventListener("rest", stopInterval);
- controls.addEventListener("control", startInterval);
- controls.addEventListener("controlend", stopInterval);
-
- return () => {
- controls.removeEventListener("rest", handleChange);
- controls.removeEventListener("rest", stopInterval);
- controls.removeEventListener("control", startInterval);
- controls.removeEventListener("controlend", stopInterval);
- if (intervalId) {
- clearInterval(intervalId);
- }
- };
- }
- }, [state.controls, floorItems, toggleView, renderDistance]);
-
- useEffect(() => {
- const canvasElement = state.gl.domElement;
- let drag = false;
- let isLeftMouseDown = false;
-
- const onMouseDown = (evt: any) => {
- if (evt.button === 0) {
- isLeftMouseDown = true;
- drag = false;
- }
- };
-
- const onMouseMove = () => {
- console.log('isLeftMouseDown: ', isLeftMouseDown);
- if (isLeftMouseDown) {
- drag = true;
- }
- };
-
- const onMouseUp = async (evt: any) => {
- if (controls) {
- (controls as any).enabled = true;
- }
- if (evt.button === 0) {
- isLeftMouseDown = false;
- if (drag) return;
-
- if (deleteTool) {
- DeleteFloorItems(
- itemsGroup,
- hoveredDeletableFloorItem,
- setFloorItems,
- socket
- );
- }
- const Mode = transformMode;
-
- if (Mode !== null || activeTool === "cursor") {
- if (!itemsGroup.current) return;
- let intersects = raycaster.intersectObjects(
- itemsGroup.current.children,
- true
- );
- 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;
- // while (currentObject) {
- // if (currentObject.name === "Scene") {
- // break;
- // }
- // currentObject = currentObject.parent as THREE.Object3D;
- // }
- // if (currentObject) {
- // AttachedObject.current = currentObject as any;
- // setSelectedFloorItem(AttachedObject.current!);
- // }
- } else {
- const target = controls.getTarget(new THREE.Vector3());
- await controls.setTarget(target.x, 0, target.z, true);
- setSelectedFloorItem(null);
- }
- }
- }
- };
-
- const onDblClick = async (evt: any) => {
- if (evt.button === 0) {
- isLeftMouseDown = false;
- if (drag) return;
-
- const Mode = transformMode;
-
- if (Mode !== null || activeTool === "cursor") {
- if (!itemsGroup.current) return;
- let intersects = raycaster.intersectObjects(
- itemsGroup.current.children,
- true
- );
- 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;
-
- while (currentObject) {
- if (currentObject.name === "Scene") {
- break;
- }
- currentObject = currentObject.parent as THREE.Object3D;
+ const onMouseDown = (evt: any) => {
+ if (evt.button === 0) {
+ isLeftMouseDown = true;
+ drag = false;
}
- if (currentObject) {
- AttachedObject.current = currentObject as any;
- // controls.fitToSphere(AttachedObject.current!, true);
+ };
- const bbox = new THREE.Box3().setFromObject(
- AttachedObject.current
- );
- const size = bbox.getSize(new THREE.Vector3());
- const center = bbox.getCenter(new THREE.Vector3());
-
- const front = new THREE.Vector3(0, 0, 1);
- AttachedObject.current.localToWorld(front);
- front.sub(AttachedObject.current.position).normalize();
-
- const distance = Math.max(size.x, size.y, size.z) * 2;
- const newPosition = center
- .clone()
- .addScaledVector(front, distance);
-
- controls.setPosition(
- newPosition.x,
- newPosition.y,
- newPosition.z,
- true
- );
- controls.setTarget(center.x, center.y, center.z, true);
- controls.fitToBox(AttachedObject.current!, true, {
- cover: true,
- paddingTop: 5,
- paddingLeft: 5,
- paddingBottom: 5,
- paddingRight: 5,
- });
-
- setSelectedFloorItem(AttachedObject.current!);
+ const onMouseMove = () => {
+ if (isLeftMouseDown) {
+ drag = true;
+ }
+ };
+
+ const onMouseUp = async (evt: any) => {
+ if (controls) {
+ (controls as any).enabled = true;
+ }
+ if (evt.button === 0) {
+ isLeftMouseDown = false;
+ if (drag) return;
+
+ if (deleteTool) {
+ DeleteFloorItems(
+ itemsGroup,
+ hoveredDeletableFloorItem,
+ setFloorItems,
+ socket
+ );
+ }
+ const Mode = transformMode;
+
+ if (Mode !== null || activeTool === "cursor") {
+ if (!itemsGroup.current) return;
+ let intersects = raycaster.intersectObjects(
+ itemsGroup.current.children,
+ true
+ );
+ 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;
+ // while (currentObject) {
+ // if (currentObject.name === "Scene") {
+ // break;
+ // }
+ // currentObject = currentObject.parent as THREE.Object3D;
+ // }
+ // if (currentObject) {
+ // AttachedObject.current = currentObject as any;
+ // setSelectedFloorItem(AttachedObject.current!);
+ // }
+ } else {
+ const target = controls.getTarget(new THREE.Vector3());
+ await controls.setTarget(target.x, 0, target.z, true);
+ setSelectedFloorItem(null);
+ }
+ }
+ }
+ };
+
+ const onDblClick = async (evt: any) => {
+ if (evt.button === 0) {
+ isLeftMouseDown = false;
+ if (drag) return;
+
+ const Mode = transformMode;
+
+ if (Mode !== null || activeTool === "cursor") {
+ if (!itemsGroup.current) return;
+ let intersects = raycaster.intersectObjects(
+ itemsGroup.current.children,
+ true
+ );
+ 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;
+
+ while (currentObject) {
+ if (currentObject.name === "Scene") {
+ break;
+ }
+ currentObject = currentObject.parent as THREE.Object3D;
+ }
+ if (currentObject) {
+ AttachedObject.current = currentObject as any;
+ // controls.fitToSphere(AttachedObject.current!, true);
+
+ const bbox = new THREE.Box3().setFromObject(
+ AttachedObject.current
+ );
+ const size = bbox.getSize(new THREE.Vector3());
+ const center = bbox.getCenter(new THREE.Vector3());
+
+ const front = new THREE.Vector3(0, 0, 1);
+ AttachedObject.current.localToWorld(front);
+ front.sub(AttachedObject.current.position).normalize();
+
+ const distance = Math.max(size.x, size.y, size.z) * 2;
+ const newPosition = center
+ .clone()
+ .addScaledVector(front, distance);
+
+ controls.setPosition(
+ newPosition.x,
+ newPosition.y,
+ newPosition.z,
+ true
+ );
+ controls.setTarget(center.x, center.y, center.z, true);
+ controls.fitToBox(AttachedObject.current!, true, {
+ cover: true,
+ paddingTop: 5,
+ paddingLeft: 5,
+ paddingBottom: 5,
+ paddingRight: 5,
+ });
+
+ setSelectedFloorItem(AttachedObject.current!);
+ }
+ } else {
+ const target = controls.getTarget(new THREE.Vector3());
+ await controls.setTarget(target.x, 0, target.z, true);
+ setSelectedFloorItem(null);
+ }
+ }
+ }
+ };
+
+ const onDrop = (event: any) => {
+ if (!event.dataTransfer?.files[0]) return;
+
+ if (selectedItem.id !== "" && event.dataTransfer?.files[0]) {
+ addAssetModel(
+ raycaster,
+ state.camera,
+ state.pointer,
+ floorGroup,
+ setFloorItems,
+ itemsGroup,
+ isTempLoader,
+ tempLoader,
+ socket,
+ selectedItem,
+ setSelectedItem,
+ addEvent,
+ plane
+ );
+ }
+ };
+
+ const onDragOver = (event: any) => {
+ event.preventDefault();
+ };
+
+ if (activeModule === "builder") {
+ canvasElement.addEventListener("mousedown", onMouseDown);
+ canvasElement.addEventListener("mouseup", onMouseUp);
+ canvasElement.addEventListener("mousemove", onMouseMove);
+ canvasElement.addEventListener("dblclick", onDblClick);
+ canvasElement.addEventListener("drop", onDrop);
+ canvasElement.addEventListener("dragover", onDragOver);
+ } else {
+ if (controls) {
+ const target = controls.getTarget(new THREE.Vector3());
+ controls.setTarget(target.x, 0, target.z, true);
+ setSelectedFloorItem(null);
}
- } else {
- const target = controls.getTarget(new THREE.Vector3());
- await controls.setTarget(target.x, 0, target.z, true);
- setSelectedFloorItem(null);
- }
}
- }
- };
- const onDrop = (event: any) => {
- if (!event.dataTransfer?.files[0]) return;
+ return () => {
+ canvasElement.removeEventListener("mousedown", onMouseDown);
+ canvasElement.removeEventListener("mouseup", onMouseUp);
+ canvasElement.removeEventListener("mousemove", onMouseMove);
+ canvasElement.removeEventListener("dblclick", onDblClick);
+ canvasElement.removeEventListener("drop", onDrop);
+ canvasElement.removeEventListener("dragover", onDragOver);
+ };
+ }, [
+ deleteTool,
+ transformMode,
+ controls,
+ selectedItem,
+ state.camera,
+ state.pointer,
+ activeTool,
+ activeModule,
+ ]);
- if (selectedItem.id !== "" && event.dataTransfer?.files[0]) {
- addAssetModel(
- raycaster,
- state.camera,
- state.pointer,
- floorGroup,
- setFloorItems,
- itemsGroup,
- isTempLoader,
- tempLoader,
- socket,
- selectedItem,
- setSelectedItem,
- addEvent,
- plane
- );
- }
- };
+ useFrame(() => {
+ if (controls)
+ if (deleteTool && activeModule === "builder") {
+ // assetVisibility(itemsGroup, state.camera.position, renderDistance);
+ DeletableHoveredFloorItems(
+ state,
+ itemsGroup,
+ hoveredDeletableFloorItem,
+ setDeletableFloorItem
+ );
+ } else if (!deleteTool) {
+ if (hoveredDeletableFloorItem.current) {
+ hoveredDeletableFloorItem.current = undefined;
+ setDeletableFloorItem(null);
+ }
+ }
+ });
- const onDragOver = (event: any) => {
- event.preventDefault();
- };
-
- if (activeModule === "builder") {
- canvasElement.addEventListener("mousedown", onMouseDown);
- canvasElement.addEventListener("mouseup", onMouseUp);
- canvasElement.addEventListener("mousemove", onMouseMove);
- canvasElement.addEventListener("dblclick", onDblClick);
- canvasElement.addEventListener("drop", onDrop);
- canvasElement.addEventListener("dragover", onDragOver);
- } else {
- if (controls) {
- const target = controls.getTarget(new THREE.Vector3());
- controls.setTarget(target.x, 0, target.z, true);
- setSelectedFloorItem(null);
- }
- }
-
- return () => {
- canvasElement.removeEventListener("mousedown", onMouseDown);
- canvasElement.removeEventListener("mouseup", onMouseUp);
- canvasElement.removeEventListener("mousemove", onMouseMove);
- canvasElement.removeEventListener("dblclick", onDblClick);
- canvasElement.removeEventListener("drop", onDrop);
- canvasElement.removeEventListener("dragover", onDragOver);
- };
- }, [
- deleteTool,
- transformMode,
- controls,
- selectedItem,
- state.camera,
- state.pointer,
- activeTool,
- activeModule,
- ]);
-
- useFrame(() => {
- if (controls)
- if (deleteTool && activeModule === "builder") {
- // assetVisibility(itemsGroup, state.camera.position, renderDistance);
- DeletableHoveredFloorItems(
- state,
- itemsGroup,
- hoveredDeletableFloorItem,
- setDeletableFloorItem
- );
- } else if (!deleteTool) {
- if (hoveredDeletableFloorItem.current) {
- hoveredDeletableFloorItem.current = undefined;
- setDeletableFloorItem(null);
- }
- }
- });
-
- return ;
+ return ;
};
export default FloorItemsGroup;
diff --git a/app/src/modules/simulation/actions/conveyor/actionHandler/spawnActionHandler.ts b/app/src/modules/simulation/actions/conveyor/actionHandler/spawnActionHandler.ts
new file mode 100644
index 0000000..c28f1d0
--- /dev/null
+++ b/app/src/modules/simulation/actions/conveyor/actionHandler/spawnActionHandler.ts
@@ -0,0 +1,129 @@
+import { useCallback, useEffect, useRef } from "react";
+import * as THREE from 'three';
+import { useFrame } from "@react-three/fiber";
+import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore";
+import { useProductStore } from "../../../../../store/simulation/useProductStore";
+import { useSelectedProduct } from "../../../../../store/simulation/useSimulationStore";
+
+export function useSpawnHandler() {
+ const { addMaterial } = useMaterialStore();
+ const { getModelUuidByActionUuid } = useProductStore();
+ const { selectedProduct } = useSelectedProduct();
+ const lastSpawnTime = useRef(null);
+ const startTime = useRef(null);
+ const spawnCountRef = useRef(0);
+ const spawnParams = useRef<{
+ material: string;
+ intervalMs: number;
+ totalCount: number;
+ point: ConveyorPointSchema;
+ } | null>(null);
+
+ const clearCurrentSpawn = useCallback(() => {
+ lastSpawnTime.current = null;
+ startTime.current = null;
+ spawnCountRef.current = 0;
+ spawnParams.current = null;
+ }, []);
+
+ const spawnLogStatus = (materialUuid: string, status: string) => {
+ // console.log(`${materialUuid}, ${status}`);
+ }
+
+ const createNewMaterial = useCallback((materialType: string, point: ConveyorPointSchema) => {
+ const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, point.action.actionUuid);
+ if (!modelUuid || !point.uuid) return;
+
+ const newMaterial: MaterialSchema = {
+ materialId: THREE.MathUtils.generateUUID(),
+ materialName: `${materialType}-${Date.now()}`,
+ materialType: materialType,
+ isActive: false,
+ isVisible: true,
+ isRendered: true,
+ current: {
+ modelUuid: modelUuid,
+ pointUuid: point.uuid,
+ actionUuid: point.action?.actionUuid || ''
+ },
+ weight: 1,
+ cost: 1
+ };
+
+ addMaterial(newMaterial);
+ return newMaterial;
+ }, [addMaterial]);
+
+ useFrame(() => {
+ if (!spawnParams.current || !startTime.current) return;
+
+ const currentTime = performance.now();
+ const { material, intervalMs, totalCount, point } = spawnParams.current;
+ const isFirstSpawn = lastSpawnTime.current === null;
+ const elapsed = currentTime - startTime.current;
+
+ // First spawn
+ if (isFirstSpawn) {
+ if (elapsed >= intervalMs) {
+ const createdMaterial = createNewMaterial(material, point);
+ if (createdMaterial) {
+ spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
+ }
+ lastSpawnTime.current = currentTime;
+ spawnCountRef.current = 1;
+
+ if (totalCount <= 1) {
+ clearCurrentSpawn();
+ }
+ }
+ return;
+ }
+
+ // Subsequent spawns
+ if (lastSpawnTime.current !== null) {
+ const timeSinceLast = currentTime - lastSpawnTime.current;
+ if (timeSinceLast >= intervalMs) {
+ const count = spawnCountRef.current + 1;
+ const createdMaterial = createNewMaterial(material, point);
+ if (createdMaterial) {
+ spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
+ }
+ lastSpawnTime.current = currentTime;
+ spawnCountRef.current = count;
+
+ if (count >= totalCount) {
+ clearCurrentSpawn();
+ }
+ }
+ }
+ });
+
+ const handleSpawn = useCallback((point: ConveyorPointSchema) => {
+ if (!point.action || point.action.actionType !== 'spawn') return;
+
+ const { material, spawnInterval = 0, spawnCount = 1 } = point.action;
+ const intervalMs = spawnInterval * 1000;
+
+ clearCurrentSpawn();
+
+ spawnParams.current = {
+ material,
+ intervalMs,
+ totalCount: spawnCount,
+ point: point
+ };
+
+ startTime.current = performance.now();
+ }, [clearCurrentSpawn]);
+
+ useEffect(() => {
+ return () => {
+ clearCurrentSpawn();
+ };
+ }, [clearCurrentSpawn]);
+
+ return {
+ handleSpawn,
+ clearCurrentSpawn
+ };
+}
\ No newline at end of file
diff --git a/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts b/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts
new file mode 100644
index 0000000..e10e115
--- /dev/null
+++ b/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts
@@ -0,0 +1,40 @@
+import { useEffect } from "react";
+import { useSpawnHandler } from "./actionHandler/spawnActionHandler";
+
+// Conveyor Actions
+export function useConveyorActions(point: ConveyorPointSchema) {
+ const { handleSpawn } = useSpawnHandler();
+
+ useEffect(() => {
+ if (!point.action) return;
+
+ const { actionType, material, delay, spawnInterval, spawnCount } = point.action;
+
+ const handleAction = () => {
+ switch (actionType) {
+ case 'default':
+ console.log(`Default conveyor action at point ${point.uuid}`);
+ break;
+ case 'spawn':
+ console.log(`Spawning material ${material} at point ${point.uuid}`);
+ handleSpawn(point);
+ break;
+ case 'swap':
+ console.log(`Swapping to material ${material} at point ${point.uuid}`);
+ break;
+ case 'delay':
+ console.log(`Delaying for ${delay}ms at point ${point.uuid}`);
+ break;
+ case 'despawn':
+ console.log(`Despawning material at point ${point.uuid}`);
+ break;
+ }
+ };
+
+ handleAction();
+
+ return () => {
+ // Cleanup if needed
+ };
+ }, [point]);
+}
\ No newline at end of file
diff --git a/app/src/modules/simulation/actions/machine/useMachineActions.ts b/app/src/modules/simulation/actions/machine/useMachineActions.ts
new file mode 100644
index 0000000..0ac9865
--- /dev/null
+++ b/app/src/modules/simulation/actions/machine/useMachineActions.ts
@@ -0,0 +1,23 @@
+import { useEffect } from 'react';
+
+// Machine Actions
+export function useMachineActions(point: MachinePointSchema) {
+ useEffect(() => {
+ if (!point.action) return;
+
+ const { actionType, processTime, swapMaterial } = point.action;
+
+ const handleAction = () => {
+ if (actionType === 'process') {
+ console.log(`Machine processing for ${processTime}ms at point ${point.uuid}`);
+ if (swapMaterial) {
+ console.log(`Swapping material to ${swapMaterial}`);
+ }
+ }
+ };
+
+ return () => {
+ // Cleanup if needed
+ };
+ }, [point]);
+}
\ No newline at end of file
diff --git a/app/src/modules/simulation/actions/roboticArm/useRoboticArmActions.ts b/app/src/modules/simulation/actions/roboticArm/useRoboticArmActions.ts
new file mode 100644
index 0000000..50a6196
--- /dev/null
+++ b/app/src/modules/simulation/actions/roboticArm/useRoboticArmActions.ts
@@ -0,0 +1,26 @@
+import { useEffect } from 'react';
+
+// Robotic Arm Actions
+export function useRoboticArmActions(point: RoboticArmPointSchema) {
+ useEffect(() => {
+ point.actions.forEach(action => {
+ const { actionType, process } = action;
+
+ const handleAction = () => {
+ if (actionType === 'pickAndPlace') {
+ console.log(`Robotic arm pick and place at point ${point.uuid}`);
+ if (process.startPoint) {
+ console.log(`Start point: ${process.startPoint}`);
+ }
+ if (process.endPoint) {
+ console.log(`End point: ${process.endPoint}`);
+ }
+ }
+ };
+ });
+
+ return () => {
+ // Cleanup if needed
+ };
+ }, [point]);
+}
\ No newline at end of file
diff --git a/app/src/modules/simulation/actions/storageUnit/useStorageUnitActions.ts b/app/src/modules/simulation/actions/storageUnit/useStorageUnitActions.ts
new file mode 100644
index 0000000..6ffa8ad
--- /dev/null
+++ b/app/src/modules/simulation/actions/storageUnit/useStorageUnitActions.ts
@@ -0,0 +1,22 @@
+import { useEffect } from 'react';
+
+// Storage Actions
+export function useStorageActions(point: StoragePointSchema) {
+ useEffect(() => {
+ if (!point.action) return;
+
+ const { actionType, materials, storageCapacity } = point.action;
+
+ const handleAction = () => {
+ if (actionType === 'store') {
+ console.log(`Storage action at point ${point.uuid}`);
+ console.log(`Materials: ${materials.map(m => m.materialName).join(', ')}`);
+ console.log(`Capacity: ${storageCapacity}`);
+ }
+ };
+
+ return () => {
+ // Cleanup if needed
+ };
+ }, [point]);
+}
\ No newline at end of file
diff --git a/app/src/modules/simulation/actions/temp.md b/app/src/modules/simulation/actions/temp.md
deleted file mode 100644
index e69de29..0000000
diff --git a/app/src/modules/simulation/actions/useActionHandler.ts b/app/src/modules/simulation/actions/useActionHandler.ts
new file mode 100644
index 0000000..0975e83
--- /dev/null
+++ b/app/src/modules/simulation/actions/useActionHandler.ts
@@ -0,0 +1,25 @@
+import { useConveyorActions } from "./conveyor/useConveyorActions";
+import { useMachineActions } from "./machine/useMachineActions";
+import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions";
+import { useStorageActions } from "./storageUnit/useStorageUnitActions";
+import { useVehicleActions } from "./vehicle/useVehicleActions";
+
+// Master hook that selects the appropriate action handler
+export function useActionHandler(point: PointsScheme) {
+ if ('actions' in point) {
+ // Robotic Arm
+ useRoboticArmActions(point);
+ } else if (point.action.actionType === 'travel') {
+ // Vehicle
+ useVehicleActions(point as VehiclePointSchema);
+ } else if (point.action.actionType === 'process') {
+ // Machine
+ useMachineActions(point as MachinePointSchema);
+ } else if (point.action.actionType === 'store') {
+ // Storage
+ useStorageActions(point as StoragePointSchema);
+ } else {
+ // Conveyor
+ useConveyorActions(point as ConveyorPointSchema);
+ }
+}
\ No newline at end of file
diff --git a/app/src/modules/simulation/actions/vehicle/useVehicleActions.ts b/app/src/modules/simulation/actions/vehicle/useVehicleActions.ts
new file mode 100644
index 0000000..7a6ce4d
--- /dev/null
+++ b/app/src/modules/simulation/actions/vehicle/useVehicleActions.ts
@@ -0,0 +1,26 @@
+import { useEffect } from 'react';
+
+// Vehicle Actions
+export function useVehicleActions(point: VehiclePointSchema) {
+ useEffect(() => {
+ if (!point.action) return;
+
+ const { actionType, unLoadDuration, loadCapacity, steeringAngle } = point.action;
+
+ const handleAction = () => {
+ if (actionType === 'travel') {
+ console.log(`Vehicle travel action at point ${point.uuid}`);
+ if (point.action.pickUpPoint) {
+ console.log(`Pick up at: ${JSON.stringify(point.action.pickUpPoint)}`);
+ }
+ if (point.action.unLoadPoint) {
+ console.log(`Unload at: ${JSON.stringify(point.action.unLoadPoint)}`);
+ }
+ }
+ };
+
+ return () => {
+ // Cleanup if needed
+ };
+ }, [point]);
+}
\ No newline at end of file
diff --git a/app/src/modules/simulation/machine/instances/machineInstances.tsx b/app/src/modules/simulation/machine/instances/machineInstances.tsx
index 8536cac..594265d 100644
--- a/app/src/modules/simulation/machine/instances/machineInstances.tsx
+++ b/app/src/modules/simulation/machine/instances/machineInstances.tsx
@@ -6,8 +6,8 @@ function MachineInstances() {
const { machines } = useMachineStore();
return (
<>
- {machines.map((val: MachineStatus) => (
-
+ {machines.map((machine: MachineStatus) => (
+
))}
>
diff --git a/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx b/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx
index 466e235..01a5e43 100644
--- a/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx
+++ b/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx
@@ -1,9 +1,22 @@
-import React from 'react'
+import React, { useEffect, useState } from 'react'
+import * as THREE from 'three';
+import MaterialAnimator from '../animator/materialAnimator'
-function MaterialInstance() {
- return (
- <>>
- )
+function MaterialInstance({ material }: { material: MaterialSchema }) {
+ const [position, setPosition] = useState();
+ const [rotation, setRotation] = useState();
+
+ useEffect(() => {
+ // console.log('material: ', material);
+ }, [material])
+
+ return (
+ <>
+
+
+
+ >
+ )
}
export default MaterialInstance
\ No newline at end of file
diff --git a/app/src/modules/simulation/materials/instances/materialInstances.tsx b/app/src/modules/simulation/materials/instances/materialInstances.tsx
index 519cba9..1864f0f 100644
--- a/app/src/modules/simulation/materials/instances/materialInstances.tsx
+++ b/app/src/modules/simulation/materials/instances/materialInstances.tsx
@@ -1,14 +1,20 @@
-import React from 'react'
+import React, { useEffect } from 'react'
import MaterialInstance from './instance/materialInstance'
-import MaterialAnimator from './animator/materialAnimator'
+import { useMaterialStore } from '../../../../store/simulation/useMaterialStore';
function MaterialInstances() {
+ const { materials } = useMaterialStore();
+
+ useEffect(() => {
+ // console.log('materials: ', materials);
+ }, [materials])
+
return (
<>
-
-
-
+ {materials.map((material: MaterialSchema) =>
+
+ )}
>
)
diff --git a/app/src/modules/simulation/products/temp.md b/app/src/modules/simulation/products/temp.md
deleted file mode 100644
index e69de29..0000000
diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx
index 06d50a1..ceccd40 100644
--- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx
@@ -1,13 +1,13 @@
-import React, { useEffect, useRef, useState } from "react";
-import { useFrame } from "@react-three/fiber";
-import * as THREE from "three";
-import { Line } from "@react-three/drei";
+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";
+ useResetButtonStore
+} from '../../../../../store/usePlayButtonStore';
function RoboticArmAnimator({
HandleCallback,
@@ -16,24 +16,21 @@ function RoboticArmAnimator({
targetBone,
armBot,
logStatus,
- path,
+ 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<
- THREE.Vector3[] | null
- >(null);
-
+ const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
+ const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]);
+ const [customCurvePoints, setCustomCurvePoints] = useState(null);
+ let curveHeight = 1.75
+ const totalDistanceRef = useRef(0);
+ const startTimeRef = useRef(null);
+ const segmentDistancesRef = useRef([]);
// Zustand stores
const { isPlaying } = usePlayButtonStore();
const { isPaused } = usePauseButtonStore();
- const { isReset } = useResetButtonStore();
+ const { isReset, setReset } = useResetButtonStore();
const { speed } = useAnimationPlaySpeed();
// Update path state whenever `path` prop changes
@@ -41,20 +38,26 @@ function RoboticArmAnimator({
setCurrentPath(path);
}, [path]);
- // Reset logic when `isPlaying` changes
- useEffect(() => {
- if (!isPlaying) {
- setCurrentPath([]);
- curveRef.current = null;
- }
- }, [isPlaying]);
-
// Handle circle points based on armBot position
useEffect(() => {
- const points = generateRingPoints(1.6, 64);
+ const points = generateRingPoints(1.6, 64)
setCirclePoints(points);
}, [armBot.position]);
+ useEffect(() => {
+ if (isReset || !isPlaying) {
+ progressRef.current = 0;
+ curveRef.current = null;
+ setCurrentPath([]);
+ setCustomCurvePoints(null);
+ totalDistanceRef.current = 0;
+ startTimeRef.current = null;
+ segmentDistancesRef.current = [];
+ setReset(false);
+ }
+ }, [isReset, isPlaying])
+
+
function generateRingPoints(radius: any, segments: any) {
const points: [number, number, number][] = [];
for (let i = 0; i < segments; i++) {
@@ -68,11 +71,7 @@ function RoboticArmAnimator({
return points;
}
- const findNearestIndex = (
- nearestPoint: [number, number, number],
- points: [number, number, number][],
- epsilon = 1e-6
- ) => {
+ 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 (
@@ -89,168 +88,150 @@ function RoboticArmAnimator({
// 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 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]
- );
+ 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 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 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
- );
+ arcPoints = circlePoints.slice(indexOfNearestStart, indexOfNearestEnd + 1);
} else {
- // Wrap around
arcPoints = [
...circlePoints.slice(indexOfNearestStart, 64),
- ...circlePoints.slice(0, indexOfNearestEnd + 1),
+ ...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 {
+ 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
+ new THREE.Vector3(start[0], start[1], start[2]),
+ new THREE.Vector3(start[0], curveHeight, start[2]),
+ new THREE.Vector3(nearestToStart[0], curveHeight, nearestToStart[2]),
+ ...arcPoints.map(point => new THREE.Vector3(point[0], curveHeight, point[2])),
+ new THREE.Vector3(nearestToEnd[0], curveHeight, nearestToEnd[2]),
+ new THREE.Vector3(end[0], curveHeight, end[2]),
+ new THREE.Vector3(end[0], end[1], end[2])
];
- const customCurve = new THREE.CatmullRomCurve3(
- pathVectors,
- false,
- "centripetal",
- 1
- );
- const generatedPoints = customCurve.getPoints(100);
- setCustomCurvePoints(generatedPoints);
+
+ const pathSegments: [THREE.Vector3, THREE.Vector3][] = [];
+
+ for (let i = 0; i < pathVectors.length - 1; i++) {
+ pathSegments.push([pathVectors[i], pathVectors[i + 1]]);
+ }
+
+ const segmentDistances = pathSegments.map(([p1, p2]) => p1.distanceTo(p2));
+ segmentDistancesRef.current = segmentDistances;
+ const totalDistance = segmentDistances.reduce((sum, d) => sum + d, 0);
+ totalDistanceRef.current = totalDistance;
+
+ const movementSpeed = speed * armBot.speed;
+ const totalMoveTime = totalDistance / movementSpeed;
+
+ const segmentTimes = segmentDistances.map(distance => (distance / totalDistance) * totalMoveTime);
+
+ setCustomCurvePoints(pathVectors);
+
}
}, [circlePoints, currentPath]);
// Frame update for animation
- useFrame((_, delta) => {
+ useFrame((state, delta) => {
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;
if (isPlaying) {
- if (!isPaused && customCurvePoints && currentPath.length > 0) {
- const curvePoints = customCurvePoints;
- const speedAdjustedProgress =
- progressRef.current + speed * armBot.speed;
- const index = Math.floor(speedAdjustedProgress);
+ if (!isPaused && customCurvePoints && customCurvePoints.length > 0) {
+ const distances = segmentDistancesRef.current; // distances between each pair of points
+ const totalDistance = totalDistanceRef.current;
- if (index >= curvePoints.length) {
- // Reached the end of the curve
+ progressRef.current += delta * (speed * armBot.speed);
+ const coveredDistance = progressRef.current;
+
+ let index = 0;
+ let accumulatedDistance = 0;
+
+ // Find which segment we are currently in
+ while (index < distances.length && coveredDistance > accumulatedDistance + distances[index]) {
+ accumulatedDistance += distances[index];
+ index++;
+ }
+ if (index < distances.length) {
+ const startPoint = customCurvePoints[index];
+ const endPoint = customCurvePoints[index + 1];
+ const segmentDistance = distances[index];
+ const t = (coveredDistance - accumulatedDistance) / segmentDistance;
+ if (startPoint && endPoint) {
+ const position = startPoint.clone().lerp(endPoint, t);
+ bone.position.copy(position);
+ }
+ }
+ if (progressRef.current >= totalDistance) {
HandleCallback();
setCurrentPath([]);
+ setCustomCurvePoints([]);
curveRef.current = null;
progressRef.current = 0;
- } else {
- const point = curvePoints[index];
- bone.position.copy(point);
- progressRef.current = speedAdjustedProgress;
+ startTimeRef.current = null;
}
- } else if (isPaused) {
- logStatus(armBot.modelUuid, "Simulation Paused");
- }
- ikSolver.update();
- } else if (!isPlaying && currentPath.length === 0) {
- // Not playing anymore, reset to rest
+ ikSolver.update();
+ }
+ } else if ((!isPlaying && currentPath.length === 0) || isReset) {
bone.position.copy(restPosition);
ikSolver.update();
}
+
});
return (
<>
- {customCurvePoints && currentPath && isPlaying && (
+ {customCurvePoints && customCurvePoints?.length >= 2 && currentPath && isPlaying && (
[p.x, p.y, p.z] as [number, number, number]
- )}
+ points={customCurvePoints.map((p) => [p.x, p.y, p.z] as [number, number, number])}
color="green"
lineWidth={5}
dashed={false}
/>
)}
-
+
@@ -258,4 +239,4 @@ function RoboticArmAnimator({
);
}
-export default RoboticArmAnimator;
+export default RoboticArmAnimator;
\ No newline at end of file
diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
index 45cae97..60d4c33 100644
--- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
@@ -51,13 +51,12 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
pauseTimeRef.current = null;
}
const elapsedTime = performance.now() - startTime;
- if (elapsedTime < 1500) {
+ if (elapsedTime < 1000) {
// Wait until 1500ms has passed
requestAnimationFrame(step);
return;
}
if (currentPhase === "picking") {
-
setArmBotActive(armBot.modelUuid, true);
setArmBotState(armBot.modelUuid, "running");
setCurrentPhase("start-to-end");
@@ -75,7 +74,6 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
}
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");
@@ -91,35 +89,26 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
}
logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
}
-
}
useEffect(() => {
isPausedRef.current = isPaused;
}, [isPaused]);
useEffect(() => {
- 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) {
-
+ if (isReset || !isPlaying) {
logStatus(armBot.modelUuid, "Simulation Play Reset Successfully")
removeCurrentAction(armBot.modelUuid)
- setArmBotActive(armBot.modelUuid, true)
- setArmBotState(armBot.modelUuid, "running")
- setCurrentPhase("init-to-rest");
+ setArmBotActive(armBot.modelUuid, false)
+ setArmBotState(armBot.modelUuid, "idle")
+ setCurrentPhase("init");
isPausedRef.current = false
pauseTimeRef.current = null
- isPausedRef.current = false
startTime = 0
+ const targetBones = ikSolver?.mesh.skeleton.bones.find(
+ (b: any) => b.name === targetBone
+ );
if (targetBones) {
- let curve = createCurveBetweenTwoPoints(targetBones.position, targetBones.position)
+ let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition)
if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z]));
}
@@ -127,6 +116,16 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
setReset(false);
logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.")
}
+ }, [isReset, isPlaying])
+
+ useEffect(() => {
+ 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 (isPlaying) {
//Moving armBot from initial point to rest position.
@@ -136,7 +135,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
setArmBotState(armBot.modelUuid, "running")
setCurrentPhase("init-to-rest");
if (targetBones) {
- let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition)
+ let curve = createCurveBetweenTwoPoints(targetBones.position, targetBones.position)
if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z]));
}
@@ -157,10 +156,9 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
setArmBotActive(armBot.modelUuid, true);
setArmBotState(armBot.modelUuid, "running");
setCurrentPhase("rest-to-start");
- let actiondata = getActionByUuid(selectedProduct.productId, selectedAction.actionId)
const startPoint = armBot.point.actions[0].process.startPoint;
if (startPoint) {
- let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]));
+ let curve = createCurveBetweenTwoPoints(targetBones.position, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]));
if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z]));
}
@@ -206,16 +204,19 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
// logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
}
} else {
- logStatus(armBot.modelUuid, "Simulation Play Stopped")
+ logStatus(armBot.modelUuid, "Simulation Play Exited")
setArmBotActive(armBot.modelUuid, false)
setArmBotState(armBot.modelUuid, "idle")
setCurrentPhase("init");
setPath([])
+ isPausedRef.current = false
+ pauseTimeRef.current = null
+ isPausedRef.current = false
+ startTime = 0
removeCurrentAction(armBot.modelUuid)
-
}
- }, [currentPhase, armBot, isPlaying, ikSolver, isReset])
+ }, [currentPhase, armBot, isPlaying, ikSolver])
function createCurveBetweenTwoPoints(p1: any, p2: any) {
@@ -258,6 +259,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
}
}
const logStatus = (id: string, status: string) => {
+
//
}
diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx
index 4a8947b..c090a92 100644
--- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx
@@ -70,7 +70,7 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKIns
setSelectedArm(OOI.Target_Bone);
- // scene.add(helper);
+ scene.add(helper);
}, [cloned, gltf, setIkSolver]);
diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx
index efacd53..7fe3c50 100644
--- a/app/src/modules/simulation/simulation.tsx
+++ b/app/src/modules/simulation/simulation.tsx
@@ -23,7 +23,7 @@ function Simulation() {
}, [events])
useEffect(() => {
- console.log('products: ', products);
+ // console.log('products: ', products);
}, [products])
return (
diff --git a/app/src/modules/simulation/simulator/simulator.tsx b/app/src/modules/simulation/simulator/simulator.tsx
index c4f1c40..66a1dc3 100644
--- a/app/src/modules/simulation/simulator/simulator.tsx
+++ b/app/src/modules/simulation/simulator/simulator.tsx
@@ -1,18 +1,238 @@
-import { useEffect } from 'react'
-import { useProductStore } from '../../../store/simulation/useProductStore'
+import { useProductStore } from '../../../store/simulation/useProductStore';
+import { useActionHandler } from '../actions/useActionHandler';
function Simulator() {
const { products } = useProductStore();
- useEffect(() => {
- // console.log('products: ', products);
- }, [products])
+ const executionOrder = determineExecutionOrder(products);
- return (
- <>
+ executionOrder.forEach(point => {
+ useActionHandler(point);
+ });
- >
- )
+ function determineExecutionSequences(products: productsSchema): PointsScheme[][] {
+ // Create maps for all points
+ const pointMap = new Map();
+ const allPoints: PointsScheme[] = [];
+
+ // First pass: collect all points
+ products.forEach(product => {
+ product.eventDatas.forEach(event => {
+ if (event.type === 'transfer') {
+ event.points.forEach(point => {
+ pointMap.set(point.uuid, point);
+ allPoints.push(point);
+ });
+ } else if (event.type === 'vehicle' ||
+ event.type === 'machine' ||
+ event.type === 'storageUnit' ||
+ event.type === 'roboticArm') {
+ pointMap.set(event.point.uuid, event.point);
+ allPoints.push(event.point);
+ }
+ });
+ });
+
+ // Build complete dependency graph
+ const dependencyGraph = new Map();
+ const reverseDependencyGraph = new Map();
+ const triggeredPoints = new Set();
+
+ allPoints.forEach(point => {
+ const triggers = extractTriggersFromPoint(point);
+ const dependencies: string[] = [];
+
+ triggers.forEach(trigger => {
+ const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid;
+ if (targetUuid && pointMap.has(targetUuid)) {
+ dependencies.push(targetUuid);
+ triggeredPoints.add(targetUuid);
+
+ if (!reverseDependencyGraph.has(targetUuid)) {
+ reverseDependencyGraph.set(targetUuid, []);
+ }
+ reverseDependencyGraph.get(targetUuid)!.push(point.uuid);
+ }
+ });
+
+ dependencyGraph.set(point.uuid, dependencies);
+ });
+
+ // Identify independent root points (points that trigger others but aren't triggered themselves)
+ const rootPoints = allPoints.filter(point => {
+ const hasOutgoingTriggers = extractTriggersFromPoint(point).some(
+ t => t.triggeredAsset?.triggeredPoint?.pointUuid
+ );
+ return hasOutgoingTriggers && !triggeredPoints.has(point.uuid);
+ });
+
+ // For each root point, build its complete trigger chain
+ const executionSequences: PointsScheme[][] = [];
+
+ function buildSequence(startUuid: string): PointsScheme[] {
+ const sequence: PointsScheme[] = [];
+ const visited = new Set();
+
+ function traverse(uuid: string) {
+ if (visited.has(uuid)) return;
+ visited.add(uuid);
+
+ const point = pointMap.get(uuid);
+ if (point) {
+ sequence.push(point);
+ }
+
+ // Follow forward dependencies
+ const nextPoints = dependencyGraph.get(uuid) || [];
+ nextPoints.forEach(nextUuid => traverse(nextUuid));
+ }
+
+ traverse(startUuid);
+ return sequence;
+ }
+
+ // Build sequences for all root points
+ rootPoints.forEach(root => {
+ executionSequences.push(buildSequence(root.uuid));
+ });
+
+ // Handle any triggered points not reachable from roots (isolated chains)
+ const processedPoints = new Set(
+ executionSequences.flat().map(p => p.uuid)
+ );
+
+ allPoints.forEach(point => {
+ if (triggeredPoints.has(point.uuid) && !processedPoints.has(point.uuid)) {
+ executionSequences.push(buildSequence(point.uuid));
+ }
+ });
+
+ return executionSequences;
+ }
+
+ function determineExecutionOrder(products: productsSchema): PointsScheme[] {
+ // Create maps for all events and points
+ const eventMap = new Map();
+ const pointMap = new Map();
+ const allPoints: PointsScheme[] = [];
+
+ // First pass: collect all points
+ products.forEach(product => {
+ product.eventDatas.forEach(event => {
+ eventMap.set(event.modelUuid, event);
+
+ if (event.type === 'transfer') {
+ event.points.forEach(point => {
+ pointMap.set(point.uuid, point);
+ allPoints.push(point);
+ });
+ } else if (event.type === 'vehicle' ||
+ event.type === 'machine' ||
+ event.type === 'storageUnit') {
+ pointMap.set(event.point.uuid, event.point);
+ allPoints.push(event.point);
+ } else if (event.type === 'roboticArm') {
+ pointMap.set(event.point.uuid, event.point);
+ allPoints.push(event.point);
+ }
+ });
+ });
+
+ // Build dependency graphs
+ const graph = new Map();
+ const reverseGraph = new Map();
+ const allTriggeredPoints = new Set();
+
+ allPoints.forEach(point => {
+ const triggers = extractTriggersFromPoint(point);
+ const dependencies: string[] = [];
+
+ triggers.forEach(trigger => {
+ const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid;
+ if (targetUuid && pointMap.has(targetUuid)) {
+ dependencies.push(targetUuid);
+ allTriggeredPoints.add(targetUuid);
+
+ if (!reverseGraph.has(targetUuid)) {
+ reverseGraph.set(targetUuid, []);
+ }
+ reverseGraph.get(targetUuid)!.push(point.uuid);
+ }
+ });
+
+ graph.set(point.uuid, dependencies);
+ });
+
+ // Identify root points (points that trigger others but aren't triggered themselves)
+ const rootPoints = allPoints
+ .filter(point => !allTriggeredPoints.has(point.uuid))
+ .filter(point => {
+ // Only include roots that actually have triggers pointing FROM them
+ const triggers = extractTriggersFromPoint(point);
+ return triggers.some(t => t.triggeredAsset?.triggeredPoint?.pointUuid);
+ });
+
+ // If no root points found but we have triggered points, find the earliest triggers
+ if (rootPoints.length === 0 && allTriggeredPoints.size > 0) {
+ // This handles cases where we have circular dependencies
+ // but still want to include the triggered points
+ const minTriggerCount = Math.min(
+ ...Array.from(allTriggeredPoints)
+ .map(uuid => (graph.get(uuid) || []).length)
+ );
+ const potentialRoots = Array.from(allTriggeredPoints)
+ .filter(uuid => (graph.get(uuid) || []).length === minTriggerCount);
+
+ rootPoints.push(...potentialRoots.map(uuid => pointMap.get(uuid)!));
+ }
+
+ // Topological sort only for triggered points
+ const visited = new Set();
+ const temp = new Set();
+ const order: string[] = [];
+ let hasCycle = false;
+
+ function visit(node: string) {
+ if (temp.has(node)) {
+ hasCycle = true;
+ return;
+ }
+ if (visited.has(node)) return;
+
+ temp.add(node);
+
+ const dependencies = reverseGraph.get(node) || [];
+ for (const dep of dependencies) {
+ visit(dep);
+ }
+
+ temp.delete(node);
+ visited.add(node);
+ order.push(node);
+ }
+
+ // Start processing from root points
+ rootPoints.forEach(root => visit(root.uuid));
+
+ // Convert UUIDs back to points and filter out untriggered points
+ const triggeredPoints = order
+ .map(uuid => pointMap.get(uuid)!)
+ .filter(point => allTriggeredPoints.has(point.uuid) ||
+ rootPoints.some(root => root.uuid === point.uuid));
+
+ return triggeredPoints;
+ }
+
+ function extractTriggersFromPoint(point: PointsScheme): TriggerSchema[] {
+ if ('actions' in point) {
+ return point.actions.flatMap(action => action.triggers);
+ } else if ('action' in point) {
+ return point.action.triggers;
+ }
+ return [];
+ }
+
+ return <>>;
}
-export default Simulator
\ No newline at end of file
+export default Simulator;
\ No newline at end of file
diff --git a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts
index e450bfd..ab89507 100644
--- a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts
+++ b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts
@@ -1,10 +1,19 @@
import { useRef, useState } from "react";
import * as THREE from "three";
import { ThreeEvent, useThree } from "@react-three/fiber";
+import { useProductStore } from "../../../../store/simulation/useProductStore";
+import {
+ useSelectedEventData,
+ useSelectedProduct,
+} from "../../../../store/simulation/useSimulationStore";
type OnUpdateCallback = (object: THREE.Object3D) => void;
export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
+ const { getEventByModelUuid, updateAction, getActionByUuid } =
+ useProductStore();
+ const { selectedEventData } = useSelectedEventData();
+ const { selectedProduct } = useSelectedProduct();
const { camera, gl, controls } = useThree();
const activeObjRef = useRef(null);
const planeRef = useRef(
@@ -34,7 +43,6 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
activeObjRef.current = obj;
initialPositionRef.current.copy(obj.position);
-
// Get world position
setObjectWorldPos(obj.getWorldPosition(objectWorldPos));
@@ -62,57 +70,84 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
const handlePointerMove = (e: PointerEvent) => {
if (!activeObjRef.current) return;
+ if (selectedEventData?.data.type === "roboticArm") {
+ const selectedArmBot = getEventByModelUuid(
+ selectedProduct.productId,
+ selectedEventData.data.modelUuid
+ );
+ if (!selectedArmBot) return;
+ // Check if Shift key is pressed
+ const isShiftKeyPressed = e.shiftKey;
- // 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;
- // 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);
- // Update raycaster to point to the mouse position
- raycaster.setFromCamera(pointer, camera);
+ // Create a vector to store intersection point
+ const intersection = new THREE.Vector3();
+ const intersects = raycaster.ray.intersectPlane(
+ planeRef.current,
+ intersection
+ );
+ if (!intersects) return;
- // Create a vector to store intersection point
- const intersection = new THREE.Vector3();
- const intersects = raycaster.ray.intersectPlane(
- planeRef.current,
- intersection
- );
- if (!intersects) return;
+ // Add offset for dragging
+ intersection.add(offsetRef.current);
- // Add offset for dragging
- intersection.add(offsetRef.current);
+ // Get the parent's world matrix if exists
+ const parent = activeObjRef.current.parent;
+ const targetPosition = new THREE.Vector3();
- // Get the parent's world matrix if exists
- const parent = activeObjRef.current.parent;
- const targetPosition = new THREE.Vector3();
+ // OnPointerDown
+ initialPositionRef.current.copy(objectWorldPos);
- // OnPointerDown
- initialPositionRef.current.copy(objectWorldPos);
+ // OnPointerMove
+ if (isShiftKeyPressed) {
+ const { x: initialX, y: initialY } = initialPositionRef.current;
+ const { x: objectX, z: objectZ } = objectWorldPos;
- // OnPointerMove
- if (isShiftKeyPressed) {
- const { x: initialX, y: initialY } = initialPositionRef.current;
- const { x: objectX, z: objectZ } = objectWorldPos;
+ const deltaX = intersection.x - initialX;
- const deltaX = intersection.x - initialX;
+ targetPosition.set(objectX, initialY + deltaX, objectZ);
+ } else {
+ // For free movement
+ targetPosition.copy(intersection);
+ }
- targetPosition.set(objectX, initialY + deltaX, objectZ);
- } else {
- // For free movement
- targetPosition.copy(intersection);
+ // CONSTRAIN MOVEMENT HERE:
+ const centerX = selectedArmBot.position[0];
+ const centerZ = selectedArmBot.position[2];
+ const minDistance = 1.2; // 1.4 radius
+ const maxDistance = 2; // 2 radius
+
+ const deltaX = targetPosition.x - centerX;
+ const deltaZ = targetPosition.z - centerZ;
+ const distance = Math.sqrt(deltaX * deltaX + deltaZ * deltaZ);
+
+ if (distance < minDistance || distance > maxDistance) {
+ const angle = Math.atan2(deltaZ, deltaX);
+ const clampedDistance = Math.min(
+ Math.max(distance, minDistance),
+ maxDistance
+ );
+
+ targetPosition.x = centerX + Math.cos(angle) * clampedDistance;
+ targetPosition.z = centerZ + Math.sin(angle) * clampedDistance;
+ }
+ targetPosition.y = Math.min(Math.max(targetPosition.y, 0.6), 1.5);
+ // 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);
}
-
- // Convert world position to local if object is nested inside a parent
- if (parent) {
- parent.worldToLocal(targetPosition);
- }
-
- // Update object position
-
- activeObjRef.current.position.copy(targetPosition);
};
const handlePointerUp = () => {
diff --git a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx
index fcc840d..7b29e2f 100644
--- a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx
+++ b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx
@@ -9,8 +9,8 @@ function VehicleInstances() {
return (
<>
- {vehicles.map((val: VehicleStatus) =>
-
+ {vehicles.map((vehicle: VehicleStatus) =>
+
)}
>
diff --git a/app/src/store/simulation/useMaterialStore.ts b/app/src/store/simulation/useMaterialStore.ts
index 56a35a7..ba75460 100644
--- a/app/src/store/simulation/useMaterialStore.ts
+++ b/app/src/store/simulation/useMaterialStore.ts
@@ -4,16 +4,39 @@ import { immer } from 'zustand/middleware/immer';
type MaterialsStore = {
materials: MaterialsSchema;
- addMaterial: (material: MaterialSchema) => void;
- removeMaterial: (materialId: string) => void;
- updateMaterial: (materialId: string, updates: Partial) => void;
+ addMaterial: (material: MaterialSchema) => MaterialSchema | undefined;
+ removeMaterial: (materialId: string) => MaterialSchema | undefined;
+ updateMaterial: (materialId: string, updates: Partial) => MaterialSchema | undefined;
- setStartTime: (materialId: string, startTime: string) => void;
- setEndTime: (materialId: string, endTime: string) => void;
- setCost: (materialId: string, cost: number) => void;
- setWeight: (materialId: string, weight: number) => void;
+ setCurrentLocation: (
+ materialId: string,
+ location: {
+ modelUuid: string;
+ pointUuid: string;
+ actionUuid: string;
+ }
+ ) => MaterialSchema | undefined;
+
+ setNextLocation: (
+ materialId: string,
+ location?: {
+ modelUuid: string;
+ pointUuid: string;
+ actionUuid: string;
+ } | null
+ ) => MaterialSchema | undefined;
+
+ setStartTime: (materialId: string, startTime: string) => MaterialSchema | undefined;
+ setEndTime: (materialId: string, endTime: string) => MaterialSchema | undefined;
+ setCost: (materialId: string, cost: number) => MaterialSchema | undefined;
+ setWeight: (materialId: string, weight: number) => MaterialSchema | undefined;
+ setIsActive: (materialId: string, isActive: boolean) => MaterialSchema | undefined;
+ setIsVisible: (materialId: string, isVisible: boolean) => MaterialSchema | undefined;
+ setIsRendered: (materialId: string, isRendered: boolean) => MaterialSchema | undefined;
getMaterialById: (materialId: string) => MaterialSchema | undefined;
+ getMaterialsByPoint: (pointUuid: string) => MaterialSchema[];
+ getMaterialsByModel: (modelUuid: string) => MaterialSchema[];
};
export const useMaterialStore = create()(
@@ -21,56 +44,161 @@ export const useMaterialStore = create()(
materials: [],
addMaterial: (material) => {
+ let updatedMaterial: MaterialSchema | undefined;
set((state) => {
state.materials.push(material);
});
+ return updatedMaterial;
},
removeMaterial: (materialId) => {
+ let updatedMaterial: MaterialSchema | undefined;
set((state) => {
- state.materials = state.materials.filter(m => m.materialId !== materialId);
+ const material = state.materials.find(m => m.materialId === materialId);
+ if (material) {
+ state.materials.filter(m => m.materialId !== material.materialId);
+ updatedMaterial = JSON.parse(JSON.stringify(material));
+ }
});
+ return updatedMaterial;
},
updateMaterial: (materialId, updates) => {
+ let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
if (material) {
Object.assign(material, updates);
+ updatedMaterial = JSON.parse(JSON.stringify(material));
}
});
+ return updatedMaterial;
+ },
+
+ setCurrentLocation: (materialId, location) => {
+ let updatedMaterial: MaterialSchema | undefined;
+ set((state) => {
+ const material = state.materials.find(m => m.materialId === materialId);
+ if (material) {
+ material.current = location;
+ updatedMaterial = JSON.parse(JSON.stringify(material));
+ }
+ });
+ return updatedMaterial;
+ },
+
+ setNextLocation: (materialId, location) => {
+ let updatedMaterial: MaterialSchema | undefined;
+ set((state) => {
+ const material = state.materials.find(m => m.materialId === materialId);
+ if (material) {
+ material.next = location || undefined;
+ updatedMaterial = JSON.parse(JSON.stringify(material));
+ }
+ });
+ return updatedMaterial;
},
setStartTime: (materialId, startTime) => {
+ let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
- if (material) material.startTime = startTime;
+ if (material) {
+ material.startTime = startTime
+ updatedMaterial = JSON.parse(JSON.stringify(material));
+ };
});
+ return updatedMaterial;
},
setEndTime: (materialId, endTime) => {
+ let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
- if (material) material.endTime = endTime;
+ if (material) {
+ material.endTime = endTime;
+ updatedMaterial = JSON.parse(JSON.stringify(material));
+ };
});
+ return updatedMaterial;
},
setCost: (materialId, cost) => {
+ let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
- if (material) material.cost = cost;
+ if (material) {
+ material.cost = cost;
+ updatedMaterial = JSON.parse(JSON.stringify(material));
+ };
});
+ return updatedMaterial;
},
setWeight: (materialId, weight) => {
+ let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
- if (material) material.weight = weight;
+ if (material) {
+ material.weight = weight;
+ updatedMaterial = JSON.parse(JSON.stringify(material));
+ };
});
+ return updatedMaterial;
+ },
+
+ setIsActive: (materialId, isActive) => {
+ let updatedMaterial: MaterialSchema | undefined;
+ set((state) => {
+ const material = state.materials.find(m => m.materialId === materialId);
+ if (material) {
+ material.isActive = isActive;
+ updatedMaterial = JSON.parse(JSON.stringify(material));
+ };
+ });
+ return updatedMaterial;
+ },
+
+ setIsVisible: (materialId, isVisible) => {
+ let updatedMaterial: MaterialSchema | undefined;
+ set((state) => {
+ const material = state.materials.find(m => m.materialId === materialId);
+ if (material) {
+ material.isVisible = isVisible;
+ updatedMaterial = JSON.parse(JSON.stringify(material));
+ };
+ });
+ return updatedMaterial;
+ },
+
+ setIsRendered: (materialId, isRendered) => {
+ let updatedMaterial: MaterialSchema | undefined;
+ set((state) => {
+ const material = state.materials.find(m => m.materialId === materialId);
+ if (material) {
+ material.isRendered = isRendered;
+ updatedMaterial = JSON.parse(JSON.stringify(material));
+ };
+ });
+ return updatedMaterial;
},
getMaterialById: (materialId) => {
return get().materials.find(m => m.materialId === materialId);
},
+
+ getMaterialsByPoint: (pointUuid) => {
+ return get().materials.filter(m =>
+ m.current?.pointUuid === pointUuid ||
+ m.next?.pointUuid === pointUuid
+ );
+ },
+
+ getMaterialsByModel: (modelUuid) => {
+ return get().materials.filter(m =>
+ m.current?.modelUuid === modelUuid ||
+ m.next?.modelUuid === modelUuid
+ );
+ },
}))
-);
+);
\ No newline at end of file
diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts
index c0be958..c24ec63 100644
--- a/app/src/store/simulation/useProductStore.ts
+++ b/app/src/store/simulation/useProductStore.ts
@@ -63,6 +63,7 @@ type ProductsStore = {
getEventByModelUuid: (productId: string, modelUuid: string) => EventsSchema | undefined;
getPointByUuid: (productId: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | undefined;
getActionByUuid: (productId: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined;
+ getModelUuidByActionUuid: (productId: string, actionUuid: string) => (string) | undefined;
getTriggerByUuid: (productId: string, triggerUuid: string) => TriggerSchema | undefined;
getIsEventInProduct: (productId: string, modelUuid: string) => boolean;
};
@@ -573,6 +574,30 @@ export const useProductStore = create()(
return undefined;
},
+ getModelUuidByActionUuid: (productId, actionUuid) => {
+ const product = get().products.find(p => p.productId === productId);
+ if (!product) return undefined;
+
+ for (const event of product.eventDatas) {
+ if ('points' in event) {
+ for (const point of (event as ConveyorEventSchema).points) {
+ if (point.action?.actionUuid === actionUuid) {
+ return event.modelUuid;
+ }
+ }
+ } else if ('point' in event) {
+ const point = (event as any).point;
+ if ('action' in point && point.action?.actionUuid === actionUuid) {
+ return event.modelUuid;
+ } else if ('actions' in point) {
+ const action = point.actions.find((a: any) => a.actionUuid === actionUuid);
+ if (action) return event.modelUuid;
+ }
+ }
+ }
+ return undefined;
+ },
+
getTriggerByUuid: (productId, triggerUuid) => {
const product = get().products.find(p => p.productId === productId);
if (!product) return undefined;
diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts
index 11d5156..a33056e 100644
--- a/app/src/types/simulationTypes.d.ts
+++ b/app/src/types/simulationTypes.d.ts
@@ -27,9 +27,9 @@ interface ConveyorPointSchema {
actionName: string;
actionType: "default" | "spawn" | "swap" | "delay" | "despawn";
material: string;
- delay: number | "inherit";
- spawnInterval: number | "inherit";
- spawnCount: number | "inherit";
+ delay: number;
+ spawnInterval: number;
+ spawnCount: number;
triggers: TriggerSchema[];
};
}
@@ -180,13 +180,27 @@ interface StorageUnitStatus extends StorageEventSchema {
interface MaterialSchema {
materialId: string;
- materialName: string;
+ materialName: stri9ng;
materialType: string;
isActive: boolean;
+ isVisible: boolean;
+ isRendered: boolean;
startTime?: string;
endTime?: string;
cost?: number;
weight?: number;
+
+ current: {
+ modelUuid: string;
+ pointUuid: string;
+ actionUuid: string;
+ };
+
+ next?: {
+ modelUuid: string;
+ pointUuid: string;
+ actionUuid: string;
+ };
}
type MaterialsSchema = MaterialSchema[];
\ No newline at end of file