From b7a5908884687f84f4e03e6105b98b717ce78cec Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 16 May 2025 18:11:58 +0530 Subject: [PATCH] Refactor scene components and add moving clouds feature - Cleaned up the Ground and Shadows components for better readability and consistency. - Introduced MovingClouds component to enhance the visual environment with animated clouds. - Updated Scene component to include new cloud feature. - Improved ConveyorInstance logic to handle product selection and conveyor state management. - Enhanced RoboticArmInstance to check for active robotic arms in subsequences. - Added utility functions to find conveyors and robotic arms in sequences for better simulation control. - Refactored trigger handling to manage material pause states more effectively. - Optimized vehicle instance logic for smoother operation during loading and unloading. --- app/src/modules/builder/builder.tsx | 167 ++++++++-------- app/src/modules/scene/clouds/clouds.tsx | 90 +++++++++ app/src/modules/scene/environment/ground.tsx | 73 +++---- app/src/modules/scene/environment/shadow.tsx | 184 +++++++++-------- app/src/modules/scene/scene.tsx | 49 +++-- app/src/modules/scene/setup/setup.tsx | 3 + .../conveyorInstance/conveyorInstance.tsx | 45 +++-- .../modules/simulation/products/products.tsx | 8 +- .../armInstance/roboticArmInstance.tsx | 28 ++- .../checkActiveRoboticArmsInSubsequence.ts | 188 ++++++++++++++++++ .../functions/findConveyorInSequences.ts | 49 +++++ .../getConveyorSequencesForProduct.ts | 126 ++++++++++++ .../triggerHandler/useTriggerHandler.ts | 12 +- .../instances/instance/vehicleInstance.tsx | 50 +++-- 14 files changed, 777 insertions(+), 295 deletions(-) create mode 100644 app/src/modules/scene/clouds/clouds.tsx create mode 100644 app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts create mode 100644 app/src/modules/simulation/simulator/functions/findConveyorInSequences.ts create mode 100644 app/src/modules/simulation/simulator/functions/getConveyorSequencesForProduct.ts diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index 57ead56..18d407b 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -48,6 +48,7 @@ import NavMesh from "../simulation/vehicle/navMesh/navMesh"; import CalculateAreaGroup from "./groups/calculateAreaGroup"; import LayoutImage from "./layout/layoutImage"; import AssetsGroup from "./assetGroup/assetsGroup"; +import { Bvh } from "@react-three/drei"; export default function Builder() { const state = useThree(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements. @@ -203,29 +204,33 @@ export default function Builder() { <> - + + + - + + + - + + - + - + - + - + - {/* */} + {/* */} - + - + - + - + + ); } diff --git a/app/src/modules/scene/clouds/clouds.tsx b/app/src/modules/scene/clouds/clouds.tsx new file mode 100644 index 0000000..f876530 --- /dev/null +++ b/app/src/modules/scene/clouds/clouds.tsx @@ -0,0 +1,90 @@ +import * as THREE from 'three'; +import { useRef, useState } from 'react'; +import { useFrame } from '@react-three/fiber'; +import { Clouds, Cloud } from '@react-three/drei'; + +interface CloudGroupProps { + initialX: number; + initialZ: number; + speed: number; + height: number; +} + +function CloudGroup({ initialX, initialZ, speed, height }: CloudGroupProps) { + const group = useRef(null); + + useFrame((_, delta) => { + if (group.current) { + + group.current.position.x += delta * speed; + group.current.position.z += delta * speed * 0.5; + + if (group.current.position.x > 500) group.current.position.x = -500; + if (group.current.position.z > 500) group.current.position.z = -500; + } + }); + + return ( + + + + + + + + ); +} + +export function MovingClouds() { + + const savedTheme: string | null = localStorage.getItem("theme"); + const [theme, setTheme] = useState(savedTheme || "light"); + const cloudGroups = [ + { initialX: 0, initialZ: 0, speed: 8, height: 300 }, + { initialX: -300, initialZ: 100, speed: 10, height: 300 }, + { initialX: 200, initialZ: -150, speed: 4, height: 300 }, + { initialX: -400, initialZ: -200, speed: 7, height: 300 }, + { initialX: 400, initialZ: 300, speed: 5, height: 300 }, + { initialX: -200, initialZ: -300, speed: 7, height: 300 }, + { initialX: 300, initialZ: 200, speed: 10, height: 300 }, + ]; + + return ( + <> + {theme === 'light' && + <> + {cloudGroups.map((group, index) => ( + + ))} + + } + + ); +} \ No newline at end of file diff --git a/app/src/modules/scene/environment/ground.tsx b/app/src/modules/scene/environment/ground.tsx index e8d36d1..ef51791 100644 --- a/app/src/modules/scene/environment/ground.tsx +++ b/app/src/modules/scene/environment/ground.tsx @@ -2,50 +2,37 @@ import { useTileDistance, useToggleView } from "../../../store/builder/store"; import * as CONSTANTS from "../../../types/world/worldConstants"; const Ground = ({ grid, plane }: any) => { - const { toggleView } = useToggleView(); - const { planeValue, gridValue } = useTileDistance(); + const { toggleView } = useToggleView(); + const { planeValue, gridValue } = useTileDistance(); - return ( - - - - - - - - - - ); + return ( + + + + + + + + + + ); }; export default Ground; diff --git a/app/src/modules/scene/environment/shadow.tsx b/app/src/modules/scene/environment/shadow.tsx index e44b9cd..59b348b 100644 --- a/app/src/modules/scene/environment/shadow.tsx +++ b/app/src/modules/scene/environment/shadow.tsx @@ -2,111 +2,105 @@ import { useRef, useEffect } from "react"; import { useThree } from "@react-three/fiber"; import * as THREE from "three"; import { - useAzimuth, - useElevation, - useShadows, - useSunPosition, - useFloorItems, - useWallItems, - useTileDistance, + useAzimuth, + useElevation, + useShadows, + useSunPosition, + useFloorItems, + useWallItems, + useTileDistance, } from "../../../store/builder/store"; import * as CONSTANTS from "../../../types/world/worldConstants"; const shadowWorker = new Worker( - new URL( - "../../../services/factoryBuilder/webWorkers/shadowWorker", - import.meta.url - ) + new URL( + "../../../services/factoryBuilder/webWorkers/shadowWorker", + import.meta.url + ) ); export default function Shadows() { - const { shadows, setShadows } = useShadows(); - const { sunPosition, setSunPosition } = useSunPosition(); - const lightRef = useRef(null); - const targetRef = useRef(null); - const { controls, gl } = useThree(); - const { elevation, setElevation } = useElevation(); - const { azimuth, setAzimuth } = useAzimuth(); - const { floorItems } = useFloorItems(); - const { wallItems } = useWallItems(); - const { planeValue } = useTileDistance(); + const { shadows, setShadows } = useShadows(); + const { sunPosition, setSunPosition } = useSunPosition(); + const lightRef = useRef(null); + const targetRef = useRef(null); + const { controls, gl } = useThree(); + const { elevation, setElevation } = useElevation(); + const { azimuth, setAzimuth } = useAzimuth(); + const { floorItems } = useFloorItems(); + const { wallItems } = useWallItems(); + const { planeValue } = useTileDistance(); - useEffect(() => { - gl.shadowMap.enabled = true; - gl.shadowMap.type = THREE.PCFShadowMap; - }, [gl, floorItems, wallItems]); + useEffect(() => { + gl.shadowMap.enabled = true; + gl.shadowMap.type = THREE.PCFShadowMap; + }, [gl, floorItems, wallItems]); - useEffect(() => { - if (lightRef.current && targetRef.current) { - lightRef.current.target = targetRef.current; - } - }, []); + useEffect(() => { + if (lightRef.current && targetRef.current) { + lightRef.current.target = targetRef.current; + } + }, []); - useEffect(() => { - shadowWorker.onmessage = (event) => { - const { lightPosition, controlsTarget } = event.data; - if (lightRef.current && targetRef.current && controls) { - lightRef.current.position.copy(lightPosition); - targetRef.current.position.copy(controlsTarget); - } + useEffect(() => { + shadowWorker.onmessage = (event) => { + const { lightPosition, controlsTarget } = event.data; + if (lightRef.current && targetRef.current && controls) { + lightRef.current.position.copy(lightPosition); + targetRef.current.position.copy(controlsTarget); + gl.shadowMap.needsUpdate = true; + } + }; + }, [shadowWorker, controls]); + + const updateShadows = () => { + if (controls && shadowWorker) { + const offsetDistance = CONSTANTS.shadowConfig.shadowOffset; + const controlsTarget = (controls as any).getTarget(); + shadowWorker.postMessage({ controlsTarget, sunPosition, offsetDistance }); + } }; - }, [shadowWorker, controls]); - const updateShadows = () => { - if (controls && shadowWorker) { - const offsetDistance = CONSTANTS.shadowConfig.shadowOffset; - const controlsTarget = (controls as any).getTarget(); - shadowWorker.postMessage({ controlsTarget, sunPosition, offsetDistance }); - } - }; + useEffect(() => { + if (controls && shadows) { + updateShadows(); + (controls as any).addEventListener("update", updateShadows); + return () => { + (controls as any).removeEventListener("update", updateShadows); + }; + } + }, [controls, elevation, azimuth, shadows]); - useEffect(() => { - if (controls && shadows) { - updateShadows(); - (controls as any).addEventListener("update", updateShadows); - return () => { - (controls as any).removeEventListener("update", updateShadows); - }; - } - }, [controls, elevation, azimuth, shadows]); - - return ( - <> - {/* {(lightRef.current?.shadow) && - - } */} - - - - {/* - */} - - - - - ); + return ( + <> + {/* {(lightRef.current?.shadow) && + + } */} + + + + + + + + ); } diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index 0e81cf6..d8d3447 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -9,35 +9,32 @@ import Simulation from "../simulation/simulation"; import Collaboration from "../collaboration/collaboration"; export default function Scene() { - const map = useMemo( - () => [ - { name: "forward", keys: ["ArrowUp", "w", "W"] }, - { name: "backward", keys: ["ArrowDown", "s", "S"] }, - { name: "left", keys: ["ArrowLeft", "a", "A"] }, - { name: "right", keys: ["ArrowRight", "d", "D"] }, - ], - [] - ); + const map = useMemo(() => [ + { name: "forward", keys: ["ArrowUp", "w", "W"] }, + { name: "backward", keys: ["ArrowDown", "s", "S"] }, + { name: "left", keys: ["ArrowLeft", "a", "A"] }, + { name: "right", keys: ["ArrowRight", "d", "D"] }, + ], []); - return ( - - { - e.preventDefault(); - }} - > - + return ( + + { + e.preventDefault(); + }} + > + - + - + - + - - - - ); + + + + ); } diff --git a/app/src/modules/scene/setup/setup.tsx b/app/src/modules/scene/setup/setup.tsx index 660264b..f3f8d15 100644 --- a/app/src/modules/scene/setup/setup.tsx +++ b/app/src/modules/scene/setup/setup.tsx @@ -5,6 +5,7 @@ import Controls from '../controls/controls'; import { Environment } from '@react-three/drei' import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr"; +import { MovingClouds } from '../clouds/clouds'; function Setup() { return ( @@ -17,6 +18,8 @@ function Setup() { + + ) diff --git a/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx b/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx index f1fc34d..fa1b33b 100644 --- a/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx +++ b/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx @@ -2,36 +2,43 @@ import React, { useEffect } from 'react' import { useMaterialStore } from '../../../../../store/simulation/useMaterialStore'; import { useConveyorStore } from '../../../../../store/simulation/useConveyorStore'; import { useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { findConveyorInSequences } from '../../../simulator/functions/findConveyorInSequences'; +import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; +import { useProductStore } from '../../../../../store/simulation/useProductStore'; function ConveyorInstance({ conveyor }: { conveyor: ConveyorStatus }) { + const { getProductById } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); const { materials, getMaterialsByCurrentModelUuid } = useMaterialStore(); const { isReset } = useResetButtonStore(); - const { setConveyorPaused } = useConveyorStore(); useEffect(() => { + const product = getProductById(selectedProduct.productId); + if (!product) return; + + const sequenceInfo = findConveyorInSequences(product, conveyor.modelUuid); + if (!sequenceInfo) return; + + const { currentSubSequence } = sequenceInfo; const conveyorMaterials = getMaterialsByCurrentModelUuid(conveyor.modelUuid); - if (conveyorMaterials && conveyorMaterials?.length > 0) { - const hasPausedMaterials = conveyorMaterials.some(material => material.isPaused); + if (conveyorMaterials && conveyorMaterials.length > 0) { + const shouldPauseSubsequence = currentSubSequence.some(subConveyor => { + if (subConveyor.type !== 'transfer') return false; + const subMaterials = getMaterialsByCurrentModelUuid(subConveyor.modelUuid); + return subMaterials?.some(m => m.isPaused) ?? false; + }); - if (hasPausedMaterials) { - setConveyorPaused(conveyor.modelUuid, true); - } else { - setConveyorPaused(conveyor.modelUuid, false); - } + currentSubSequence.forEach(subConveyor => { + if (subConveyor.type === 'transfer') { + setConveyorPaused(subConveyor.modelUuid, shouldPauseSubsequence); + } + }); } + }, [materials, conveyor.modelUuid, getMaterialsByCurrentModelUuid, setConveyorPaused, isReset, selectedProduct.productId, getProductById]); - }, [materials, conveyor.modelUuid, getMaterialsByCurrentModelUuid, setConveyorPaused, isReset]); - - useEffect(() => { - // console.log('conveyor: ', conveyor); - }, [conveyor]) - - return ( - <> - - ) + return null; } -export default ConveyorInstance \ No newline at end of file +export default React.memo(ConveyorInstance); \ No newline at end of file diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx index d56b05e..4bd0bcb 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -52,7 +52,7 @@ function Products() { }); } } - }, [selectedProduct, products, isReset]); + }, [selectedProduct, products, isReset, isPlaying]); useEffect(() => { if (selectedProduct.productId) { @@ -66,7 +66,7 @@ function Products() { }); } } - }, [selectedProduct, products, isReset]); + }, [selectedProduct, products, isReset, isPlaying]); useEffect(() => { if (selectedProduct.productId) { @@ -80,7 +80,7 @@ function Products() { }); } } - }, [selectedProduct, products, isReset]); + }, [selectedProduct, products, isReset, isPlaying]); useEffect(() => { if (selectedProduct.productId) { @@ -94,7 +94,7 @@ function Products() { }); } } - }, [selectedProduct, products, isReset]); + }, [selectedProduct, products, isReset, isPlaying]); useEffect(() => { if (selectedProduct.productId) { diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index 0925b04..3427733 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -13,6 +13,7 @@ import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore import { useStorageUnitStore } from '../../../../../store/simulation/useStorageUnitStore'; import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; +import { useCheckActiveRoboticArmsInSubsequence } from '../../../simulator/functions/checkActiveRoboticArmsInSubsequence'; function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { @@ -30,13 +31,16 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { const { setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); const { decrementVehicleLoad, removeLastMaterial } = useVehicleStore(); const { removeLastMaterial: removeLastStorageMaterial, updateCurrentLoad } = useStorageUnitStore(); - const { setIsVisible, getMaterialById } = useMaterialStore(); + const { setIsVisible, setIsPaused, getMaterialById } = useMaterialStore(); const { selectedProduct } = useSelectedProduct(); - const { getActionByUuid, getEventByActionUuid, getEventByModelUuid } = useProductStore(); + const { getActionByUuid, getEventByActionUuid, getEventByModelUuid, getProductById } = useProductStore(); const { triggerPointActions } = useTriggerHandler(); const { isPlaying } = usePlayButtonStore(); const { isReset } = useResetButtonStore(); const { isPaused } = usePauseButtonStore(); + const checkActiveRoboticArms = useCheckActiveRoboticArmsInSubsequence(); + + const lastRemoved = useRef<{ type: string, materialId: string } | null>(null); function firstFrame() { startTime = performance.now(); @@ -63,6 +67,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { removeLastStorageMaterial(previousModel.modelUuid); updateCurrentLoad(previousModel.modelUuid, -1) } + lastRemoved.current = { type: previousModel.type, materialId: armBot.currentAction.materialId }; } else { setIsVisible(armBot.currentAction.materialId, false); } @@ -76,11 +81,20 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { if (armBot.currentAction) { const action = getActionByUuid(selectedProduct.productId, armBot.currentAction.actionUuid); + const model = getEventByModelUuid(selectedProduct.productId, action?.triggers[0].triggeredAsset?.triggeredModel.modelUuid || ''); if (action && action.triggers[0].triggeredAsset?.triggeredModel.modelUuid) { - const model = getEventByModelUuid(selectedProduct.productId, action?.triggers[0].triggeredAsset?.triggeredModel.modelUuid); if (!model) return; if (model.type === 'transfer') { setIsVisible(armBot.currentAction.materialId || '', true); + + const product = getProductById(selectedProduct.productId); + if (product) { + const result = checkActiveRoboticArms(product, armBot.modelUuid); + // console.log('result: ', result); + // if (result?.hasActiveRoboticArm) { + // lastRemoved.current = null; + // } + } } else if (model.type === 'machine') { // } else if (model.type === 'vehicle') { @@ -287,6 +301,14 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("rest"); setPath([]) + + if (lastRemoved.current) { + if (lastRemoved.current.type === 'transfer') { + setIsPaused(lastRemoved.current.materialId, true) + } else { + setIsPaused(lastRemoved.current.materialId, false) + } + } } } const logStatus = (id: string, status: string) => { diff --git a/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts new file mode 100644 index 0000000..797f836 --- /dev/null +++ b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts @@ -0,0 +1,188 @@ +import { extractTriggersFromPoint } from "./extractTriggersFromPoint"; +import { useArmBotStore } from "../../../../store/simulation/useArmBotStore"; + +export function getRoboticArmSequencesInProduct( + product: { + productName: string; + productId: string; + eventDatas: EventsSchema[]; + } +): EventsSchema[][][] { + // Get all machine sequences for this product + const machineSequences = determineExecutionMachineSequences([product]); + + const allRoboticArmSequences: EventsSchema[][][] = []; + + // Process each machine sequence separately + for (const machineSequence of machineSequences) { + const roboticArmSequencesForThisMachineSequence: EventsSchema[][] = []; + let currentRoboticArmSequence: EventsSchema[] = []; + + for (const event of machineSequence) { + if (event.type === 'roboticArm') { + // Add robotic arm to current sequence + currentRoboticArmSequence.push(event); + } else if (event.type === 'vehicle') { + // Vehicle encountered - split the sequence + if (currentRoboticArmSequence.length > 0) { + roboticArmSequencesForThisMachineSequence.push([...currentRoboticArmSequence]); + currentRoboticArmSequence = []; + } + } + // Other machine types continue the current sequence + } + + // Add any remaining robotic arms in the current sequence + if (currentRoboticArmSequence.length > 0) { + roboticArmSequencesForThisMachineSequence.push([...currentRoboticArmSequence]); + } + + if (roboticArmSequencesForThisMachineSequence.length > 0) { + allRoboticArmSequences.push(roboticArmSequencesForThisMachineSequence); + } + } + + return allRoboticArmSequences; +} + +export function findRoboticArmSubsequence( + product: { + productName: string; + productId: string; + eventDatas: EventsSchema[]; + }, + roboticArmModelUuid: string +): { + allSequences: EventsSchema[][][]; + parentSequence: EventsSchema[][]; + currentSubSequence: EventsSchema[]; +} | null { + const allSequences = getRoboticArmSequencesInProduct(product); + + for (const parentSequence of allSequences) { + for (const currentSubSequence of parentSequence) { + const hasTargetRoboticArm = currentSubSequence.some( + event => event.type === 'roboticArm' && event.modelUuid === roboticArmModelUuid + ); + + if (hasTargetRoboticArm) { + return { + allSequences, + parentSequence, + currentSubSequence + }; + } + } + } + + return null; +} + +// React component/hook that uses the pure functions +export function useCheckActiveRoboticArmsInSubsequence() { + const { getArmBotById } = useArmBotStore(); + + return function (product: { + productName: string; + productId: string; + eventDatas: EventsSchema[]; + }, roboticArmModelUuid: string) { + const result = findRoboticArmSubsequence(product, roboticArmModelUuid); + + if (!result) return null; + + const hasActiveRoboticArm = result.currentSubSequence.some(event => { + if (event.type === 'roboticArm' && event.modelUuid !== roboticArmModelUuid) { + const armBot = getArmBotById(event.modelUuid); + return armBot?.isActive; + } + return false; + }); + + return { + ...result, + hasActiveRoboticArm + }; + }; +} + +// Helper function to get machine sequences (simplified from your example) +function determineExecutionMachineSequences(products: productsSchema): EventsSchema[][] { + const pointToEventMap = new Map(); + const allPoints: PointsScheme[] = []; + + // First pass: map points to their corresponding events + products.forEach(product => { + product.eventDatas.forEach(event => { + if (event.type === 'transfer') { + event.points.forEach(point => { + pointToEventMap.set(point.uuid, event); + allPoints.push(point); + }); + } else if ( + event.type === 'vehicle' || + event.type === 'machine' || + event.type === 'storageUnit' || + event.type === 'roboticArm' + ) { + pointToEventMap.set(event.point.uuid, event); + allPoints.push(event.point); + } + }); + }); + + // Build dependency graph + const dependencyGraph = 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 && pointToEventMap.has(targetUuid)) { + dependencies.push(targetUuid); + triggeredPoints.add(targetUuid); + } + }); + + dependencyGraph.set(point.uuid, dependencies); + }); + + // Find root points (points that aren't triggered by others) + const rootPoints = allPoints.filter(point => + !triggeredPoints.has(point.uuid) && + dependencyGraph.get(point.uuid)?.length + ); + + const executionSequences: EventsSchema[][] = []; + + function buildSequence(startUuid: string): EventsSchema[] { + const sequence: EventsSchema[] = []; + const visited = new Set(); + + function traverse(uuid: string) { + if (visited.has(uuid)) return; + visited.add(uuid); + + const event = pointToEventMap.get(uuid); + if (event && !sequence.includes(event)) { + sequence.push(event); + } + + const nextPoints = dependencyGraph.get(uuid) || []; + nextPoints.forEach(nextUuid => traverse(nextUuid)); + } + + traverse(startUuid); + return sequence; + } + + // Build sequences from root points + rootPoints.forEach(root => { + executionSequences.push(buildSequence(root.uuid)); + }); + + return executionSequences; +} \ No newline at end of file diff --git a/app/src/modules/simulation/simulator/functions/findConveyorInSequences.ts b/app/src/modules/simulation/simulator/functions/findConveyorInSequences.ts new file mode 100644 index 0000000..3c9ee44 --- /dev/null +++ b/app/src/modules/simulation/simulator/functions/findConveyorInSequences.ts @@ -0,0 +1,49 @@ +import { getConveyorSequencesInProduct } from "./getConveyorSequencesForProduct"; + +export function findConveyorInSequences( + product: { + productName: string; + productId: string; + eventDatas: EventsSchema[]; + }, + conveyorUuid: string +): { + allSequences: EventsSchema[][][]; + parentSequence: EventsSchema[][]; + currentSubSequence: EventsSchema[]; +} | null { + // Get all conveyor sequences + const allSequences = getConveyorSequencesInProduct(product); + + // Search through all sequences + for (const parentSequence of allSequences) { + for (const currentSubSequence of parentSequence) { + for (const conveyor of currentSubSequence) { + // Check if this is the conveyor we're looking for + if (conveyor.modelUuid === conveyorUuid) { + return { + allSequences, + parentSequence, + currentSubSequence + }; + } + + // Also check points in case the UUID matches a point's conveyor + if (conveyor.type === 'transfer') { + for (const point of conveyor.points) { + if (point.uuid === conveyorUuid) { + return { + allSequences, + parentSequence, + currentSubSequence + }; + } + } + } + } + } + } + + // Conveyor not found + return null; +} \ No newline at end of file diff --git a/app/src/modules/simulation/simulator/functions/getConveyorSequencesForProduct.ts b/app/src/modules/simulation/simulator/functions/getConveyorSequencesForProduct.ts new file mode 100644 index 0000000..3aaaa9b --- /dev/null +++ b/app/src/modules/simulation/simulator/functions/getConveyorSequencesForProduct.ts @@ -0,0 +1,126 @@ +import { extractTriggersFromPoint } from "./extractTriggersFromPoint"; + +export function getConveyorSequencesInProduct( + product: { + productName: string; + productId: string; + eventDatas: EventsSchema[]; + } +): EventsSchema[][][] { // Now returns array of array of arrays + // Get all machine sequences for this product + const machineSequences = determineExecutionMachineSequences([product]); + + const allConveyorSequences: EventsSchema[][][] = []; + + // Process each machine sequence separately + for (const machineSequence of machineSequences) { + const conveyorSequencesForThisMachineSequence: EventsSchema[][] = []; + let currentConveyorSequence: EventsSchema[] = []; + + for (const event of machineSequence) { + if (event.type === 'transfer') { + // Add conveyor to current sequence + currentConveyorSequence.push(event); + } else if (event.type === 'vehicle') { + // Vehicle encountered - split the sequence + if (currentConveyorSequence.length > 0) { + conveyorSequencesForThisMachineSequence.push([...currentConveyorSequence]); + currentConveyorSequence = []; + } + } + // Other machine types don't affect the conveyor sequence + } + + // Add any remaining conveyors in the current sequence + if (currentConveyorSequence.length > 0) { + conveyorSequencesForThisMachineSequence.push([...currentConveyorSequence]); + } + + if (conveyorSequencesForThisMachineSequence.length > 0) { + allConveyorSequences.push(conveyorSequencesForThisMachineSequence); + } + } + + return allConveyorSequences; +} + +// Helper function to get machine sequences (simplified from your example) +function determineExecutionMachineSequences(products: productsSchema): EventsSchema[][] { + const pointToEventMap = new Map(); + const allPoints: PointsScheme[] = []; + + // First pass: map points to their corresponding events + products.forEach(product => { + product.eventDatas.forEach(event => { + if (event.type === 'transfer') { + event.points.forEach(point => { + pointToEventMap.set(point.uuid, event); + allPoints.push(point); + }); + } else if ( + event.type === 'vehicle' || + event.type === 'machine' || + event.type === 'storageUnit' || + event.type === 'roboticArm' + ) { + pointToEventMap.set(event.point.uuid, event); + allPoints.push(event.point); + } + }); + }); + + // Build dependency graph + const dependencyGraph = 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 && pointToEventMap.has(targetUuid)) { + dependencies.push(targetUuid); + triggeredPoints.add(targetUuid); + } + }); + + dependencyGraph.set(point.uuid, dependencies); + }); + + // Find root points (points that aren't triggered by others) + const rootPoints = allPoints.filter(point => + !triggeredPoints.has(point.uuid) && + dependencyGraph.get(point.uuid)?.length + ); + + const executionSequences: EventsSchema[][] = []; + + function buildSequence(startUuid: string): EventsSchema[] { + const sequence: EventsSchema[] = []; + const visited = new Set(); + + function traverse(uuid: string) { + if (visited.has(uuid)) return; + visited.add(uuid); + + const event = pointToEventMap.get(uuid); + if (event && !sequence.includes(event)) { + sequence.push(event); + } + + const nextPoints = dependencyGraph.get(uuid) || []; + nextPoints.forEach(nextUuid => traverse(nextUuid)); + } + + traverse(startUuid); + return sequence; + } + + // Build sequences from root points + rootPoints.forEach(root => { + executionSequences.push(buildSequence(root.uuid)); + }); + + return executionSequences; +} \ 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 9936535..7de5d29 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -138,6 +138,7 @@ export function useTriggerHandler() { if (armBot.isActive === false && armBot.state === 'idle') { // Handle current action from arm bot + setIsPaused(materialId, true); handleAction(action, materialId); } else { @@ -291,6 +292,9 @@ export function useTriggerHandler() { // Machine to Robotic Arm if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { const material = getMaterialById(materialId); + + setIsPaused(materialId, true); + if (material) { const action = getActionByUuid(selectedProduct.productId, trigger.triggeredAsset.triggeredAction.actionUuid); const armBot = getArmBotById(trigger.triggeredAsset?.triggeredModel.modelUuid); @@ -302,7 +306,7 @@ export function useTriggerHandler() { }) setCurrentLocation(material.materialId, { - modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + modelUuid: material.current.modelUuid, pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, }); @@ -338,7 +342,7 @@ export function useTriggerHandler() { const material = getMaterialById(materialId); if (material) { - setIsPaused(material.materialId, false); + // setIsPaused(material.materialId, false); setPreviousLocation(material.materialId, { modelUuid: material.current.modelUuid, @@ -477,7 +481,7 @@ export function useTriggerHandler() { const material = getMaterialById(materialId); if (material) { - setIsPaused(material.materialId, false); + // setIsPaused(material.materialId, false); const action = getActionByUuid(selectedProduct.productId, trigger.triggeredAsset.triggeredAction.actionUuid); const machine = getMachineById(trigger.triggeredAsset?.triggeredModel.modelUuid); @@ -498,7 +502,7 @@ export function useTriggerHandler() { }) setCurrentLocation(material.materialId, { - modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + modelUuid: material.current.modelUuid, pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, }); diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index df64dbe..3a45409 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -280,26 +280,34 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) unLoadDuration: number, action: VehicleAction ) { - startTime = performance.now(); - const fixedInterval = unLoadDuration * (1000 / speed); + let lastIncrementTime = performance.now(); + let pauseStartTime: number | null = null; + let totalPausedDuration = 0; + const fixedInterval = (unLoadDuration * 1000) / speed; - const dropLoop = () => { - if (isPausedRef.current) { - pauseTimeRef.current ??= performance.now(); + const dropLoop = (currentTime: number) => { + const conveyor = getConveyorById(conveyorId); + + if (isPausedRef.current || (conveyor && conveyor.isPaused)) { + if (pauseStartTime === null) { + pauseStartTime = currentTime; + } requestAnimationFrame(dropLoop); return; } - if (pauseTimeRef.current) { - const pauseDuration = performance.now() - pauseTimeRef.current; - startTime += pauseDuration; - pauseTimeRef.current = null; + // If we were paused but now resumed + if (pauseStartTime !== null) { + totalPausedDuration += currentTime - pauseStartTime; + pauseStartTime = null; } - const elapsedTime = performance.now() - startTime; - const conveyor = getConveyorById(conveyorId); - if (elapsedTime >= fixedInterval) { - if (conveyor && !conveyor.isPaused && vehicleCurrentLoad > 0) { + // Adjust for paused time + const adjustedCurrentTime = currentTime - totalPausedDuration; + const elapsedSinceLastIncrement = adjustedCurrentTime - lastIncrementTime; + + if (elapsedSinceLastIncrement >= fixedInterval) { + if (conveyor && vehicleCurrentLoad > 0) { decrementVehicleLoad(vehicleId, 1); vehicleCurrentLoad -= 1; @@ -308,18 +316,18 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) triggerPointActions(action, material.materialId); } - if (vehicleCurrentLoad > 0) { - startTime = performance.now(); - requestAnimationFrame(dropLoop); - } - } else if (!conveyor?.isActive) { - requestAnimationFrame(dropLoop); + // Update the last increment time (using adjusted time) + lastIncrementTime = adjustedCurrentTime; } - } else { + } + + // Continue the loop if there's more load to drop + if (vehicleCurrentLoad > 0) { requestAnimationFrame(dropLoop); } }; - dropLoop(); + + requestAnimationFrame(dropLoop); } function handleMaterialDropToArmBot(model: RoboticArmEventSchema) {