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