diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx index 997d078..3ecbc9d 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx @@ -38,7 +38,7 @@ function RoboticArmMechanics() { } else { clearSelectedAction(); } - }, [selectedAction, selectedEventData, selectedProduct]); + }, [selectedEventData, selectedProduct]); const updateBackend = ( productName: string, diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx index 3b2bb1a..67fd358 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx @@ -9,7 +9,7 @@ import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import { handleResize } from "../../../../../../functions/handleResizePannel"; import { useProductStore } from "../../../../../../store/simulation/useProductStore"; -import { useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; +import { useSelectedAction, useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; type TriggerProps = { @@ -25,6 +25,7 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => { const [selectedTrigger, setSelectedTrigger] = useState(); const [activeOption, setActiveOption] = useState<"onComplete" | "onStart" | "onStop" | "delay" | "onError">("onComplete"); const triggersContainerRef = useRef(null); + const { selectedAction } = useSelectedAction(); const email = localStorage.getItem('email') const organization = (email!.split("@")[1]).split(".")[0]; @@ -36,12 +37,12 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => { if (type === 'Conveyor' || type === 'Vehicle' || type === 'Machine' || type === 'StorageUnit') { actionUuid = (selectedPointData as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema).action?.actionUuid; - } else if (type === 'RoboticArm') { - actionUuid = (selectedPointData as RoboticArmPointSchema).actions[0]?.actionUuid; + } else if (type === 'RoboticArm' && selectedAction) { + actionUuid = selectedAction.actionId; } setCurrentAction(actionUuid); - }, [selectedPointData, selectedProduct, type]); + }, [selectedPointData, selectedProduct, type, selectedAction]); const updateBackend = ( productName: string, diff --git a/app/src/components/templates/SkeletonUI.tsx b/app/src/components/templates/SkeletonUI.tsx index e2d48f1..76c3fbd 100644 --- a/app/src/components/templates/SkeletonUI.tsx +++ b/app/src/components/templates/SkeletonUI.tsx @@ -7,7 +7,6 @@ interface SkeletonUIProps { // Define the SkeletonUI component const SkeletonUI: React.FC = ({ type }) => { - console.log("type: ", type); // Function to render skeleton content based on 'type' const renderSkeleton = () => { diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index e53e1c8..360d166 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -40,9 +40,9 @@ const SimulationPlayer: React.FC = () => { useEffect(() => { if (isReset) { - setTimeout(()=>{ + setTimeout(() => { setReset(false); - },0) + }, 0) } }, [isReset]) @@ -282,11 +282,10 @@ const SimulationPlayer: React.FC = () => { {index < intervals.length - 1 && (
= ((index + 1) / totalSegments) * 100 + className={`line ${progress >= ((index + 1) / totalSegments) * 100 ? "filled" : "" - }`} + }`} >
)} diff --git a/app/src/modules/simulation/actions/conveyor/actionHandler/useDelayHandler.ts b/app/src/modules/simulation/actions/conveyor/actionHandler/useDelayHandler.ts index f49a816..492ac3d 100644 --- a/app/src/modules/simulation/actions/conveyor/actionHandler/useDelayHandler.ts +++ b/app/src/modules/simulation/actions/conveyor/actionHandler/useDelayHandler.ts @@ -1,22 +1,27 @@ import { useCallback, useEffect, useRef } from "react"; import { useFrame } from "@react-three/fiber"; -import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore } from "../../../../../store/usePlayButtonStore"; +import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore, useAnimationPlaySpeed } from "../../../../../store/usePlayButtonStore"; import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore"; interface DelayInstance { + initialDelay: number; delayEndTime: number; materialId?: string; action: ConveyorAction; isPaused: boolean; remainingTime: number; + lastUpdateTime: number; + elapsedTime: number; } export function useDelayHandler() { const { isPlaying } = usePlayButtonStore(); const { isPaused } = usePauseButtonStore(); const { isReset } = useResetButtonStore(); + const { speed } = useAnimationPlaySpeed(); const { setIsPaused } = useMaterialStore(); const activeDelays = useRef>(new Map()); + const lastUpdateTimeRef = useRef(performance.now()); const cleanupDelay = useCallback(() => { activeDelays.current.clear(); @@ -30,34 +35,33 @@ export function useDelayHandler() { const delayLogStatus = (materialUuid: string, status: string) => { // console.log(`${materialUuid}, ${status}`); - } + }; - useEffect(() => { + useFrame(() => { const currentTime = performance.now(); + const deltaTime = currentTime - lastUpdateTimeRef.current; + lastUpdateTimeRef.current = currentTime; + const completedDelays: string[] = []; - activeDelays.current.forEach((delay) => { + activeDelays.current.forEach((delay, key) => { if (isPaused && !delay.isPaused) { delay.remainingTime = Math.max(0, delay.delayEndTime - currentTime); delay.isPaused = true; } else if (!isPaused && delay.isPaused) { delay.delayEndTime = currentTime + delay.remainingTime; delay.isPaused = false; - delay.remainingTime = 0; - } - }); - }, [isPaused]); + delay.lastUpdateTime = currentTime; + delay.elapsedTime = 0; + } else if (!delay.isPaused && isPlaying) { + delay.elapsedTime += deltaTime * speed; - useFrame(() => { - if (!isPlaying || isPaused) return; - - const currentTime = performance.now(); - const completedDelays: string[] = []; - - activeDelays.current.forEach((delay, key) => { - if (!delay.isPaused && currentTime >= delay.delayEndTime && delay.materialId) { - delayLogStatus(delay.materialId, `Delay completed}`); - setIsPaused(delay.materialId, false); - completedDelays.push(key); + if (delay.elapsedTime >= delay.initialDelay) { + if (delay.materialId) { + delayLogStatus(delay.materialId, `Delay completed`); + setIsPaused(delay.materialId, false); + } + completedDelays.push(key); + } } }); @@ -67,29 +71,31 @@ export function useDelayHandler() { }); const handleDelay = useCallback((action: ConveyorAction, materialId?: string) => { - if (!action || action.actionType !== 'delay' || !isPlaying || !materialId) return; + if (!action || action.actionType !== 'delay' || !materialId) return; const delayMs = (action.delay || 0) * 1000; if (delayMs <= 0) return; const key = materialId ? `${materialId}-${action.actionUuid}` : action.actionUuid; - // If this material already has a delay, cancel it if (activeDelays.current.has(key)) { activeDelays.current.delete(key); } + const now = performance.now(); activeDelays.current.set(key, { - delayEndTime: performance.now() + delayMs, + initialDelay: delayMs, + delayEndTime: now + delayMs, materialId, action, isPaused: false, - remainingTime: 0 + remainingTime: 0, + lastUpdateTime: now, + elapsedTime: 0 }); - delayLogStatus(materialId, `Started ${delayMs * 1000}s delay`); + delayLogStatus(materialId, `Started ${delayMs / 1000}s delay`); setIsPaused(materialId, true); - }, [isPlaying]); useEffect(() => { diff --git a/app/src/modules/simulation/actions/conveyor/actionHandler/useDespawnHandler.ts b/app/src/modules/simulation/actions/conveyor/actionHandler/useDespawnHandler.ts new file mode 100644 index 0000000..464629c --- /dev/null +++ b/app/src/modules/simulation/actions/conveyor/actionHandler/useDespawnHandler.ts @@ -0,0 +1,26 @@ +import { useCallback } from "react"; +import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore"; + +export function useDespawnHandler() { + const { addMaterial, getMaterialById, removeMaterial } = useMaterialStore(); + + const deSpawnLogStatus = (materialUuid: string, status: string) => { + // console.log(`${materialUuid}, ${status}`); + } + + const handleDespawn = useCallback((action: ConveyorAction, materialId?: string) => { + if (!action || action.actionType !== 'despawn' || !materialId) return; + + const material = getMaterialById(materialId); + if (!material) return; + + removeMaterial(material.materialId); + + deSpawnLogStatus(material.materialId, `Despawned`); + + }, [addMaterial, getMaterialById, removeMaterial]); + + return { + handleDespawn, + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts b/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts index e12e538..ab00964 100644 --- a/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts +++ b/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef } from "react"; +import { useCallback, useEffect, useState } from "react"; import * as THREE from 'three'; import { useFrame } from "@react-three/fiber"; import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore"; @@ -32,10 +32,23 @@ export function useSpawnHandler() { const { isReset } = useResetButtonStore(); const { selectedProduct } = useSelectedProduct(); - const activeSpawns = useRef>(new Map()); + const [activeSpawns, setActiveSpawns] = useState>(new Map()); + + const getConveyorPausedState = useCallback((action: ConveyorAction) => { + const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid); + if (!modelUuid) return false; + + const conveyor = getConveyorById(modelUuid); + if (!conveyor) return false; + return conveyor.isPaused; + }, [getConveyorById, getModelUuidByActionUuid, selectedProduct.productId]); + + const shouldPauseSpawn = useCallback((action: ConveyorAction) => { + return isPaused || getConveyorPausedState(action); + }, [isPaused, getConveyorPausedState]); const clearAllSpawns = useCallback(() => { - activeSpawns.current.clear(); + setActiveSpawns(new Map()); }, []); useEffect(() => { @@ -52,6 +65,7 @@ export function useSpawnHandler() { const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid); const pointUuid = getPointUuidByActionUuid(selectedProduct.productId, action.actionUuid); if (!modelUuid || !pointUuid) return; + const currentTime = performance.now(); const newMaterial: MaterialSchema = { materialId: THREE.MathUtils.generateUUID(), @@ -61,13 +75,12 @@ export function useSpawnHandler() { isVisible: true, isPaused: false, isRendered: true, + startTime:currentTime, current: { modelUuid: modelUuid, pointUuid: pointUuid, actionUuid: action.actionUuid }, - weight: 1, - cost: 1 }; if (action.triggers[0].triggeredAsset?.triggeredModel.modelUuid && @@ -84,92 +97,96 @@ export function useSpawnHandler() { return newMaterial; }, [addMaterial, getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct.productId]); - - const getConveyorPausedState = useCallback((action: ConveyorAction) => { - const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid); - if (!modelUuid) return false; - - const conveyor = getConveyorById(modelUuid); - return conveyor?.isPaused ?? false; - }, [getConveyorById, getModelUuidByActionUuid, selectedProduct.productId]); - - useEffect(() => { + useFrame(() => { const currentTime = performance.now(); + const completedActions: string[] = []; + let hasChanges = false; - activeSpawns.current.forEach((spawn) => { - if (isPaused && !spawn.isPaused) { + activeSpawns.forEach(spawn => { + const isPausedNow = shouldPauseSpawn(spawn.params.action); + + if (isPausedNow && !spawn.isPaused) { if (spawn.lastSpawnTime === null) { - spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.startTime)); + spawn.remainingTime = Math.max(0, (spawn.params.intervalMs / speed) - (currentTime - spawn.startTime)); } else { - spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.lastSpawnTime)); + spawn.remainingTime = Math.max(0, (spawn.params.intervalMs / speed) - (currentTime - spawn.lastSpawnTime)); } spawn.pauseStartTime = currentTime; spawn.isPaused = true; - } else if (!isPaused && spawn.isPaused) { - if (spawn.remainingTime > 0) { - if (spawn.lastSpawnTime === null) { - spawn.startTime = currentTime - (spawn.params.intervalMs - spawn.remainingTime); - } else { - spawn.lastSpawnTime = currentTime - (spawn.params.intervalMs - spawn.remainingTime); - } + hasChanges = true; + } else if (!isPausedNow && spawn.isPaused) { + const pauseDuration = currentTime - spawn.pauseStartTime; + if (spawn.lastSpawnTime === null) { + spawn.startTime += pauseDuration; + } else { + spawn.lastSpawnTime += pauseDuration; } spawn.isPaused = false; spawn.pauseStartTime = 0; spawn.remainingTime = 0; + hasChanges = true; } }); - }, [isPaused]); - useFrame(() => { - if (!isPlaying || isPaused || isReset) return; + if (isPlaying && !isReset && !isPaused) { + activeSpawns.forEach((spawn, actionUuid) => { + if (spawn.isPaused) return; - const currentTime = performance.now(); - const completedActions: string[] = []; + const { material, intervalMs, totalCount, action } = spawn.params; + const adjustedInterval = intervalMs / speed; + const isFirstSpawn = spawn.lastSpawnTime === null; - activeSpawns.current.forEach((spawn, actionUuid) => { - const { material, intervalMs, totalCount, action } = spawn.params; - const isFirstSpawn = spawn.lastSpawnTime === null; + // First spawn + if (isFirstSpawn) { + const elapsed = currentTime - spawn.startTime; + if (elapsed >= adjustedInterval) { + const createdMaterial = createNewMaterial(material, action); + if (createdMaterial) { + spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`); + } + spawn.lastSpawnTime = currentTime; + spawn.spawnCount = 1; + hasChanges = true; - // First spawn - if (isFirstSpawn) { - const elapsed = currentTime - spawn.startTime; - if (elapsed >= intervalMs) { - const createdMaterial = createNewMaterial(material, action); - if (createdMaterial) { - spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`); + if (totalCount <= 1) { + completedActions.push(actionUuid); + } } - spawn.lastSpawnTime = currentTime; - spawn.spawnCount = 1; + return; + } - if (totalCount <= 1) { - completedActions.push(actionUuid); + // Subsequent spawns + if (spawn.lastSpawnTime !== null) { + const timeSinceLast = currentTime - spawn.lastSpawnTime; + if (timeSinceLast >= adjustedInterval) { + const count = spawn.spawnCount + 1; + const createdMaterial = createNewMaterial(material, action); + if (createdMaterial) { + spawnLogStatus(createdMaterial.materialId, `[${timeSinceLast.toFixed(2)}ms] Spawned ${material} (${count}/${totalCount})`); + } + spawn.lastSpawnTime = currentTime; + spawn.spawnCount = count; + hasChanges = true; + + if (count >= totalCount) { + completedActions.push(actionUuid); + } } } - return; - } + }); + } - // Subsequent spawns - if (spawn.lastSpawnTime !== null) { - const timeSinceLast = currentTime - spawn.lastSpawnTime; - if (timeSinceLast >= intervalMs) { - const count = spawn.spawnCount + 1; - const createdMaterial = createNewMaterial(material, action); - if (createdMaterial) { - spawnLogStatus(createdMaterial.materialId, `[${timeSinceLast.toFixed(2)}ms] Spawned ${material} (${count}/${totalCount})`); - } - spawn.lastSpawnTime = currentTime; - spawn.spawnCount = count; + if (hasChanges || completedActions.length > 0) { + setActiveSpawns(prevSpawns => { + const newSpawns = new Map(prevSpawns); - if (count >= totalCount) { - completedActions.push(actionUuid); - } - } - } - }); + completedActions.forEach(actionUuid => { + newSpawns.delete(actionUuid); + }); - completedActions.forEach(actionUuid => { - activeSpawns.current.delete(actionUuid); - }); + return newSpawns; + }); + } }); const handleSpawn = useCallback((action: ConveyorAction) => { @@ -178,23 +195,29 @@ export function useSpawnHandler() { const { material, spawnInterval = 0, spawnCount = 1, actionUuid } = action; const intervalMs = spawnInterval * 1000; - if (activeSpawns.current.has(actionUuid)) { - activeSpawns.current.delete(actionUuid); - } + setActiveSpawns(prevSpawns => { + const newSpawns = new Map(prevSpawns); - activeSpawns.current.set(actionUuid, { - lastSpawnTime: null, - startTime: performance.now(), - spawnCount: 0, - params: { - material, - intervalMs, - totalCount: spawnCount, - action: action - }, - pauseStartTime: 0, - remainingTime: 0, - isPaused: false + if (newSpawns.has(actionUuid)) { + newSpawns.delete(actionUuid); + } + + newSpawns.set(actionUuid, { + lastSpawnTime: null, + startTime: performance.now(), + spawnCount: 0, + params: { + material, + intervalMs, + totalCount: spawnCount, + action: action + }, + pauseStartTime: 0, + remainingTime: 0, + isPaused: false + }); + + return newSpawns; }); }, []); diff --git a/app/src/modules/simulation/actions/conveyor/actionHandler/useSwapHandler.ts b/app/src/modules/simulation/actions/conveyor/actionHandler/useSwapHandler.ts index bd3a6fc..68116db 100644 --- a/app/src/modules/simulation/actions/conveyor/actionHandler/useSwapHandler.ts +++ b/app/src/modules/simulation/actions/conveyor/actionHandler/useSwapHandler.ts @@ -1,21 +1,15 @@ import { useCallback } from "react"; import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore"; -import { useProductStore } from "../../../../../store/simulation/useProductStore"; -import { useSelectedProduct } from "../../../../../store/simulation/useSimulationStore"; -import { usePlayButtonStore } from "../../../../../store/usePlayButtonStore"; export function useSwapHandler() { - const { addMaterial, getMaterialById, setMaterial } = useMaterialStore(); - const { getPointUuidByActionUuid } = useProductStore(); - const { selectedProduct } = useSelectedProduct(); - const { isPlaying } = usePlayButtonStore(); + const { getMaterialById, setMaterial } = useMaterialStore(); const swapLogStatus = (materialUuid: string, status: string) => { // console.log(`${materialUuid}, ${status}`); } const handleSwap = useCallback((action: ConveyorAction, materialId?: string) => { - if (!action || action.actionType !== 'swap' || !isPlaying || !materialId) return; + if (!action || action.actionType !== 'swap' || !materialId) return; const { material: newMaterialType } = action; const material = getMaterialById(materialId); @@ -24,7 +18,7 @@ export function useSwapHandler() { setMaterial(material.materialId, newMaterialType); swapLogStatus(material.materialId, `Swapped to ${newMaterialType}`); - }, [addMaterial, getMaterialById, getPointUuidByActionUuid, isPlaying, setMaterial, selectedProduct.productId]); + }, [getMaterialById, setMaterial]); return { handleSwap, diff --git a/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts b/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts index 3846b99..43e3dc1 100644 --- a/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts +++ b/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts @@ -2,10 +2,12 @@ import { useEffect, useCallback } from "react"; import { useSpawnHandler } from "./actionHandler/useSpawnHandler"; import { useSwapHandler } from "./actionHandler/useSwapHandler"; import { useDelayHandler } from "./actionHandler/useDelayHandler"; +import { useDespawnHandler } from "./actionHandler/useDespawnHandler"; export function useConveyorActions() { const { handleSpawn, clearCurrentSpawn } = useSpawnHandler(); const { handleSwap } = useSwapHandler(); + const { handleDespawn } = useDespawnHandler(); const { handleDelay, cleanupDelay } = useDelayHandler(); const handleDefaultAction = useCallback((action: ConveyorAction) => { @@ -23,9 +25,9 @@ export function useConveyorActions() { handleDelay(action, materialId); }, [handleDelay]); - const handleDespawnAction = useCallback((action: ConveyorAction) => { - console.log(`Despawning material`); - }, []); + const handleDespawnAction = useCallback((action: ConveyorAction, materialId?: string) => { + handleDespawn(action, materialId) + }, [handleDespawn]); const handleConveyorAction = useCallback((action: ConveyorAction, materialId?: string) => { if (!action) return; @@ -44,7 +46,7 @@ export function useConveyorActions() { handleDelayAction(action, materialId); break; case 'despawn': - handleDespawnAction(action); + handleDespawnAction(action, materialId); break; default: console.warn(`Unknown conveyor action type: ${action.actionType}`); diff --git a/app/src/modules/simulation/materials/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/materials/instances/animator/materialAnimator.tsx index c78eac7..4afe359 100644 --- a/app/src/modules/simulation/materials/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/materials/instances/animator/materialAnimator.tsx @@ -46,12 +46,24 @@ function MaterialAnimator({ useEffect(() => { if (!isPlaying || !material.next?.pointUuid) { - setIsAnimating(false); + if (material.current.pointUuid) { + const newTarget = getWorldPosition(material.current.pointUuid); + if (newTarget && matRef.current && !material.isPaused) { + animationState.current.startPosition.copy(matRef.current.position); + animationState.current.totalDistance = animationState.current.startPosition.distanceTo(newTarget); + animationState.current.startTime = performance.now() - animationState.current.pausedTime; + animationState.current.pausedTime = 0; + animationState.current.isPaused = false; + setTargetPosition(newTarget); + setIsAnimating(true); + } + } else { + setIsAnimating(false); + } return; } - const newTarget = getWorldPosition(material.next.pointUuid); - if (newTarget && matRef.current) { + if (newTarget && matRef.current && !material.isPaused) { animationState.current.startPosition.copy(matRef.current.position); animationState.current.totalDistance = animationState.current.startPosition.distanceTo(newTarget); animationState.current.startTime = performance.now() - animationState.current.pausedTime; @@ -60,7 +72,7 @@ function MaterialAnimator({ setTargetPosition(newTarget); setIsAnimating(true); } - }, [material.next?.pointUuid, isPlaying]); + }, [material, isPlaying]); useEffect(() => { if (shouldPause) { diff --git a/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx b/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx index 6cbc0f0..a26109c 100644 --- a/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx +++ b/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx @@ -83,14 +83,6 @@ function MaterialInstance({ material }: { material: MaterialSchema }) { } } - useEffect(() => { - // console.log('material: ', material); - if (material.current && material.next) { - // console.log('current: ', material.current.pointUuid); - // console.log('next: ', material.next.pointUuid); - } - }, [material]) - const callTrigger = () => { if (!material.next) return; const fromModel = getEventByModelUuid(selectedProduct.productId, material.next.modelUuid); diff --git a/app/src/modules/simulation/materials/instances/materialInstances.tsx b/app/src/modules/simulation/materials/instances/materialInstances.tsx index 1864f0f..5ad4913 100644 --- a/app/src/modules/simulation/materials/instances/materialInstances.tsx +++ b/app/src/modules/simulation/materials/instances/materialInstances.tsx @@ -3,12 +3,16 @@ import MaterialInstance from './instance/materialInstance' import { useMaterialStore } from '../../../../store/simulation/useMaterialStore'; function MaterialInstances() { - const { materials } = useMaterialStore(); + const { materials, materialHistory } = useMaterialStore(); useEffect(() => { // console.log('materials: ', materials); }, [materials]) + useEffect(() => { + // console.log('materialHistory: ', materialHistory); + }, [materialHistory]) + return ( <> diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx index e1e8fa6..4a4807d 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -8,6 +8,7 @@ import { getAllProductsApi } from '../../../services/simulation/getallProductsAp import { useVehicleStore } from '../../../store/simulation/useVehicleStore'; import { useArmBotStore } from '../../../store/simulation/useArmBotStore'; import { useConveyorStore } from '../../../store/simulation/useConveyorStore'; +import { useResetButtonStore } from '../../../store/usePlayButtonStore'; function Products() { const { products, getProductById, addProduct, setProducts } = useProductStore(); @@ -15,6 +16,7 @@ function Products() { const { addVehicle, clearvehicles } = useVehicleStore(); const { addArmBot, clearArmBots } = useArmBotStore(); const { addConveyor, clearConveyors } = useConveyorStore(); + const { isReset } = useResetButtonStore(); useEffect(() => { const email = localStorage.getItem('email') @@ -45,7 +47,7 @@ function Products() { }); } } - }, [selectedProduct, products]); + }, [selectedProduct, products, isReset]); useEffect(() => { if (selectedProduct.productId) { @@ -59,7 +61,7 @@ function Products() { }); } } - }, [selectedProduct, products]); + }, [selectedProduct, products, isReset]); useEffect(() => { if (selectedProduct.productId) { @@ -73,7 +75,7 @@ function Products() { }); } } - }, [selectedProduct, products]); + }, [selectedProduct, products, isReset]); return ( <> diff --git a/app/src/modules/simulation/roboticArm/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/materialAnimator.tsx index b458d32..7ba6148 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/materialAnimator.tsx @@ -14,7 +14,7 @@ export default function MaterialAnimator({ ikSolver, armBot, currentPhase }: Mat const [isRendered, setIsRendered] = useState(false); useEffect(() => { - if (currentPhase === "start-to-end") { + if (currentPhase === "start-to-end" || currentPhase === "dropping") { setIsRendered(true); } else { setIsRendered(false); @@ -40,7 +40,7 @@ export default function MaterialAnimator({ ikSolver, armBot, currentPhase }: Mat const direction = new THREE.Vector3(); direction.subVectors(boneWorldPos, boneTargetWorldPos).normalize(); const downwardDirection = direction.clone().negate(); - + const adjustedPosition = boneWorldPos.clone().addScaledVector(downwardDirection, -0.01); //set position diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index 3ca7665..be8c561 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -266,7 +266,7 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone return ( <> {customCurvePoints && customCurvePoints?.length >= 2 && currentPath && isPlaying && ( - + [p.x, p.y, p.z] as [number, number, number])} color="green" @@ -278,13 +278,50 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone {/* Green ring */} - + + {/* Markers at 90°, 180°, 270°, 360° */} + {[90, 180, 270, 360].map((degree, index) => { + const rad = ((degree) * Math.PI) / 180; + const x = CIRCLE_RADIUS * Math.cos(rad); + const z = CIRCLE_RADIUS * Math.sin(rad); + const y = 0; // same plane as the ring (Y axis) + + return ( + + + + + ); + })} + + {[90, 180, 270, 360].map((degree, index) => { + const rad = ((degree) * Math.PI) / 180; + const x = CIRCLE_RADIUS * Math.cos(rad); + const z = CIRCLE_RADIUS * Math.sin(rad); + const y = 0.15; // lift the text slightly above the ring + + return ( + + {degree}° + + ); + })} + + diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index 7cd3634..ec044ce 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -1,13 +1,16 @@ import React, { useEffect, useRef, useState } from 'react' +import * as THREE from "three"; +import { useThree } from "@react-three/fiber"; import IKInstance from '../ikInstance/ikInstance'; import RoboticArmAnimator from '../animator/roboticArmAnimator'; -import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; -import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; -import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_1.glb"; -import { useThree } from "@react-three/fiber"; -import * as THREE from "three"; import MaterialAnimator from '../animator/materialAnimator'; - +import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_1.glb"; +import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useMaterialStore } from '../../../../../store/simulation/useMaterialStore'; +import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; +import { useProductStore } from '../../../../../store/simulation/useProductStore'; +import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; +import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { @@ -21,8 +24,12 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { const pauseTimeRef = useRef(null); const isPausedRef = useRef(false); let startTime: number; - //zustand - const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); + + const { setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); + const { setIsVisible } = useMaterialStore(); + const { selectedProduct } = useSelectedProduct(); + const { getActionByUuid } = useProductStore(); + const { triggerPointActions } = useTriggerHandler(); const { isPlaying } = usePlayButtonStore(); const { isReset } = useResetButtonStore(); const { isPaused } = usePauseButtonStore(); @@ -65,6 +72,10 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { if (curve) { logStatus(armBot.modelUuid, "picking the object"); setPath(curve.points.map(point => [point.x, point.y, point.z])) + + if (armBot.currentAction) { + setIsVisible(armBot.currentAction.materialId || '', false); + } } } logStatus(armBot.modelUuid, "Moving armBot from start point to end position.") @@ -80,6 +91,18 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { if (curve) { logStatus(armBot.modelUuid, "dropping the object"); setPath(curve.points.map(point => [point.x, point.y, point.z])); + + if (armBot.currentAction) { + setIsVisible(armBot.currentAction.materialId || '', true); + } + + if (armBot.currentAction) { + const action = getActionByUuid(selectedProduct.productId, armBot.currentAction.actionUuid); + if (action && armBot.currentAction.materialId) { + triggerPointActions(action, armBot.currentAction.materialId) + removeCurrentAction(armBot.modelUuid) + } + } } } logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.") @@ -137,10 +160,6 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { //Waiting for trigger. else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) { logStatus(armBot.modelUuid, "Waiting to trigger CurrentAction") - const timeoutId = setTimeout(() => { - addCurrentAction(armBot.modelUuid, armBot.point.actions[0].actionUuid, 'Material 2'); - }, 3000); - return () => clearTimeout(timeoutId); } //Moving to pickup point else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && armBot.currentAction) { @@ -219,7 +238,6 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("rest"); setPath([]) - removeCurrentAction(armBot.modelUuid) } } const logStatus = (id: string, status: string) => { @@ -228,12 +246,12 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { return ( <> {!isReset && isPlaying && ( - <> + <> - - )} + + )} ) diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts new file mode 100644 index 0000000..fd42547 --- /dev/null +++ b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts @@ -0,0 +1,114 @@ +import { extractTriggersFromPoint } from "./extractTriggersFromPoint"; + +export 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; +} \ No newline at end of file diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts b/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts new file mode 100644 index 0000000..afd5324 --- /dev/null +++ b/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts @@ -0,0 +1,101 @@ +import { extractTriggersFromPoint } from "./extractTriggersFromPoint"; + +export 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; +} \ No newline at end of file diff --git a/app/src/modules/simulation/simulator/functions/extractTriggersFromPoint.ts b/app/src/modules/simulation/simulator/functions/extractTriggersFromPoint.ts new file mode 100644 index 0000000..012bc6b --- /dev/null +++ b/app/src/modules/simulation/simulator/functions/extractTriggersFromPoint.ts @@ -0,0 +1,9 @@ + +export 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 []; +} \ No newline at end of file diff --git a/app/src/modules/simulation/simulator/simulator.tsx b/app/src/modules/simulation/simulator/simulator.tsx index c271286..8c9d8f0 100644 --- a/app/src/modules/simulation/simulator/simulator.tsx +++ b/app/src/modules/simulation/simulator/simulator.tsx @@ -2,6 +2,7 @@ import { useEffect } from 'react'; import { useProductStore } from '../../../store/simulation/useProductStore'; import { useActionHandler } from '../actions/useActionHandler'; import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore'; +import { determineExecutionOrder } from './functions/determineExecutionOrder'; function Simulator() { const { products } = useProductStore(); @@ -11,237 +12,21 @@ function Simulator() { useEffect(() => { if (!isPlaying || isReset) return; - + const executionOrder = determineExecutionOrder(products); executionOrder.map(point => { const action = 'actions' in point ? point.actions[0] : point.action; handleAction(action); }); - }, [products, handleAction, isPlaying, isReset]); + }, [products, isPlaying, isReset]); - function determineExecutionOrder(products: productsSchema): PointsScheme[] { - // Create maps for all events and points - const eventMap = new Map(); - const pointMap = new Map(); - const allPoints: PointsScheme[] = []; + return ( - // 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 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 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 diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 967a23a..c33e2ec 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -3,12 +3,14 @@ import { useActionHandler } from '../../actions/useActionHandler'; import { useProductStore } from '../../../../store/simulation/useProductStore'; import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; import { useMaterialStore } from '../../../../store/simulation/useMaterialStore'; +import { useArmBotStore } from '../../../../store/simulation/useArmBotStore'; export function useTriggerHandler() { - const { getEventByTriggerUuid, getEventByModelUuid } = useProductStore(); const { handleAction } = useActionHandler(); - const { setCurrentLocation, setNextLocation, getMaterialById } = useMaterialStore(); const { selectedProduct } = useSelectedProduct(); + const { getEventByTriggerUuid, getEventByModelUuid, getActionByUuid, getModelUuidByActionUuid } = useProductStore(); + const { addCurrentAction, getArmBotById } = useArmBotStore(); + const { setCurrentLocation, setNextLocation, getMaterialById, setIsPaused, setEndTime } = useMaterialStore(); const handleTrigger = (trigger: TriggerSchema, action: Action, materialId: string) => { @@ -46,7 +48,38 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'roboticArm') { // Transfer to Robotic Arm + if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + if (material.next) { + const armBot = getArmBotById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (armBot) { + if (armBot.isActive === false && armBot.state === 'idle') { + setCurrentLocation(material.materialId, { + modelUuid: material.next.modelUuid, + pointUuid: material.next.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + setNextLocation(material.materialId, null); + + setIsPaused(material.materialId, true); + addCurrentAction( + trigger.triggeredAsset?.triggeredModel.modelUuid, + trigger.triggeredAsset?.triggeredAction?.actionUuid, + material.materialType, + material.materialId + ); + } else { + + // Event Manager Needed + + } + } + } + handleAction(action, materialId); + } + } } else if (toEvent?.type === 'storageUnit') { // Transfer to Storage Unit @@ -88,6 +121,31 @@ export function useTriggerHandler() { } else if (fromEvent?.type === 'roboticArm') { if (toEvent?.type === 'transfer') { // Robotic Arm to Transfer + if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + setIsPaused(material.materialId, false); + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + const action = getActionByUuid(selectedProduct.productId, trigger.triggeredAsset.triggeredAction.actionUuid); + + if (action && action.triggers.length > 0 && + action.triggers[0].triggeredAsset?.triggeredModel.modelUuid && + action.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid) { + setNextLocation(material.materialId, { + modelUuid: action.triggers[0].triggeredAsset?.triggeredModel.modelUuid, + pointUuid: action.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid, + }) + handleAction(action, material.materialId); + } + + } + } } else if (toEvent?.type === 'vehicle') { // Robotic Arm to Vehicle @@ -122,26 +180,75 @@ export function useTriggerHandler() { } } + const handleFinalAction = (action: Action, materialId: string) => { + if (!action) return; + + const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid); + if (!modelUuid) return; + const finalModel = getEventByModelUuid(selectedProduct.productId, modelUuid); + if (!finalModel) return; + const material = getMaterialById(materialId); + + if (finalModel.type === 'transfer') { + // Storage Unit to Transfer + + if (material) { + const currentTime = performance.now(); + + setCurrentLocation(material.materialId, { + modelUuid: material.next?.modelUuid || '', + pointUuid: material.next?.pointUuid || '', + actionUuid: action.actionUuid, + }); + + setNextLocation(material.materialId, null); + + setEndTime(material.materialId, currentTime); + + handleAction(action, material.materialId); + } + + } else if (finalModel.type === 'vehicle') { + // Storage Unit to Vehicle + + } else if (finalModel.type === 'machine') { + // Storage Unit to Machine + + } else if (finalModel.type === 'roboticArm') { + // Storage Unit to Robotic Arm + + } else if (finalModel.type === 'storageUnit') { + // Storage Unit to Storage Unit + + } + + } + const triggerPointActions = useCallback((action: Action, materialId: string) => { if (!action) return; - action.triggers.forEach(trigger => { - switch (trigger.triggerType) { - case 'onStart': - break; - case 'onComplete': - handleTrigger(trigger, action, materialId); - break; - case 'onStop': - break; - case 'onError': - break; - case 'delay': - break; - default: - console.warn(`Unknown trigger type: ${trigger.triggerType}`); - } - }); + if (action.triggers.length > 0) { + + action.triggers.forEach(trigger => { + switch (trigger.triggerType) { + case 'onStart': + break; + case 'onComplete': + handleTrigger(trigger, action, materialId); + break; + case 'onStop': + break; + case 'onError': + break; + case 'delay': + break; + default: + console.warn(`Unknown trigger type: ${trigger.triggerType}`); + } + }); + } else { + handleFinalAction(action, materialId); + } }, []); return { diff --git a/app/src/store/simulation/useArmBotStore.ts b/app/src/store/simulation/useArmBotStore.ts index bd81e61..a112552 100644 --- a/app/src/store/simulation/useArmBotStore.ts +++ b/app/src/store/simulation/useArmBotStore.ts @@ -12,7 +12,7 @@ interface ArmBotStore { ) => void; clearArmBots: () => void; - addCurrentAction: (modelUuid: string, actionUuid: string, materialType: string) => void; + addCurrentAction: (modelUuid: string, actionUuid: string, materialType: string, materialId: string) => void; removeCurrentAction: (modelUuid: string) => void; addAction: (modelUuid: string, action: RoboticArmPointSchema['actions'][number]) => void; @@ -75,7 +75,7 @@ export const useArmBotStore = create()( }); }, - addCurrentAction: (modelUuid, actionUuid, materialType) => { + addCurrentAction: (modelUuid, actionUuid, materialType, materialId) => { set((state) => { const armBot = state.armBots.find(a => a.modelUuid === modelUuid); if (armBot) { @@ -84,7 +84,8 @@ export const useArmBotStore = create()( armBot.currentAction = { actionUuid: action.actionUuid, actionName: action.actionName, - materialType: materialType + materialType: materialType, + materialId:materialId }; } } diff --git a/app/src/store/simulation/useMaterialStore.ts b/app/src/store/simulation/useMaterialStore.ts index 2421f53..044f9c5 100644 --- a/app/src/store/simulation/useMaterialStore.ts +++ b/app/src/store/simulation/useMaterialStore.ts @@ -3,6 +3,7 @@ import { immer } from 'zustand/middleware/immer'; type MaterialsStore = { materials: MaterialsSchema; + materialHistory: MaterialHistorySchema; addMaterial: (material: MaterialSchema) => MaterialSchema | undefined; removeMaterial: (materialId: string) => MaterialSchema | undefined; @@ -20,20 +21,20 @@ type MaterialsStore = { setNextLocation: ( materialId: string, - location?: { + location: { modelUuid: string; pointUuid: string; } | null ) => MaterialSchema | undefined; setMaterial: (materialId: string, materialType: string) => MaterialSchema | undefined; - setStartTime: (materialId: string, startTime: string) => MaterialSchema | undefined; - setEndTime: (materialId: string, endTime: string) => MaterialSchema | undefined; + setStartTime: (materialId: string, startTime: number) => MaterialSchema | undefined; + setEndTime: (materialId: string, endTime: number) => 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; - setIsPaused: (materialId: string, isPlaying: boolean) => MaterialSchema | undefined; + setIsPaused: (materialId: string, isPaused: boolean) => MaterialSchema | undefined; setIsRendered: (materialId: string, isRendered: boolean) => MaterialSchema | undefined; getMaterialById: (materialId: string) => MaterialSchema | undefined; @@ -41,11 +42,13 @@ type MaterialsStore = { getMaterialByCurrentPointUuid: (currentPointUuid: string) => MaterialSchema | undefined; getMaterialsByPoint: (pointUuid: string) => MaterialSchema[]; getMaterialsByModel: (modelUuid: string) => MaterialSchema[]; + getMaterialHistory: () => MaterialHistorySchema; }; export const useMaterialStore = create()( immer((set, get) => ({ materials: [], + materialHistory: [], addMaterial: (material) => { let updatedMaterial: MaterialSchema | undefined; @@ -60,8 +63,13 @@ export const useMaterialStore = create()( set((state) => { const material = state.materials.find(m => m.materialId === materialId); if (material) { - state.materials.filter(m => m.materialId !== material.materialId); + state.materials = state.materials.filter(m => m.materialId !== materialId); updatedMaterial = JSON.parse(JSON.stringify(material)); + + state.materialHistory.push({ + material, + removedAt: new Date().toISOString() + }); } }); return updatedMaterial; @@ -102,7 +110,7 @@ export const useMaterialStore = create()( set((state) => { const material = state.materials.find(m => m.materialId === materialId); if (material) { - material.next = location || undefined; + material.next = location; updatedMaterial = JSON.parse(JSON.stringify(material)); } }); @@ -242,5 +250,9 @@ export const useMaterialStore = create()( m.next?.modelUuid === modelUuid ); }, + + getMaterialHistory: () => { + return get().materialHistory; + }, })) ); \ No newline at end of file diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 754d151..04b742d 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -171,6 +171,7 @@ interface ArmBotStatus extends RoboticArmEventSchema { actionUuid: string; actionName: string; materialType: string | null; + materialId: string | null; }; } @@ -201,8 +202,8 @@ interface MaterialSchema { isVisible: boolean; isPaused: boolean; isRendered: boolean; - startTime?: string; - endTime?: string; + startTime?: number; + endTime?: number; cost?: number; weight?: number; @@ -215,7 +216,14 @@ interface MaterialSchema { next?: { modelUuid: string; pointUuid: string; - }; + } | null; } -type MaterialsSchema = MaterialSchema[]; \ No newline at end of file +type MaterialsSchema = MaterialSchema[]; + +interface MaterialHistoryEntry { + material: MaterialSchema; + removedAt: string; +} + +type MaterialHistorySchema = MaterialHistoryEntry[]; \ No newline at end of file