From 7519aa90c622e37ad84f0189daa7e9326fe5438f Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 2 Jul 2025 15:07:31 +0530 Subject: [PATCH 01/13] feat: Implement human simulation features - Added human event handling in the simulation, including the ability to add, update, and remove human instances. - Introduced a new Human store to manage human states and actions. - Updated the simulation context to include human management. - Enhanced the Points and TriggerConnector components to support human interactions. - Refactored existing components to integrate human-related functionalities. - Added HumanInstance and HumanInstances components for rendering human entities in the simulation. - Updated TypeScript definitions to include human-related types and actions. --- .../properties/AssetProperties.tsx | 5 +- .../versionHisory/VersionHistory.tsx | 4 +- .../aisle/aisleCreator/aisleCreator.tsx | 141 ++++++++++- .../builder/asset/functions/addAssetModel.ts | 34 +++ .../builder/asset/models/model/model.tsx | 24 +- app/src/modules/builder/point/point.tsx | 38 ++- .../socket/socketResponses.dev.tsx | 2 +- app/src/modules/scene/sceneContext.tsx | 9 +- .../productionCapacityData.tsx | 10 +- .../analysis/simulationAnalysis.tsx | 11 +- .../events/points/creator/pointsCreator.tsx | 31 +++ .../simulation/events/points/points.tsx | 1 - .../triggerConnections/triggerConnector.tsx | 16 ++ app/src/modules/simulation/human/human.tsx | 13 + .../human/instances/humanInstances.tsx | 20 ++ .../instances/instance/humanInstance.tsx | 15 ++ .../modules/simulation/products/products.tsx | 17 +- app/src/modules/simulation/simulation.tsx | 5 +- .../simulation/simulator/simulator.tsx | 1 - .../vehicle/instances/vehicleInstances.tsx | 26 +- app/src/store/simulation/useHumanStore.ts | 239 ++++++++++++++++++ app/src/types/simulationTypes.d.ts | 188 ++++++++------ 22 files changed, 706 insertions(+), 144 deletions(-) create mode 100644 app/src/modules/simulation/human/human.tsx create mode 100644 app/src/modules/simulation/human/instances/humanInstances.tsx create mode 100644 app/src/modules/simulation/human/instances/instance/humanInstance.tsx create mode 100644 app/src/store/simulation/useHumanStore.ts diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index 1ff41e7..818bd8a 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -51,9 +51,8 @@ const AssetProperties: React.FC = () => { }; const handleAnimationClick = (animation: string) => { - if (selectedFloorItem && selectedFloorItem.animationState) { - const isPlaying = selectedFloorItem.animationState?.playing || false; - setCurrentAnimation(selectedFloorItem.uuid, animation, !isPlaying); + if (selectedFloorItem) { + setCurrentAnimation(selectedFloorItem.uuid, animation, true); } } diff --git a/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx b/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx index 9d24ba0..a0255f4 100644 --- a/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx +++ b/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx @@ -28,9 +28,9 @@ const VersionHistory = () => { const handleSelectVersion = (version: Version) => { if (!projectId) return; - getVersionDataApi(projectId, version.versionId).then((verdionData) => { + getVersionDataApi(projectId, version.versionId).then((versionData) => { setSelectedVersion(version); - // console.log(verdionData); + // console.log(versionData); }).catch((err) => { // console.log(err); }) diff --git a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx index 31ed1f6..af08226 100644 --- a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx +++ b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx @@ -3,12 +3,14 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { useThree } from '@react-three/fiber'; import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store'; import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; -import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi'; import { useParams } from 'react-router-dom'; import { useVersionContext } from '../../version/versionContext'; import { useSceneContext } from '../../../scene/sceneContext'; import ReferenceAisle from './referenceAisle'; import ReferencePoint from '../../point/reference/referencePoint'; +import { getUserData } from '../../../../functions/getUserData'; + +// import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi'; function AisleCreator() { const { scene, camera, raycaster, gl, pointer } = useThree(); @@ -23,6 +25,7 @@ function AisleCreator() { const isLeftMouseDown = useRef(false); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const { userId, organization } = getUserData(); const { projectId } = useParams(); const [tempPoints, setTempPoints] = useState([]); @@ -106,7 +109,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -129,7 +147,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -151,7 +184,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -172,7 +220,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -195,7 +258,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -217,7 +295,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -238,7 +331,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -260,7 +368,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 7c85a49..978b45c 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -360,6 +360,40 @@ async function handleModelLoad( position: storageEvent.point.position, rotation: storageEvent.point.rotation, }; + } else if (selectedItem.type === "Human") { + const humanEvent: HumanEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + rotation: newFloorItem.rotation, + state: "idle", + type: "human", + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [data.points[0].x, data.points[0].y, data.points[0].z], + rotation: [0, 0, 0], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "animation", + animation: null, + loadCapacity: 1, + travelPoints: { + startPoint: null, + endPoint: null, + }, + triggers: [] + } + ] + } + } + addEvent(humanEvent); + eventData.point = { + uuid: humanEvent.point.uuid, + position: humanEvent.point.position, + rotation: humanEvent.point.rotation, + } } const completeData = { diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 3c76664..b47fcd7 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -301,20 +301,21 @@ function Model({ asset }: { readonly asset: Asset }) { useEffect(() => { - const handlePlay = (clipName: string) => { + + if (asset.animationState && asset.animationState.playing) { if (!mixerRef.current) return; Object.values(actions.current).forEach((action) => action.stop()); - const action = actions.current[clipName]; + const action = actions.current[asset.animationState.current]; if (action && asset.animationState?.playing) { action.reset().setLoop(THREE.LoopOnce, 1).play(); } - }; + } else { + Object.values(actions.current).forEach((action) => action.stop()); + } - handlePlay(asset.animationState?.current || ''); - - }, [asset]) + }, [asset.animationState]) return ( ) )} - {/* - -
- {animationNames.map((name) => ( - - ))} -
- -
*/}
); } diff --git a/app/src/modules/builder/point/point.tsx b/app/src/modules/builder/point/point.tsx index 15777aa..73dec77 100644 --- a/app/src/modules/builder/point/point.tsx +++ b/app/src/modules/builder/point/point.tsx @@ -10,8 +10,8 @@ import { useParams } from 'react-router-dom'; import { useVersionContext } from '../version/versionContext'; import { useSceneContext } from '../../scene/sceneContext'; -import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi'; -import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi'; +// import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi'; +// import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi'; // import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi'; // import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi'; // import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi'; @@ -159,7 +159,22 @@ function Point({ point }: { readonly point: Point }) { const updatedAisles = getAislesByPointId(point.pointUuid); if (updatedAisles.length > 0 && projectId) { updatedAisles.forEach((updatedAisle) => { - upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || ''); + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: updatedAisle.aisleUuid, + points: updatedAisle.points, + type: updatedAisle.type + }) }) } } else if (point.pointType === 'Wall') { @@ -238,7 +253,22 @@ function Point({ point }: { readonly point: Point }) { if (removedAisles.length > 0) { removedAisles.forEach(aisle => { if (projectId) { - deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '') + + // API + + // deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || ''); + + // SOCKET + + const data = { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid + } + + socket.emit('v1:model-aisle:delete', data); } }); setHoveredPoint(null); diff --git a/app/src/modules/collaboration/socket/socketResponses.dev.tsx b/app/src/modules/collaboration/socket/socketResponses.dev.tsx index 5b8f772..f2ab238 100644 --- a/app/src/modules/collaboration/socket/socketResponses.dev.tsx +++ b/app/src/modules/collaboration/socket/socketResponses.dev.tsx @@ -6,7 +6,7 @@ export default function SocketResponses() { useEffect(() => { socket.on("v1:model-asset:response:add", (data: any) => { - console.log('data: ', data); + // console.log('data: ', data); }); return () => { diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index 4e48e51..67811c4 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -16,6 +16,7 @@ import { createMachineStore, MachineStoreType } from '../../store/simulation/use import { createConveyorStore, ConveyorStoreType } from '../../store/simulation/useConveyorStore'; import { createVehicleStore, VehicleStoreType } from '../../store/simulation/useVehicleStore'; import { createStorageUnitStore, StorageUnitStoreType } from '../../store/simulation/useStorageUnitStore'; +import { createHumanStore, HumanStoreType } from '../../store/simulation/useHumanStore'; type SceneContextValue = { @@ -35,6 +36,7 @@ type SceneContextValue = { conveyorStore: ConveyorStoreType; vehicleStore: VehicleStoreType; storageUnitStore: StorageUnitStoreType; + humanStore: HumanStoreType; clearStores: () => void; @@ -67,6 +69,7 @@ export function SceneProvider({ const conveyorStore = useMemo(() => createConveyorStore(), []); const vehicleStore = useMemo(() => createVehicleStore(), []); const storageUnitStore = useMemo(() => createStorageUnitStore(), []); + const humanStore = useMemo(() => createHumanStore(), []); const clearStores = useMemo(() => () => { assetStore.getState().clearAssets(); @@ -83,7 +86,8 @@ export function SceneProvider({ conveyorStore.getState().clearConveyors(); vehicleStore.getState().clearVehicles(); storageUnitStore.getState().clearStorageUnits(); - }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore]); + humanStore.getState().clearHumans(); + }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]); const contextValue = useMemo(() => ( { @@ -101,10 +105,11 @@ export function SceneProvider({ conveyorStore, vehicleStore, storageUnitStore, + humanStore, clearStores, layout } - ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, clearStores, layout]); + ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, clearStores, layout]); return ( diff --git a/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx b/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx index 888a519..f7891e7 100644 --- a/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx +++ b/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx @@ -1,19 +1,15 @@ -import React, { useEffect } from 'react' +import { useEffect } from 'react' import { useInputValues, useProductionCapacityData, useThroughPutData } from '../../../../store/builder/store' import { usePlayButtonStore } from '../../../../store/usePlayButtonStore'; -import { useProductContext } from '../../products/productContext'; export default function ProductionCapacityData() { const { throughputData } = useThroughPutData() - const { productionCapacityData, setProductionCapacityData } = useProductionCapacityData() + const { setProductionCapacityData } = useProductionCapacityData() const { inputValues } = useInputValues(); const { isPlaying } = usePlayButtonStore(); - const { selectedProductStore } = useProductContext(); - const { selectedProduct } = selectedProductStore(); useEffect(() => { if (!isPlaying) { - setProductionCapacityData(0); } }, [isPlaying]); @@ -26,8 +22,6 @@ export default function ProductionCapacityData() { const Monthly_working_days = workingDaysPerYear / 12; const Production_capacity_per_month = throughputData * Monthly_working_days; - - setProductionCapacityData(Number(Production_capacity_per_month.toFixed(2))); } }, [throughputData, inputValues, isPlaying]); diff --git a/app/src/modules/simulation/analysis/simulationAnalysis.tsx b/app/src/modules/simulation/analysis/simulationAnalysis.tsx index db5a88b..2cfda53 100644 --- a/app/src/modules/simulation/analysis/simulationAnalysis.tsx +++ b/app/src/modules/simulation/analysis/simulationAnalysis.tsx @@ -1,18 +1,9 @@ -import React, { useEffect } from 'react' -import { usePlayButtonStore } from '../../../store/usePlayButtonStore' import ProductionCapacityData from './productionCapacity/productionCapacityData' import ThroughPutData from './throughPut/throughPutData' import ROIData from './ROI/roiData' function SimulationAnalysis() { - const { isPlaying } = usePlayButtonStore() - // useEffect(()=>{ - // if (isPlaying) { - // - // } else { - // - // } - // },[isPlaying]) + return ( <> diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx index 9f9cbb4..25e0b3d 100644 --- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx +++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx @@ -352,6 +352,37 @@ function PointsCreator() { ); + } else if (usedEvent.type === "human") { + const point = usedEvent.point; + return ( + + (sphereRefs.current[point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + if (toolMode === 'cursor') { + setSelectedEventSphere( + sphereRefs.current[point.uuid] + ); + } + }} + position={new THREE.Vector3(...point.position)} + userData={{ + modelUuid: usedEvent.modelUuid, + pointUuid: point.uuid, + }} + > + + + + + ); } else { return null; } diff --git a/app/src/modules/simulation/events/points/points.tsx b/app/src/modules/simulation/events/points/points.tsx index 2a50f2d..c8b9d6d 100644 --- a/app/src/modules/simulation/events/points/points.tsx +++ b/app/src/modules/simulation/events/points/points.tsx @@ -1,4 +1,3 @@ -import React from 'react' import PointsCreator from './creator/pointsCreator' function Points() { diff --git a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx index 0b8cb2d..918007a 100644 --- a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx +++ b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx @@ -152,6 +152,22 @@ function TriggerConnector() { }); } } + // Handle Human point + else if (event.type === "human" && 'point' in event) { + const point = event.point; + point.actions?.forEach(action => { + action.triggers?.forEach(trigger => { + if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { + newConnections.push({ + id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, + startPointUuid: point.uuid, + endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + trigger + }); + } + }); + }); + } }); setConnections(newConnections); diff --git a/app/src/modules/simulation/human/human.tsx b/app/src/modules/simulation/human/human.tsx new file mode 100644 index 0000000..30608d4 --- /dev/null +++ b/app/src/modules/simulation/human/human.tsx @@ -0,0 +1,13 @@ +import HumanInstances from './instances/humanInstances' + +function Human() { + return ( + <> + + + + + ) +} + +export default Human \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/humanInstances.tsx b/app/src/modules/simulation/human/instances/humanInstances.tsx new file mode 100644 index 0000000..75849ad --- /dev/null +++ b/app/src/modules/simulation/human/instances/humanInstances.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import HumanInstance from './instance/humanInstance'; +import { useSceneContext } from '../../../scene/sceneContext'; + +function HumanInstances() { + const { humanStore } = useSceneContext(); + const { humans } = humanStore(); + + return ( + <> + {humans.map((human: HumanStatus) => ( + + + + ))} + + ) +} + +export default HumanInstances \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx new file mode 100644 index 0000000..d8dc023 --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -0,0 +1,15 @@ +import { useEffect } from 'react' + +function HumanInstance({ human }: { human: HumanStatus }) { + + useEffect(() => { + console.log('human: ', human); + }, [human]) + + return ( + <> + + ) +} + +export default HumanInstance \ 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 e3ade5e..1478255 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom'; import { useVersionContext } from '../../builder/version/versionContext'; function Products() { - const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, layout, productStore } = useSceneContext(); + const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, layout, productStore } = useSceneContext(); const { products, getProductById, addProduct, setProducts } = productStore(); const { selectedProductStore } = useProductContext(); const { setMainProduct } = useMainProduct(); @@ -20,6 +20,7 @@ function Products() { const { addMachine, clearMachines } = machineStore(); const { addConveyor, clearConveyors } = conveyorStore(); const { setCurrentMaterials, clearStorageUnits, updateCurrentLoad, addStorageUnit } = storageUnitStore(); + const { addHuman, clearHumans } = humanStore(); const { isReset } = useResetButtonStore(); const { isPlaying } = usePlayButtonStore(); const { mainProduct } = useMainProduct(); @@ -153,6 +154,20 @@ function Products() { } }, [selectedProduct, products, isReset, isPlaying]); + useEffect(() => { + if (selectedProduct.productUuid) { + const product = getProductById(selectedProduct.productUuid); + if (product) { + clearHumans(); + product.eventDatas.forEach(events => { + if (events.type === 'human') { + addHuman(selectedProduct.productUuid, events); + } + }); + } + } + }, [selectedProduct, products, isReset, isPlaying]); + return ( <> diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 3429393..bb9d91f 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import Vehicles from './vehicle/vehicles'; import Points from './events/points/points'; import Conveyor from './conveyor/conveyor'; @@ -6,6 +6,7 @@ import RoboticArm from './roboticArm/roboticArm'; import Materials from './materials/materials'; import Machine from './machine/machine'; import StorageUnit from './storageUnit/storageUnit'; +import Human from './human/human'; import Simulator from './simulator/simulator'; import Products from './products/products'; import Trigger from './triggers/trigger'; @@ -52,6 +53,8 @@ function Simulation() { + + diff --git a/app/src/modules/simulation/simulator/simulator.tsx b/app/src/modules/simulation/simulator/simulator.tsx index 598d3d8..869ffa9 100644 --- a/app/src/modules/simulation/simulator/simulator.tsx +++ b/app/src/modules/simulation/simulator/simulator.tsx @@ -4,7 +4,6 @@ import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayB import { determineExecutionOrder } from './functions/determineExecutionOrder'; import { useProductContext } from '../products/productContext'; import { useSceneContext } from '../../scene/sceneContext'; -import { useCompareProductDataStore } from '../../../store/builder/store'; function Simulator() { const { selectedProductStore } = useProductContext(); diff --git a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx index 989242b..8f2b3f3 100644 --- a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx +++ b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx @@ -5,20 +5,20 @@ import { useSceneContext } from "../../../scene/sceneContext"; import { useViewSceneStore } from "../../../../store/builder/store"; function VehicleInstances() { - const { vehicleStore } = useSceneContext(); - const { vehicles } = vehicleStore(); - const { viewSceneLabels } = useViewSceneStore(); + const { vehicleStore } = useSceneContext(); + const { vehicles } = vehicleStore(); + const { viewSceneLabels } = useViewSceneStore(); - return ( - <> - {vehicles.map((vehicle: VehicleStatus) => ( - - - {viewSceneLabels && } - - ))} - - ); + return ( + <> + {vehicles.map((vehicle: VehicleStatus) => ( + + + {viewSceneLabels && } + + ))} + + ); } export default VehicleInstances; diff --git a/app/src/store/simulation/useHumanStore.ts b/app/src/store/simulation/useHumanStore.ts new file mode 100644 index 0000000..75b9c7d --- /dev/null +++ b/app/src/store/simulation/useHumanStore.ts @@ -0,0 +1,239 @@ +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; + +interface HumansStore { + humans: HumanStatus[]; + + addHuman: (productUuid: string, event: HumanEventSchema) => void; + removeHuman: (modelUuid: string) => void; + updateHuman: ( + modelUuid: string, + updates: Partial> + ) => void; + clearHumans: () => void; + + setHumanActive: (modelUuid: string, isActive: boolean) => void; + setHumanPicking: (modelUuid: string, isPicking: boolean) => void; + setHumanLoad: (modelUuid: string, load: number) => void; + incrementHumanLoad: (modelUuid: string, incrementBy: number) => void; + decrementHumanLoad: (modelUuid: string, decrementBy: number) => void; + + addCurrentMaterial: (modelUuid: string, materialType: string, materialId: string) => void; + setCurrentMaterials: (modelUuid: string, materials: { materialType: string; materialId: string }[]) => void; + removeLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined; + getLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined; + clearCurrentMaterials: (modelUuid: string) => void; + + setCurrentAction: ( + modelUuid: string, + action: HumanStatus["currentAction"] + ) => void; + + incrementActiveTime: (modelUuid: string, incrementBy: number) => void; + incrementIdleTime: (modelUuid: string, incrementBy: number) => void; + incrementDistanceTraveled: (modelUuid: string, incrementBy: number) => void; + resetTime: (modelUuid: string) => void; + + getHumanById: (modelUuid: string) => HumanStatus | undefined; + getHumansByProduct: (productUuid: string) => HumanStatus[]; + getActiveHumans: () => HumanStatus[]; +} + +export const createHumanStore = () => { + return create()( + immer((set, get) => ({ + humans: [], + + addHuman: (productUuid, event) => { + set((state) => { + const exists = state.humans.some(h => h.modelUuid === event.modelUuid); + if (!exists) { + state.humans.push({ + ...event, + productUuid, + isActive: false, + isPicking: false, + idleTime: 0, + activeTime: 0, + currentLoad: 0, + currentMaterials: [], + distanceTraveled: 0 + }); + } + }); + }, + + removeHuman: (modelUuid) => { + set((state) => { + state.humans = state.humans.filter(h => h.modelUuid !== modelUuid); + }); + }, + + updateHuman: (modelUuid, updates) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + Object.assign(human, updates); + } + }); + }, + + clearHumans: () => { + set((state) => { + state.humans = []; + }); + }, + + setHumanActive: (modelUuid, isActive) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.isActive = isActive; + } + }); + }, + + setHumanPicking: (modelUuid, isPicking) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.isPicking = isPicking; + } + }); + }, + + setHumanLoad: (modelUuid, load) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentLoad = load; + } + }); + }, + + incrementHumanLoad: (modelUuid, incrementBy) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentLoad += incrementBy; + } + }); + }, + + decrementHumanLoad: (modelUuid, decrementBy) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentLoad -= decrementBy; + } + }); + }, + + addCurrentMaterial: (modelUuid, materialType, materialId) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentMaterials.push({ materialType, materialId }); + } + }); + }, + + setCurrentMaterials: (modelUuid, materials) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentMaterials = materials; + } + }); + }, + + removeLastMaterial: (modelUuid) => { + let removed; + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human && human.currentMaterials.length > 0) { + removed = human.currentMaterials.pop(); + } + }); + return removed; + }, + + getLastMaterial: (modelUuid) => { + const human = get().humans.find(h => h.modelUuid === modelUuid); + if (human && human.currentMaterials.length > 0) { + return human.currentMaterials[human.currentMaterials.length - 1]; + } + return undefined; + }, + + clearCurrentMaterials: (modelUuid) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentMaterials = []; + } + }); + }, + + setCurrentAction: (modelUuid, action) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentAction = action; + } + }); + }, + + incrementActiveTime: (modelUuid, incrementBy) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.activeTime += incrementBy; + } + }); + }, + + incrementIdleTime: (modelUuid, incrementBy) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.idleTime += incrementBy; + } + }); + }, + + incrementDistanceTraveled: (modelUuid, incrementBy) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.distanceTraveled += incrementBy; + } + }); + }, + + resetTime: (modelUuid) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.activeTime = 0; + human.idleTime = 0; + } + }); + }, + + getHumanById: (modelUuid) => { + return get().humans.find(h => h.modelUuid === modelUuid); + }, + + getHumansByProduct: (productUuid) => { + return get().humans.filter(h => h.productUuid === productUuid); + }, + + getActiveHumans: () => { + return get().humans.filter(h => h.isActive); + } + })) + ); +}; + +export type HumanStoreType = ReturnType; diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index d5d2f00..1d2f364 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -1,3 +1,4 @@ +// Base Types interface AssetEventSchema { modelUuid: string; modelName: string; @@ -18,69 +19,7 @@ interface TriggerSchema { } | null; } -interface ConveyorPointSchema { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - action: ConveyorAction; -} - -interface VehiclePointSchema { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - action: VehicleAction; -} - -interface RoboticArmPointSchema { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - actions: RoboticArmAction[]; -} - -interface MachinePointSchema { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - action: MachineAction; -} - -interface StoragePointSchema { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - action: StorageAction; -} - -interface ConveyorEventSchema extends AssetEventSchema { - type: "transfer"; - speed: number; - points: ConveyorPointSchema[]; -} - -interface VehicleEventSchema extends AssetEventSchema { - type: "vehicle"; - speed: number; - point: VehiclePointSchema; -} - -interface RoboticArmEventSchema extends AssetEventSchema { - type: "roboticArm"; - speed: number; - point: RoboticArmPointSchema; -} - -interface MachineEventSchema extends AssetEventSchema { - type: "machine"; - point: MachinePointSchema; -} - -interface StorageEventSchema extends AssetEventSchema { - type: "storageUnit"; - point: StoragePointSchema; -} - +// Actions interface ConveyorAction { actionUuid: string; actionName: string; @@ -130,19 +69,100 @@ interface StorageAction { triggers: TriggerSchema[]; } -type Action = ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction; +interface HumanAction { + actionUuid: string; + actionName: string; + actionType: "animation" | "animatedTravel"; + animation: string | null; + loadCapacity: number; + travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } + triggers: TriggerSchema[]; +} -type PointsScheme = ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema; +type Action = ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction; -type EventsSchema = ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema; +// Points +interface ConveyorPointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + action: ConveyorAction; +} -type productsSchema = { - productName: string; - productUuid: string; - eventDatas: EventsSchema[]; -}[] +interface VehiclePointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + action: VehicleAction; +} +interface RoboticArmPointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: RoboticArmAction[]; +} +interface MachinePointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + action: MachineAction; +} + +interface StoragePointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + action: StorageAction; +} + +interface HumanPointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: HumanAction[]; +} + +type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema; + +// Events +interface ConveyorEventSchema extends AssetEventSchema { + type: "transfer"; + speed: number; + points: ConveyorPointSchema[]; +} + +interface VehicleEventSchema extends AssetEventSchema { + type: "vehicle"; + speed: number; + point: VehiclePointSchema; +} + +interface RoboticArmEventSchema extends AssetEventSchema { + type: "roboticArm"; + speed: number; + point: RoboticArmPointSchema; +} + +interface MachineEventSchema extends AssetEventSchema { + type: "machine"; + point: MachinePointSchema; +} + +interface StorageEventSchema extends AssetEventSchema { + type: "storageUnit"; + point: StoragePointSchema; +} + +interface HumanEventSchema extends AssetEventSchema { + type: "human"; + point: HumanPointSchema; +} + +type EventsSchema = | ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema | HumanEventSchema; + +// Statuses interface ConveyorStatus extends ConveyorEventSchema { productUuid: string; isActive: boolean; @@ -197,6 +217,24 @@ interface StorageUnitStatus extends StorageEventSchema { currentMaterials: { materialType: string; materialId: string; }[]; } +interface HumanStatus extends HumanEventSchema { + productUuid: string; + isActive: boolean; + isPicking: boolean; + idleTime: number; + activeTime: number; + currentLoad: number; + currentMaterials: { materialType: string; materialId: string; }[]; + distanceTraveled: number; + currentAction?: { + actionUuid: string; + actionName: string; + materialType?: string | null; + materialId?: string | null; + }; +} + +// Materials interface MaterialSchema { materialId: string; materialName: string; @@ -230,6 +268,14 @@ interface MaterialSchema { type MaterialsSchema = MaterialSchema[]; +// Products +type productsSchema = { + productName: string; + productUuid: string; + eventDatas: EventsSchema[]; +}[]; + +// Material History interface MaterialHistoryEntry { material: MaterialSchema; removedAt: string; From 2f0acbda3cfb69eaf93a567e92b29635e8d6db7b Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 2 Jul 2025 15:41:24 +0530 Subject: [PATCH 02/13] feat: Add human action handling with animation and animated travel capabilities --- .../actionHandler/useAnimatedTravelHandler.ts | 34 ++++++++++++++ .../actionHandler/useAnimationHandler.ts | 34 ++++++++++++++ .../actions/human/useHumanActions.ts | 45 +++++++++++++++++++ .../storageUnit/useStorageUnitActions.ts | 2 +- .../simulation/actions/useActionHandler.ts | 10 ++++- 5 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 app/src/modules/simulation/actions/human/actionHandler/useAnimatedTravelHandler.ts create mode 100644 app/src/modules/simulation/actions/human/actionHandler/useAnimationHandler.ts create mode 100644 app/src/modules/simulation/actions/human/useHumanActions.ts diff --git a/app/src/modules/simulation/actions/human/actionHandler/useAnimatedTravelHandler.ts b/app/src/modules/simulation/actions/human/actionHandler/useAnimatedTravelHandler.ts new file mode 100644 index 0000000..d347f03 --- /dev/null +++ b/app/src/modules/simulation/actions/human/actionHandler/useAnimatedTravelHandler.ts @@ -0,0 +1,34 @@ +import { useCallback } from "react"; +import { useSceneContext } from "../../../../scene/sceneContext"; +import { useProductContext } from "../../../products/productContext"; + +export function useAnimatedTravelHandler() { + const { materialStore, humanStore, productStore } = useSceneContext(); + const { getMaterialById } = materialStore(); + const { } = humanStore(); + const { getModelUuidByActionUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + + const animatedTravelLogStatus = (materialUuid: string, status: string) => { + echo.info(`${materialUuid}, ${status}`); + } + + const handleAnimatedTravel = useCallback((action: HumanAction, materialId?: string) => { + if (!action || action.actionType !== 'animatedTravel' || !materialId) return; + + const material = getMaterialById(materialId); + if (!material) return; + + const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid); + if (!modelUuid) return; + + + animatedTravelLogStatus(material.materialName, `performing animatedTravel`); + + }, [getMaterialById]); + + return { + handleAnimatedTravel, + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/actions/human/actionHandler/useAnimationHandler.ts b/app/src/modules/simulation/actions/human/actionHandler/useAnimationHandler.ts new file mode 100644 index 0000000..eb0ed6a --- /dev/null +++ b/app/src/modules/simulation/actions/human/actionHandler/useAnimationHandler.ts @@ -0,0 +1,34 @@ +import { useCallback } from "react"; +import { useSceneContext } from "../../../../scene/sceneContext"; +import { useProductContext } from "../../../products/productContext"; + +export function useAnimationHandler() { + const { materialStore, humanStore, productStore } = useSceneContext(); + const { getMaterialById } = materialStore(); + const { } = humanStore(); + const { getModelUuidByActionUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + + const animationLogStatus = (materialUuid: string, status: string) => { + echo.info(`${materialUuid}, ${status}`); + } + + const handleAnimation = useCallback((action: HumanAction, materialId?: string) => { + if (!action || action.actionType !== 'animation' || !materialId) return; + + const material = getMaterialById(materialId); + if (!material) return; + + const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid); + if (!modelUuid) return; + + + animationLogStatus(material.materialName, `performing animation`); + + }, [getMaterialById]); + + return { + handleAnimation, + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/actions/human/useHumanActions.ts b/app/src/modules/simulation/actions/human/useHumanActions.ts new file mode 100644 index 0000000..b355ff6 --- /dev/null +++ b/app/src/modules/simulation/actions/human/useHumanActions.ts @@ -0,0 +1,45 @@ +import { useEffect, useCallback } from 'react'; +import { useAnimationHandler } from './actionHandler/useAnimationHandler'; +import { useAnimatedTravelHandler } from './actionHandler/useAnimatedTravelHandler'; + +export function useHumanActions() { + const { handleAnimation } = useAnimationHandler(); + const { handleAnimatedTravel } = useAnimatedTravelHandler(); + + const handleAnimationAction = useCallback((action: HumanAction, materialId: string) => { + handleAnimation(action, materialId); + }, [handleAnimation]); + + const handleAnimatedTravelAction = useCallback((action: HumanAction) => { + handleAnimatedTravel(action); + }, [handleAnimatedTravel]); + + const handleHumanAction = useCallback((action: HumanAction, materialId: string) => { + if (!action) return; + + switch (action.actionType) { + case 'animation': + handleAnimationAction(action, materialId); + break; + case 'animatedTravel': + handleAnimatedTravelAction(action); + break; + default: + console.warn(`Unknown Human action type: ${action.actionType}`); + } + }, [handleAnimationAction, handleAnimatedTravelAction]); + + const cleanup = useCallback(() => { + }, []); + + useEffect(() => { + return () => { + cleanup(); + }; + }, [cleanup]); + + return { + handleHumanAction, + cleanup + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/actions/storageUnit/useStorageUnitActions.ts b/app/src/modules/simulation/actions/storageUnit/useStorageUnitActions.ts index 6f5fb19..151ca20 100644 --- a/app/src/modules/simulation/actions/storageUnit/useStorageUnitActions.ts +++ b/app/src/modules/simulation/actions/storageUnit/useStorageUnitActions.ts @@ -27,7 +27,7 @@ export function useStorageActions() { default: console.warn(`Unknown storage action type: ${action.actionType}`); } - }, [handleStoreAction]); + }, [handleStoreAction, handleRetrieveAction]); const cleanup = useCallback(() => { }, []); diff --git a/app/src/modules/simulation/actions/useActionHandler.ts b/app/src/modules/simulation/actions/useActionHandler.ts index 23f2712..099e200 100644 --- a/app/src/modules/simulation/actions/useActionHandler.ts +++ b/app/src/modules/simulation/actions/useActionHandler.ts @@ -7,6 +7,7 @@ import { useMachineActions } from "./machine/useMachineActions"; import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions"; import { useStorageActions } from "./storageUnit/useStorageUnitActions"; import { useVehicleActions } from "./vehicle/useVehicleActions"; +import { useHumanActions } from "./human/useHumanActions"; import { useCallback, useEffect } from "react"; export function useActionHandler() { @@ -17,6 +18,7 @@ export function useActionHandler() { const { handleRoboticArmAction, cleanup: cleanupRoboticArm } = useRoboticArmActions(); const { handleMachineAction, cleanup: cleanupMachine } = useMachineActions(); const { handleStorageAction, cleanup: cleanupStorage } = useStorageActions(); + const { handleHumanAction, cleanup: cleanupHuman } = useHumanActions(); const handleAction = useCallback((action: Action, materialId?: string) => { if (!action) return; @@ -37,6 +39,9 @@ export function useActionHandler() { case 'store': case 'retrieve': handleStorageAction(action as StorageAction, materialId as string); break; + case 'animation': case 'animatedTravel': + handleHumanAction(action as HumanAction, materialId as string); + break; default: console.warn(`Unknown action type: ${(action as Action).actionType}`); } @@ -44,7 +49,7 @@ export function useActionHandler() { echo.error("Failed to handle action"); console.error("Error handling action:", error); } - }, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction,]); + }, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction, handleHumanAction]); const cleanup = useCallback(() => { cleanupConveyor(); @@ -52,7 +57,8 @@ export function useActionHandler() { cleanupRoboticArm(); cleanupMachine(); cleanupStorage(); - }, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage,]); + cleanupHuman(); + }, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage, cleanupHuman]); useEffect(() => { return () => { From 424df54ff71efc54e25719a4b654fec50ae0411c Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 2 Jul 2025 17:31:17 +0530 Subject: [PATCH 03/13] feat: Enhance asset and human event handling with animation and loop capabilities --- .../properties/AssetProperties.tsx | 229 ++++++++---------- app/src/modules/builder/asset/assetsGroup.tsx | 35 ++- .../builder/asset/functions/addAssetModel.ts | 5 +- .../builder/asset/models/model/model.tsx | 59 ++--- app/src/store/builder/useAssetStore.ts | 21 +- app/src/store/builder/useBuilderStore.ts | 9 + app/src/types/builderTypes.d.ts | 3 +- app/src/types/simulationTypes.d.ts | 1 + 8 files changed, 192 insertions(+), 170 deletions(-) diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index 818bd8a..5cb4b4e 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -6,145 +6,128 @@ import PositionInput from "../customInput/PositionInputs"; import RotationInput from "../customInput/RotationInput"; import { useSelectedFloorItem, useObjectPosition, useObjectRotation } from "../../../../store/builder/store"; import { useSceneContext } from "../../../../modules/scene/sceneContext"; +import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; interface UserData { - id: number; // Unique identifier for the user data - label: string; // Label of the user data field - value: string; // Value of the user data field + id: number; + label: string; + value: string; } const AssetProperties: React.FC = () => { - const [userData, setUserData] = useState([]); // State to track user data - const [nextId, setNextId] = useState(1); // Unique ID for new entries - const { selectedFloorItem } = useSelectedFloorItem(); - const { objectPosition } = useObjectPosition(); - const { objectRotation } = useObjectRotation(); - const { assetStore } = useSceneContext(); - const { assets, setCurrentAnimation } = assetStore() - const [hoveredIndex, setHoveredIndex] = useState(null); - const [isPlaying, setIsplaying] = useState(false); - // Function to handle adding new user data - const handleAddUserData = () => { - const newUserData: UserData = { - id: nextId, - label: `Property ${nextId}`, - value: "", + const [userData, setUserData] = useState([]); + const { selectedFloorItem } = useSelectedFloorItem(); + const { objectPosition } = useObjectPosition(); + const { objectRotation } = useObjectRotation(); + const { assetStore } = useSceneContext(); + const { assets, setCurrentAnimation } = assetStore(); + const { loopAnimation } = useBuilderStore(); + const [hoveredIndex, setHoveredIndex] = useState(null); + + const handleAddUserData = () => { }; - setUserData([...userData, newUserData]); - setNextId(nextId + 1); // Increment the ID for the next entry - }; - // Function to update the value of a user data entry - const handleUserDataChange = (id: number, newValue: string) => { - setUserData((prevUserData) => - prevUserData.map((data) => - data.id === id ? { ...data, value: newValue } : data - ) - ); - }; + const handleUserDataChange = (id: number, newValue: string) => { + }; - // Remove user data - const handleRemoveUserData = (id: number) => { - setUserData((prevUserData) => - prevUserData.filter((data) => data.id !== id) - ); - }; + const handleRemoveUserData = (id: number) => { + }; - const handleAnimationClick = (animation: string) => { - if (selectedFloorItem) { - setCurrentAnimation(selectedFloorItem.uuid, animation, true); + const handleAnimationClick = (animation: string) => { + if (selectedFloorItem) { + setCurrentAnimation(selectedFloorItem.uuid, animation, true, loopAnimation); + } } - } - if (!selectedFloorItem) return null; + if (!selectedFloorItem) return null; - return ( -
- {/* Name */} -
{selectedFloorItem.userData.modelName}
-
- {objectPosition.x && objectPosition.z && - { }} - value1={parseFloat(objectPosition.x.toFixed(5))} - value2={parseFloat(objectPosition.z.toFixed(5))} - /> - } - {objectRotation.y && - { }} - value={parseFloat(objectRotation.y.toFixed(5))} - /> - } -
+ return ( +
+ {/* Name */} +
{selectedFloorItem.userData.modelName}
+
+ {objectPosition && + { }} + value1={parseFloat(objectPosition.x.toFixed(5))} + value2={parseFloat(objectPosition.z.toFixed(5))} + /> + } + {objectRotation && + { }} + value={parseFloat(objectRotation.y.toFixed(5))} + /> + } +
-
-
Render settings
- - -
+
+
Render settings
+ + +
-
-
User Data
- {userData.map((data) => ( -
- handleUserDataChange(data.id, newValue)} // Pass the change handler - /> -
handleRemoveUserData(data.id)} - > - -
-
- ))} +
+
User Data
+ {userData.map((data) => ( +
+ handleUserDataChange(data.id, newValue)} + /> +
handleRemoveUserData(data.id)} + > + +
+
+ ))} - {/* Add new user data */} -
- + Add -
-
-
- {selectedFloorItem.uuid &&
Animations
} - {assets.map((asset) => ( -
- {asset.modelUuid === selectedFloorItem.uuid && - asset.animations && - asset.animations.length > 0 && - asset.animations.map((animation, index) => ( -
-
handleAnimationClick(animation)} - onMouseEnter={() => setHoveredIndex(index)} - onMouseLeave={() => setHoveredIndex(null)} - style={{ - height: "20px", - width: "100%", - borderRadius: "5px", - background: - hoveredIndex === index - ? "#7b4cd3" - : "transparent", - }} - > - {animation.charAt(0).toUpperCase() + - animation.slice(1).toLowerCase()} -
+ {/* Add new user data */} +
+ + Add
- ))} -
- ))} -
-
- ); +
+
+ {selectedFloorItem.uuid &&
Animations
} + {assets.map((asset) => ( +
+ {asset.modelUuid === selectedFloorItem.uuid && + asset.animations && + asset.animations.length > 0 && + asset.animations.map((animation, index) => ( +
+
handleAnimationClick(animation)} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} + style={{ + height: "20px", + width: "100%", + borderRadius: "5px", + background: + hoveredIndex === index + ? "#7b4cd3" + : "transparent", + }} + > + {animation.charAt(0).toUpperCase() + + animation.slice(1).toLowerCase()} +
+
+ ))} +
+ ))} +
+
+ ); }; export default AssetProperties; diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index cb54f87..6fcdc7c 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -1,4 +1,4 @@ - import * as THREE from "three" +import * as THREE from "three" import { useEffect } from 'react' import { getFloorAssets } from '../../../services/factoryBuilder/asset/floorAsset/getFloorItemsApi'; import { useLoadingProgress, useRenameModeStore, useSelectedFloorItem, useSelectedItem, useSocketStore } from '../../../store/builder/store'; @@ -226,7 +226,8 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { modelUuid: item.modelUuid, modelName: item.modelName, position: item.position, - rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", type: "storageUnit", point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), @@ -242,6 +243,36 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { } }; addEvent(storageEvent); + } else if (item.eventData.type === 'Human') { + const humanEvent: HumanEventSchema = { + modelUuid: item.modelUuid, + modelName: item.modelName, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "human", + point: { + uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), + position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], + rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "animation", + animation: null, + loopAnimation: true, + loadCapacity: 1, + travelPoints: { + startPoint: null, + endPoint: null, + }, + triggers: [] + } + ] + } + } + addEvent(humanEvent); } } else { assets.push({ diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 978b45c..9fa104c 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -162,6 +162,7 @@ async function handleModelLoad( // SOCKET if (selectedItem.type) { + console.log('selectedItem: ', selectedItem); const data = PointsCalculator( selectedItem.type, gltf.scene.clone(), @@ -170,7 +171,7 @@ async function handleModelLoad( if (!data || !data.points) return; - const eventData: any = { type: selectedItem.type, }; + const eventData: any = { type: selectedItem.type }; if (selectedItem.type === "Conveyor") { const ConveyorEvent: ConveyorEventSchema = { @@ -378,6 +379,7 @@ async function handleModelLoad( actionName: "Action 1", actionType: "animation", animation: null, + loopAnimation: true, loadCapacity: 1, travelPoints: { startPoint: null, @@ -416,6 +418,7 @@ async function handleModelLoad( userId: userId, }; + console.log('completeData: ', completeData); socket.emit("v1:model-asset:add", completeData); const asset: Asset = { diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index b47fcd7..3af4bde 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -6,7 +6,7 @@ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; import { ThreeEvent, useFrame, useThree } from '@react-three/fiber'; import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; import { AssetBoundingBox } from '../../functions/assetBoundingBox'; -import { CameraControls, Html } from '@react-three/drei'; +import { CameraControls } from '@react-three/drei'; import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore'; import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore'; import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore'; @@ -15,6 +15,7 @@ import { useParams } from 'react-router-dom'; import { getUserData } from '../../../../../functions/getUserData'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useVersionContext } from '../../../version/versionContext'; +import { SkeletonUtils } from 'three-stdlib'; function Model({ asset }: { readonly asset: Asset }) { const { camera, controls, gl } = useThree(); @@ -23,7 +24,7 @@ function Model({ asset }: { readonly asset: Asset }) { const { subModule } = useSubModuleStore(); const { activeModule } = useModuleStore(); const { assetStore, eventStore, productStore } = useSceneContext(); - const { assets, removeAsset, setAnimations } = assetStore(); + const { removeAsset, setAnimations, resetAnimation } = assetStore(); const { setTop } = useTopData(); const { setLeft } = useLeftData(); const { getIsEventInProduct } = productStore(); @@ -33,7 +34,7 @@ function Model({ asset }: { readonly asset: Asset }) { const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); const { socket } = useSocketStore(); const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); - const { setSelectedFloorItem } = useSelectedFloorItem(); + const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); const { limitDistance } = useLimitDistance(); const { renderDistance } = useRenderDistance(); const [isRendered, setIsRendered] = useState(false); @@ -46,13 +47,15 @@ function Model({ asset }: { readonly asset: Asset }) { const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const { userId, organization } = getUserData(); - const [animationNames, setAnimationNames] = useState([]); const mixerRef = useRef(); const actions = useRef<{ [name: string]: THREE.AnimationAction }>({}); useEffect(() => { setDeletableFloorItem(null); - }, [activeModule, toolMode]) + if (selectedFloorItem === null) { + resetAnimation(asset.modelUuid); + } + }, [activeModule, toolMode, selectedFloorItem]) useEffect(() => { const loader = new GLTFLoader(); @@ -62,40 +65,21 @@ function Model({ asset }: { readonly asset: Asset }) { loader.setDRACOLoader(dracoLoader); const loadModel = async () => { try { - // Check Cache - // const assetId = asset.assetId; - // const cachedModel = THREE.Cache.get(assetId); - // if (cachedModel) { - // setGltfScene(cachedModel.scene.clone()); - // calculateBoundingBox(cachedModel.scene); - // return; - // } // Check Cache - // const assetId = asset.assetId; - // console.log('assetId: ', assetId); - // const cachedModel = THREE.Cache.get(assetId); - // console.log('cachedModel: ', cachedModel); - // if (cachedModel) { - // setGltfScene(cachedModel.scene.clone()); - // calculateBoundingBox(cachedModel.scene); - // return; - // } - const assetId = asset.assetId; const cachedModel = THREE.Cache.get(assetId); if (cachedModel) { - const clonedScene = cachedModel.scene.clone(); - clonedScene.animations = cachedModel.animations || []; - setGltfScene(clonedScene); - calculateBoundingBox(clonedScene); - if (cachedModel.animations && clonedScene.animations.length > 0) { - const animationName = clonedScene.animations.map((clip: any) => clip.name); - setAnimationNames(animationName) + const clone: any = SkeletonUtils.clone(cachedModel.scene); + clone.animations = cachedModel.animations || []; + setGltfScene(clone); + calculateBoundingBox(clone); + if (cachedModel.animations && clone.animations.length > 0) { + const animationName = clone.animations.map((clip: any) => clip.name); setAnimations(asset.modelUuid, animationName) - mixerRef.current = new THREE.AnimationMixer(clonedScene); + mixerRef.current = new THREE.AnimationMixer(clone); - clonedScene.animations.forEach((animation: any) => { + clone.animations.forEach((animation: any) => { const action = mixerRef.current!.clipAction(animation); actions.current[animation.name] = action; }); @@ -293,28 +277,27 @@ function Model({ asset }: { readonly asset: Asset }) { clearSelectedAsset() } } + useFrame((_, delta) => { if (mixerRef.current) { mixerRef.current.update(delta); } }); - useEffect(() => { - - if (asset.animationState && asset.animationState.playing) { + if (asset.animationState && asset.animationState.isPlaying) { if (!mixerRef.current) return; Object.values(actions.current).forEach((action) => action.stop()); const action = actions.current[asset.animationState.current]; - if (action && asset.animationState?.playing) { - action.reset().setLoop(THREE.LoopOnce, 1).play(); + if (action && asset.animationState?.isPlaying) { + const loopMode = asset.animationState.loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce; + action.reset().setLoop(loopMode, loopMode === THREE.LoopRepeat ? Infinity : 1).play(); } } else { Object.values(actions.current).forEach((action) => action.stop()); } - }, [asset.animationState]) return ( diff --git a/app/src/store/builder/useAssetStore.ts b/app/src/store/builder/useAssetStore.ts index fec16b4..a9111f4 100644 --- a/app/src/store/builder/useAssetStore.ts +++ b/app/src/store/builder/useAssetStore.ts @@ -22,7 +22,8 @@ interface AssetsStore { // Animation controls setAnimations: (modelUuid: string, animations: string[]) => void; - setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean) => void; + setCurrentAnimation: (modelUuid: string, current: string, isisPlaying: boolean, loopAnimation: boolean) => void; + resetAnimation: (modelUuid: string) => void; addAnimation: (modelUuid: string, animation: string) => void; removeAnimation: (modelUuid: string, animation: string) => void; @@ -149,18 +150,28 @@ export const createAssetStore = () => { if (asset) { asset.animations = animations; if (!asset.animationState) { - asset.animationState = { current: '', playing: false }; + asset.animationState = { current: '', isPlaying: false, loopAnimation: true }; } } }); }, - setCurrentAnimation: (modelUuid, current, isPlaying) => { + setCurrentAnimation: (modelUuid, current, isisPlaying, loopAnimation) => { set((state) => { const asset = state.assets.find(a => a.modelUuid === modelUuid); if (asset?.animationState) { asset.animationState.current = current; - asset.animationState.playing = isPlaying; + asset.animationState.isPlaying = isisPlaying; + asset.animationState.loopAnimation = loopAnimation; + } + }); + }, + + resetAnimation: (modelUuid) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset?.animationState) { + asset.animationState = { current: '', isPlaying: false, loopAnimation: true }; } }); }, @@ -184,7 +195,7 @@ export const createAssetStore = () => { if (asset?.animations) { asset.animations = asset.animations.filter(a => a !== animation); if (asset.animationState?.current === animation) { - asset.animationState.playing = false; + asset.animationState.isPlaying = false; asset.animationState.current = ''; } } diff --git a/app/src/store/builder/useBuilderStore.ts b/app/src/store/builder/useBuilderStore.ts index 6147c0c..5be4e3b 100644 --- a/app/src/store/builder/useBuilderStore.ts +++ b/app/src/store/builder/useBuilderStore.ts @@ -15,6 +15,7 @@ interface BuilderState { // Floor Asset selectedFloorAsset: Object3D | null; + loopAnimation: boolean; // Wall Settings selectedWall: Object3D | null; @@ -64,6 +65,7 @@ interface BuilderState { // Setters - Floor Asset setSelectedFloorAsset: (asset: Object3D | null) => void; + setLoopAnimation: (loop: boolean) => void; // Setters - Wall setSelectedWall: (wall: Object3D | null) => void; @@ -118,6 +120,7 @@ export const useBuilderStore = create()( deletableWallAsset: null, selectedFloorAsset: null, + loopAnimation: true, selectedWall: null, wallThickness: 0.5, @@ -197,6 +200,12 @@ export const useBuilderStore = create()( }); }, + setLoopAnimation(loopAnimation: boolean) { + set((state) => { + state.loopAnimation = loopAnimation; + }); + }, + // === Setters: Wall === setSelectedWall: (wall: Object3D | null) => { diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index de7586a..a8b0175 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -26,7 +26,8 @@ interface Asset { animations?: string[]; animationState?: { current: string; - playing: boolean; + isPlaying: boolean; + loopAnimation: boolean; }; eventData?: { type: string; diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 1d2f364..26802f8 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -74,6 +74,7 @@ interface HumanAction { actionName: string; actionType: "animation" | "animatedTravel"; animation: string | null; + loopAnimation: boolean; loadCapacity: number; travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } triggers: TriggerSchema[]; From b5c69f3335d8e6f7914b0109343a39dbdb004a9b Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 3 Jul 2025 12:09:31 +0530 Subject: [PATCH 04/13] feat: Add human mechanics and event handling, including UI components and action management --- .../eventProperties/EventProperties.tsx | 4 + .../mechanics/humanMechanics.tsx | 373 ++++++++++++++++++ .../eventProperties/trigger/Trigger.tsx | 26 +- app/src/modules/builder/asset/assetsGroup.tsx | 1 + .../builder/asset/functions/addAssetModel.ts | 1 + .../functions/handleAddEventToProduct.ts | 2 +- app/src/modules/simulation/human/human.tsx | 21 + .../instances/instance/humanInstance.tsx | 4 + .../human/instances/instance/humanUi.tsx | 10 + app/src/modules/simulation/spatialUI/temp.md | 0 .../products/UpsertProductOrEventApi.ts | 61 ++- app/src/store/simulation/useProductStore.ts | 13 +- app/src/types/simulationTypes.d.ts | 1 + 13 files changed, 469 insertions(+), 48 deletions(-) create mode 100644 app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx create mode 100644 app/src/modules/simulation/human/instances/instance/humanUi.tsx delete mode 100644 app/src/modules/simulation/spatialUI/temp.md diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx index ed799e5..ed9ef9f 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx @@ -8,6 +8,7 @@ import VehicleMechanics from "./mechanics/vehicleMechanics"; import RoboticArmMechanics from "./mechanics/roboticArmMechanics"; import MachineMechanics from "./mechanics/machineMechanics"; import StorageMechanics from "./mechanics/storageMechanics"; +import HumanMechanics from "./mechanics/humanMechanics"; import { AddIcon } from "../../../../icons/ExportCommonIcons"; import { handleAddEventToProduct } from "../../../../../modules/simulation/events/points/functions/handleAddEventToProduct"; import { useProductContext } from "../../../../../modules/simulation/products/productContext"; @@ -61,6 +62,8 @@ const EventProperties: React.FC = () => { return "machine"; case "storageUnit": return "storageUnit"; + case "human": + return "human"; default: return null; } @@ -80,6 +83,7 @@ const EventProperties: React.FC = () => { {assetType === "roboticArm" && } {assetType === "machine" && } {assetType === "storageUnit" && } + {assetType === "human" && } )} {!currentEventData && selectedEventSphere && ( diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx new file mode 100644 index 0000000..8c5a348 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -0,0 +1,373 @@ +import { useEffect, useState } from "react"; +import { MathUtils } from "three"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import Trigger from "../trigger/Trigger"; +import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; +import PickAndPlaceAction from "../actions/PickAndPlaceAction"; +import ActionsList from "../components/ActionsList"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; +import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; +import { useParams } from "react-router-dom"; +import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; +import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; +import InputToggle from "../../../../../ui/inputs/InputToggle"; + +function HumanMechanics() { + const [activeOption, setActiveOption] = useState<"animation" | "animatedTravel">("animation"); + const [activeAnimationOption, setActiveAnimationOption] = useState(""); + const [animationOptions, setAnimationOptions] = useState([]); + const [speed, setSpeed] = useState("0.5"); + const [currentAction, setCurrentAction] = useState(); + const [isLoopAnimation, setIsLoopAnimation] = useState(false); + const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); + const { productStore, assetStore } = useSceneContext(); + const { getAssetById } = assetStore(); + const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction, addAction, removeAction } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as HumanPointSchema | undefined; + + if (point?.actions) { + setSelectedPointData(point); + if (point.actions.length > 0) { + setSelectedAction(point.actions[0].actionUuid, point.actions[0].actionName); + const asset = getAssetById(selectedEventData.data.modelUuid); + if (asset && asset.animations) { + setAnimationOptions(asset.animations) + } + } + } + } else { + clearSelectedAction(); + } + }, [selectedEventData, selectedProduct]); + + useEffect(() => { + if (selectedEventData && selectedProduct.productUuid) { + const event = getEventByModelUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid + ) as HumanEventSchema | undefined; + + if (event?.speed !== undefined) { + setSpeed(event.speed.toString()); + } + + const point = getPointByUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as HumanPointSchema | undefined; + + if (point?.actions) { + setSelectedPointData(point); + const action = point.actions.find((a) => a.actionUuid === selectedAction.actionId); + if (action) { + setCurrentAction(action); + setIsLoopAnimation(action.loopAnimation ?? false); + setActiveOption(action.actionType as "animation" | "animatedTravel"); + setActiveAnimationOption(action.animation || '') + } + } + } else { + clearSelectedAction(); + setCurrentAction(undefined); + setSpeed("0.5"); + } + }, [selectedEventData, selectedProduct, selectedAction]); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName, + productUuid, + projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || "", + }); + }; + + const handleSelectActionType = (actionType: string) => { + if (!selectedAction.actionId) return; + setActiveOption(actionType as "animation" | "animatedTravel"); + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { actionType: actionType as "animation" | "animatedTravel" } + ); + + if (selectedPointData) { + const updatedActions = selectedPointData.actions.map((action) => + action.actionUuid === selectedAction.actionId + ? { ...action, actionType: actionType as "animation" | "animatedTravel" } + : action + ); + setSelectedPointData({ ...selectedPointData, actions: updatedActions }); + } + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + } + + const handleSelectAnimation = (animationOption: string) => { + if (!selectedAction.actionId) return; + setActiveAnimationOption(animationOption); + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { animation: animationOption } + ); + + if (selectedPointData) { + const updatedActions = selectedPointData.actions.map((action) => + action.actionUuid === selectedAction.actionId + ? { ...action, animation: animationOption } + : action + ); + setSelectedPointData({ ...selectedPointData, actions: updatedActions }); + } + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + } + + const handleLoopAnimationChange = () => { + if (!selectedAction.actionId || !currentAction) return; + + const updatedValue = !isLoopAnimation; + setIsLoopAnimation(updatedValue); + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { loopAnimation: updatedValue } + ); + + if (selectedPointData) { + const updatedActions = selectedPointData.actions.map((action) => + action.actionUuid === selectedAction.actionId + ? { ...action, loopAnimation: updatedValue } + : action + ); + setSelectedPointData({ ...selectedPointData, actions: updatedActions }); + setCurrentAction(updatedActions.find((a) => a.actionUuid === selectedAction.actionId)); + } + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + }; + + const handleRenameAction = (newName: string) => { + if (!selectedAction.actionId) return; + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { actionName: newName } + ); + + if (selectedPointData) { + const updatedActions = selectedPointData.actions.map((action) => + action.actionUuid === selectedAction.actionId + ? { ...action, actionName: newName } + : action + ); + setSelectedPointData({ ...selectedPointData, actions: updatedActions }); + } + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + }; + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + + const event = updateEvent( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + { speed: parseFloat(value) } + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + }; + + const handleClearPoints = () => { + if (!selectedAction.actionId || !selectedPointData) return; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { + travelPoints: { + startPoint: null, + endPoint: null, + }, + } + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + }; + + const handleAddAction = () => { + if (!selectedEventData || !selectedPointData) return; + + const newAction: HumanAction = { + actionUuid: MathUtils.generateUUID(), + actionName: `Action ${selectedPointData.actions.length + 1}`, + actionType: "animation", + animation: null, + loadCapacity: 1, + loopAnimation: true, + travelPoints: { + startPoint: null, + endPoint: null, + }, + triggers: [], + }; + + const event = addAction( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint, + newAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + const updatedPoint = { ...selectedPointData, actions: [...selectedPointData.actions, newAction] }; + setSelectedPointData(updatedPoint); + setSelectedAction(newAction.actionUuid, newAction.actionName); + }; + + const handleDeleteAction = (actionUuid: string) => { + if (!selectedPointData) return; + + const event = removeAction(selectedProduct.productUuid, actionUuid); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + const index = selectedPointData.actions.findIndex((a) => a.actionUuid === actionUuid); + const newActions = selectedPointData.actions.filter((a) => a.actionUuid !== actionUuid); + const updatedPoint = { ...selectedPointData, actions: newActions }; + setSelectedPointData(updatedPoint); + + if (selectedAction.actionId === actionUuid) { + const nextAction = newActions[index] || newActions[index - 1]; + if (nextAction) { + setSelectedAction(nextAction.actionUuid, nextAction.actionName); + } else { + clearSelectedAction(); + } + } + }; + + const availableActions = { + defaultOption: "animatedTravel", + options: ["animation", "animatedTravel"], + }; + + return ( + <> +
+
+
+ { }} + onChange={handleSpeedChange} + /> +
+
+
+
+ + + {selectedAction.actionId && currentAction && ( +
+
+ +
+
+ + + + {activeOption === 'animatedTravel' && + + } +
+
+ +
+
+ )} +
+ + ); +} + +export default HumanMechanics; 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 4ecb1e9..eb4b0ac 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx @@ -13,7 +13,7 @@ import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; type TriggerProps = { selectedPointData?: PointsScheme | undefined; - type?: "Conveyor" | "Vehicle" | "RoboticArm" | "Machine" | "StorageUnit"; + type?: "Conveyor" | "Vehicle" | "RoboticArm" | "Machine" | "StorageUnit" | "Human"; }; const Trigger = ({ selectedPointData, type }: TriggerProps) => { @@ -38,7 +38,7 @@ 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" && selectedAction.actionId) { + } else if ((type === "RoboticArm" || type === "Human") && selectedAction.actionId) { actionUuid = selectedAction.actionId; } @@ -365,18 +365,16 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => { } /> - {triggers.length > 1 && ( - - )} +
))} diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index 6fcdc7c..461bf50 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -251,6 +251,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "human", + speed: 1, point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 9fa104c..47274a2 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -369,6 +369,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "human", + speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), position: [data.points[0].x, data.points[0].y, data.points[0].z], diff --git a/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts b/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts index 0ba13a7..9439c3c 100644 --- a/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts +++ b/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts @@ -30,7 +30,7 @@ export const handleAddEventToProduct = ({ projectId: projectId || '', eventDatas: event }).then((data) => { - // console.log(data); + console.log(data); }) if (clearSelectedAsset) { diff --git a/app/src/modules/simulation/human/human.tsx b/app/src/modules/simulation/human/human.tsx index 30608d4..6a552c6 100644 --- a/app/src/modules/simulation/human/human.tsx +++ b/app/src/modules/simulation/human/human.tsx @@ -1,6 +1,27 @@ import HumanInstances from './instances/humanInstances' +import { useEffect, useState } from "react"; +import { useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { useSceneContext } from "../../scene/sceneContext"; function Human() { + const { humanStore } = useSceneContext(); + const { getHumanById } = humanStore(); + const { selectedEventSphere } = useSelectedEventSphere(); + const { isPlaying } = usePlayButtonStore(); + const [isHumanSelected, setIsHumanSelected] = useState(false); + + useEffect(() => { + if (selectedEventSphere) { + const selectedVehicle = getHumanById(selectedEventSphere.userData.modelUuid); + if (selectedVehicle) { + setIsHumanSelected(true); + } else { + setIsHumanSelected(false); + } + } + }, [getHumanById, selectedEventSphere]) + return ( <> diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index d8dc023..89e2b10 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -1,4 +1,5 @@ import { useEffect } from 'react' +import HumanUi from './humanUi'; function HumanInstance({ human }: { human: HumanStatus }) { @@ -8,6 +9,9 @@ function HumanInstance({ human }: { human: HumanStatus }) { return ( <> + + + ) } diff --git a/app/src/modules/simulation/human/instances/instance/humanUi.tsx b/app/src/modules/simulation/human/instances/instance/humanUi.tsx new file mode 100644 index 0000000..7a2bb9c --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/humanUi.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +function HumanUi({ human }: { human: HumanStatus }) { + return ( + <> + + ) +} + +export default HumanUi \ No newline at end of file diff --git a/app/src/modules/simulation/spatialUI/temp.md b/app/src/modules/simulation/spatialUI/temp.md deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/services/simulation/products/UpsertProductOrEventApi.ts b/app/src/services/simulation/products/UpsertProductOrEventApi.ts index 69b6c9e..2382762 100644 --- a/app/src/services/simulation/products/UpsertProductOrEventApi.ts +++ b/app/src/services/simulation/products/UpsertProductOrEventApi.ts @@ -1,38 +1,37 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; export const upsertProductOrEventApi = async (body: any) => { - try { - const response = await fetch( - `${url_Backend_dwinzo}/api/V1/ProductUpsert`, - { - method: "POST", - headers: { - Authorization: "Bearer ", - "Content-Type": "application/json", - token: localStorage.getItem("token") || "", - refresh_token: localStorage.getItem("refreshToken") || "", - }, - body: JSON.stringify(body), - } - ); - const newAccessToken = response.headers.get("x-access-token"); - if (newAccessToken) { - //console.log("New token received:", newAccessToken); - localStorage.setItem("token", newAccessToken); - } + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/V1/ProductUpsert`, + { + method: "POST", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + body: JSON.stringify(body), + } + ); + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + localStorage.setItem("token", newAccessToken); + } - if (!response.ok) { - console.error("Failed to add product or event"); - } + if (!response.ok) { + console.error("Failed to add product or event"); + } - const result = await response.json(); - return result; - } catch (error) { - echo.error("Failed to upsert product Or eventApi"); - if (error instanceof Error) { - console.log(error.message); - } else { - console.log("An unknown error occurred"); + const result = await response.json(); + return result; + } catch (error) { + echo.error("Failed to upsert product Or eventApi"); + if (error instanceof Error) { + console.log(error.message); + } else { + console.log("An unknown error occurred"); + } } - } }; diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 695559d..18d636b 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -32,13 +32,13 @@ type ProductsStore = { productUuid: string, modelUuid: string, pointUuid: string, - action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] + action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0] ) => EventsSchema | undefined; removeAction: (productUuid: string, actionUuid: string) => EventsSchema | undefined; updateAction: ( productUuid: string, actionUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Trigger-level actionss @@ -276,6 +276,15 @@ export const createProductStore = () => { return; } } + } else if (event.type === "human") { + if ('actions' in point) { + const index = point.actions.findIndex((a: any) => a.actionUuid === actionUuid); + if (index !== -1) { + point.actions.splice(index, 1); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } } else if ('action' in point && point.action?.actionUuid === actionUuid) { point.action = undefined; updatedEvent = JSON.parse(JSON.stringify(event)); diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 26802f8..be7cf16 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -158,6 +158,7 @@ interface StorageEventSchema extends AssetEventSchema { interface HumanEventSchema extends AssetEventSchema { type: "human"; + speed: number; point: HumanPointSchema; } From 98f4d48db292017ee3029f677bad0dbbac8b3e59 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 3 Jul 2025 12:11:58 +0530 Subject: [PATCH 05/13] feat: Refactor human action handling to support animated travel and streamline action structure --- .../mechanics/humanMechanics.tsx | 226 ++++-------------- app/src/modules/builder/asset/assetsGroup.tsx | 27 +-- .../builder/asset/functions/addAssetModel.ts | 27 +-- .../actionHandler/useAnimationHandler.ts | 34 --- .../actions/human/useHumanActions.ts | 11 +- .../simulation/actions/useActionHandler.ts | 2 +- .../triggerConnections/triggerConnector.tsx | 6 +- app/src/store/simulation/useHumanStore.ts | 14 -- app/src/store/simulation/useProductStore.ts | 4 +- app/src/types/simulationTypes.d.ts | 6 +- 10 files changed, 75 insertions(+), 282 deletions(-) delete mode 100644 app/src/modules/simulation/actions/human/actionHandler/useAnimationHandler.ts diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index 8c5a348..d07022a 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -1,31 +1,25 @@ import { useEffect, useState } from "react"; -import { MathUtils } from "three"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; import PickAndPlaceAction from "../actions/PickAndPlaceAction"; -import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; import { useParams } from "react-router-dom"; import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; -import InputToggle from "../../../../../ui/inputs/InputToggle"; +import ActionsList from "../components/ActionsList"; function HumanMechanics() { - const [activeOption, setActiveOption] = useState<"animation" | "animatedTravel">("animation"); - const [activeAnimationOption, setActiveAnimationOption] = useState(""); - const [animationOptions, setAnimationOptions] = useState([]); + const [activeOption, setActiveOption] = useState<"animatedTravel">("animatedTravel"); const [speed, setSpeed] = useState("0.5"); const [currentAction, setCurrentAction] = useState(); - const [isLoopAnimation, setIsLoopAnimation] = useState(false); const [selectedPointData, setSelectedPointData] = useState(); const { selectedEventData } = useSelectedEventData(); - const { productStore, assetStore } = useSceneContext(); - const { getAssetById } = assetStore(); - const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction, addAction, removeAction } = productStore(); + const { productStore } = useSceneContext(); + const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); @@ -41,15 +35,10 @@ function HumanMechanics() { selectedEventData.selectedPoint ) as HumanPointSchema | undefined; - if (point?.actions) { + if (point?.action) { setSelectedPointData(point); - if (point.actions.length > 0) { - setSelectedAction(point.actions[0].actionUuid, point.actions[0].actionName); - const asset = getAssetById(selectedEventData.data.modelUuid); - if (asset && asset.animations) { - setAnimationOptions(asset.animations) - } - } + const action = point.action; + setSelectedAction(action.actionUuid, action.actionName); } } else { clearSelectedAction(); @@ -73,15 +62,11 @@ function HumanMechanics() { selectedEventData.selectedPoint ) as HumanPointSchema | undefined; - if (point?.actions) { + if (point?.action) { setSelectedPointData(point); - const action = point.actions.find((a) => a.actionUuid === selectedAction.actionId); - if (action) { - setCurrentAction(action); - setIsLoopAnimation(action.loopAnimation ?? false); - setActiveOption(action.actionType as "animation" | "animatedTravel"); - setActiveAnimationOption(action.animation || '') - } + const action = point.action; + setCurrentAction(action); + setActiveOption(action.actionType as "animatedTravel"); } } else { clearSelectedAction(); @@ -106,73 +91,18 @@ function HumanMechanics() { }; const handleSelectActionType = (actionType: string) => { - if (!selectedAction.actionId) return; - setActiveOption(actionType as "animation" | "animatedTravel"); + if (!currentAction) return; + setActiveOption(actionType as "animatedTravel"); const event = updateAction( selectedProduct.productUuid, - selectedAction.actionId, - { actionType: actionType as "animation" | "animatedTravel" } + currentAction.actionUuid, + { actionType: actionType as "animatedTravel" } ); - if (selectedPointData) { - const updatedActions = selectedPointData.actions.map((action) => - action.actionUuid === selectedAction.actionId - ? { ...action, actionType: actionType as "animation" | "animatedTravel" } - : action - ); - setSelectedPointData({ ...selectedPointData, actions: updatedActions }); - } - - if (event) { - updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); - } - } - - const handleSelectAnimation = (animationOption: string) => { - if (!selectedAction.actionId) return; - setActiveAnimationOption(animationOption); - - const event = updateAction( - selectedProduct.productUuid, - selectedAction.actionId, - { animation: animationOption } - ); - - if (selectedPointData) { - const updatedActions = selectedPointData.actions.map((action) => - action.actionUuid === selectedAction.actionId - ? { ...action, animation: animationOption } - : action - ); - setSelectedPointData({ ...selectedPointData, actions: updatedActions }); - } - - if (event) { - updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); - } - } - - const handleLoopAnimationChange = () => { - if (!selectedAction.actionId || !currentAction) return; - - const updatedValue = !isLoopAnimation; - setIsLoopAnimation(updatedValue); - - const event = updateAction( - selectedProduct.productUuid, - selectedAction.actionId, - { loopAnimation: updatedValue } - ); - - if (selectedPointData) { - const updatedActions = selectedPointData.actions.map((action) => - action.actionUuid === selectedAction.actionId - ? { ...action, loopAnimation: updatedValue } - : action - ); - setSelectedPointData({ ...selectedPointData, actions: updatedActions }); - setCurrentAction(updatedActions.find((a) => a.actionUuid === selectedAction.actionId)); + if (event && selectedPointData) { + const updatedAction = { ...selectedPointData.action, actionType: actionType as "animatedTravel" }; + setSelectedPointData({ ...selectedPointData, action: updatedAction }); } if (event) { @@ -181,20 +111,17 @@ function HumanMechanics() { }; const handleRenameAction = (newName: string) => { - if (!selectedAction.actionId) return; + if (!currentAction) return; + const event = updateAction( selectedProduct.productUuid, - selectedAction.actionId, + currentAction.actionUuid, { actionName: newName } ); - if (selectedPointData) { - const updatedActions = selectedPointData.actions.map((action) => - action.actionUuid === selectedAction.actionId - ? { ...action, actionName: newName } - : action - ); - setSelectedPointData({ ...selectedPointData, actions: updatedActions }); + if (event && selectedPointData) { + const updatedAction = { ...selectedPointData.action, actionName: newName }; + setSelectedPointData({ ...selectedPointData, action: updatedAction }); } if (event) { @@ -217,11 +144,11 @@ function HumanMechanics() { }; const handleClearPoints = () => { - if (!selectedAction.actionId || !selectedPointData) return; + if (!currentAction) return; const event = updateAction( selectedProduct.productUuid, - selectedAction.actionId, + currentAction.actionUuid, { travelPoints: { startPoint: null, @@ -235,66 +162,9 @@ function HumanMechanics() { } }; - const handleAddAction = () => { - if (!selectedEventData || !selectedPointData) return; - - const newAction: HumanAction = { - actionUuid: MathUtils.generateUUID(), - actionName: `Action ${selectedPointData.actions.length + 1}`, - actionType: "animation", - animation: null, - loadCapacity: 1, - loopAnimation: true, - travelPoints: { - startPoint: null, - endPoint: null, - }, - triggers: [], - }; - - const event = addAction( - selectedProduct.productUuid, - selectedEventData.data.modelUuid, - selectedEventData.selectedPoint, - newAction - ); - - if (event) { - updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); - } - - const updatedPoint = { ...selectedPointData, actions: [...selectedPointData.actions, newAction] }; - setSelectedPointData(updatedPoint); - setSelectedAction(newAction.actionUuid, newAction.actionName); - }; - - const handleDeleteAction = (actionUuid: string) => { - if (!selectedPointData) return; - - const event = removeAction(selectedProduct.productUuid, actionUuid); - - if (event) { - updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); - } - - const index = selectedPointData.actions.findIndex((a) => a.actionUuid === actionUuid); - const newActions = selectedPointData.actions.filter((a) => a.actionUuid !== actionUuid); - const updatedPoint = { ...selectedPointData, actions: newActions }; - setSelectedPointData(updatedPoint); - - if (selectedAction.actionId === actionUuid) { - const nextAction = newActions[index] || newActions[index - 1]; - if (nextAction) { - setSelectedAction(nextAction.actionUuid, nextAction.actionName); - } else { - clearSelectedAction(); - } - } - }; - const availableActions = { defaultOption: "animatedTravel", - options: ["animation", "animatedTravel"], + options: ["animatedTravel"], }; return ( @@ -316,19 +186,20 @@ function HumanMechanics() { -
- - {selectedAction.actionId && currentAction && ( + {currentAction && ( +
+ + { }} + handleDeleteAction={() => { }} + />
@@ -340,32 +211,19 @@ function HumanMechanics() { onSelect={handleSelectActionType} disabled={true} /> - - - {activeOption === 'animatedTravel' && + {activeOption === 'animatedTravel' && ( - } + )}
- )} -
+
+ )} ); } diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index 461bf50..fbc2738 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -256,21 +256,18 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], - actions: [ - { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "animation", - animation: null, - loopAnimation: true, - loadCapacity: 1, - travelPoints: { - startPoint: null, - endPoint: null, - }, - triggers: [] - } - ] + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "animatedTravel", + loadCapacity: 1, + travelPoints: { + startPoint: null, + endPoint: null, + }, + triggers: [] + } + } } addEvent(humanEvent); diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 47274a2..471314f 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -374,21 +374,18 @@ async function handleModelLoad( uuid: THREE.MathUtils.generateUUID(), position: [data.points[0].x, data.points[0].y, data.points[0].z], rotation: [0, 0, 0], - actions: [ - { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "animation", - animation: null, - loopAnimation: true, - loadCapacity: 1, - travelPoints: { - startPoint: null, - endPoint: null, - }, - triggers: [] - } - ] + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "animatedTravel", + loadCapacity: 1, + travelPoints: { + startPoint: null, + endPoint: null, + }, + triggers: [] + } + } } addEvent(humanEvent); diff --git a/app/src/modules/simulation/actions/human/actionHandler/useAnimationHandler.ts b/app/src/modules/simulation/actions/human/actionHandler/useAnimationHandler.ts deleted file mode 100644 index eb0ed6a..0000000 --- a/app/src/modules/simulation/actions/human/actionHandler/useAnimationHandler.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useCallback } from "react"; -import { useSceneContext } from "../../../../scene/sceneContext"; -import { useProductContext } from "../../../products/productContext"; - -export function useAnimationHandler() { - const { materialStore, humanStore, productStore } = useSceneContext(); - const { getMaterialById } = materialStore(); - const { } = humanStore(); - const { getModelUuidByActionUuid } = productStore(); - const { selectedProductStore } = useProductContext(); - const { selectedProduct } = selectedProductStore(); - - const animationLogStatus = (materialUuid: string, status: string) => { - echo.info(`${materialUuid}, ${status}`); - } - - const handleAnimation = useCallback((action: HumanAction, materialId?: string) => { - if (!action || action.actionType !== 'animation' || !materialId) return; - - const material = getMaterialById(materialId); - if (!material) return; - - const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid); - if (!modelUuid) return; - - - animationLogStatus(material.materialName, `performing animation`); - - }, [getMaterialById]); - - return { - handleAnimation, - }; -} \ No newline at end of file diff --git a/app/src/modules/simulation/actions/human/useHumanActions.ts b/app/src/modules/simulation/actions/human/useHumanActions.ts index b355ff6..96e40b0 100644 --- a/app/src/modules/simulation/actions/human/useHumanActions.ts +++ b/app/src/modules/simulation/actions/human/useHumanActions.ts @@ -1,15 +1,9 @@ import { useEffect, useCallback } from 'react'; -import { useAnimationHandler } from './actionHandler/useAnimationHandler'; import { useAnimatedTravelHandler } from './actionHandler/useAnimatedTravelHandler'; export function useHumanActions() { - const { handleAnimation } = useAnimationHandler(); const { handleAnimatedTravel } = useAnimatedTravelHandler(); - const handleAnimationAction = useCallback((action: HumanAction, materialId: string) => { - handleAnimation(action, materialId); - }, [handleAnimation]); - const handleAnimatedTravelAction = useCallback((action: HumanAction) => { handleAnimatedTravel(action); }, [handleAnimatedTravel]); @@ -18,16 +12,13 @@ export function useHumanActions() { if (!action) return; switch (action.actionType) { - case 'animation': - handleAnimationAction(action, materialId); - break; case 'animatedTravel': handleAnimatedTravelAction(action); break; default: console.warn(`Unknown Human action type: ${action.actionType}`); } - }, [handleAnimationAction, handleAnimatedTravelAction]); + }, [handleAnimatedTravelAction]); const cleanup = useCallback(() => { }, []); diff --git a/app/src/modules/simulation/actions/useActionHandler.ts b/app/src/modules/simulation/actions/useActionHandler.ts index 099e200..aef8d39 100644 --- a/app/src/modules/simulation/actions/useActionHandler.ts +++ b/app/src/modules/simulation/actions/useActionHandler.ts @@ -39,7 +39,7 @@ export function useActionHandler() { case 'store': case 'retrieve': handleStorageAction(action as StorageAction, materialId as string); break; - case 'animation': case 'animatedTravel': + case 'animatedTravel': handleHumanAction(action as HumanAction, materialId as string); break; default: diff --git a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx index 918007a..ec7406c 100644 --- a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx +++ b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx @@ -155,8 +155,8 @@ function TriggerConnector() { // Handle Human point else if (event.type === "human" && 'point' in event) { const point = event.point; - point.actions?.forEach(action => { - action.triggers?.forEach(trigger => { + if (point.action?.triggers) { + point.action.triggers.forEach(trigger => { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, @@ -166,7 +166,7 @@ function TriggerConnector() { }); } }); - }); + } } }); diff --git a/app/src/store/simulation/useHumanStore.ts b/app/src/store/simulation/useHumanStore.ts index 75b9c7d..a7ed7d5 100644 --- a/app/src/store/simulation/useHumanStore.ts +++ b/app/src/store/simulation/useHumanStore.ts @@ -24,11 +24,6 @@ interface HumansStore { getLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined; clearCurrentMaterials: (modelUuid: string) => void; - setCurrentAction: ( - modelUuid: string, - action: HumanStatus["currentAction"] - ) => void; - incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementIdleTime: (modelUuid: string, incrementBy: number) => void; incrementDistanceTraveled: (modelUuid: string, incrementBy: number) => void; @@ -175,15 +170,6 @@ export const createHumanStore = () => { }); }, - setCurrentAction: (modelUuid, action) => { - set((state) => { - const human = state.humans.find(h => h.modelUuid === modelUuid); - if (human) { - human.currentAction = action; - } - }); - }, - incrementActiveTime: (modelUuid, incrementBy) => { set((state) => { const human = state.humans.find(h => h.modelUuid === modelUuid); diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 18d636b..3b483ea 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -32,13 +32,13 @@ type ProductsStore = { productUuid: string, modelUuid: string, pointUuid: string, - action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0] + action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['action'] ) => EventsSchema | undefined; removeAction: (productUuid: string, actionUuid: string) => EventsSchema | undefined; updateAction: ( productUuid: string, actionUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Trigger-level actionss diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index be7cf16..2192101 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -72,9 +72,7 @@ interface StorageAction { interface HumanAction { actionUuid: string; actionName: string; - actionType: "animation" | "animatedTravel"; - animation: string | null; - loopAnimation: boolean; + actionType: "animatedTravel"; loadCapacity: number; travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } triggers: TriggerSchema[]; @@ -122,7 +120,7 @@ interface HumanPointSchema { uuid: string; position: [number, number, number]; rotation: [number, number, number]; - actions: HumanAction[]; + action: HumanAction; } type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema; From eb5683eadc18ca0786ba982928bbe5a862033bb2 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 3 Jul 2025 14:23:57 +0530 Subject: [PATCH 06/13] feat: Refactor human action handling to replace animatedTravel with worker actions and enhance animation management --- .../components/AnimationList.tsx | 104 ++++ .../mechanics/humanMechanics.tsx | 491 +++++++++++++++--- app/src/components/ui/inputs/RenameInput.tsx | 13 +- app/src/modules/builder/asset/assetsGroup.tsx | 29 +- .../builder/asset/functions/addAssetModel.ts | 28 +- ...edTravelHandler.ts => useWorkerHandler.ts} | 12 +- .../actions/human/useHumanActions.ts | 16 +- .../simulation/actions/useActionHandler.ts | 2 +- .../triggerConnections/triggerConnector.tsx | 6 +- app/src/store/simulation/useProductStore.ts | 4 +- app/src/types/simulationTypes.d.ts | 13 +- 11 files changed, 606 insertions(+), 112 deletions(-) create mode 100644 app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx rename app/src/modules/simulation/actions/human/actionHandler/{useAnimatedTravelHandler.ts => useWorkerHandler.ts} (66%) diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx new file mode 100644 index 0000000..f3283e3 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx @@ -0,0 +1,104 @@ +import React, { useRef } from "react"; +import { AddIcon, RemoveIcon, ResizeHeightIcon } from "../../../../../icons/ExportCommonIcons"; +import { handleResize } from "../../../../../../functions/handleResizePannel"; +import { useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; + +interface AnimationListProps { + animationOptions: string[]; + animationSequences: { + animationUuid: string; + animationName: string; + animationType: "behaviour" | "animatedTravel"; + animation: string | null; + travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null }; + }[]; + onAddAnimation: () => void; + onRemoveAnimation: (animationUuid: string) => void; + handleAnimationSelect: (animationUuid: string) => void; + handleRenameAnimation: (animationUuid: string, newName: string) => void; + selectedAnimation: { + animationUuid: string; + animationName: string; + animationType: "behaviour" | "animatedTravel"; + animation: string | null; + travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null }; + } | undefined +} + +const AnimationList: React.FC = ({ + animationSequences, + onAddAnimation, + onRemoveAnimation, + handleAnimationSelect, + handleRenameAnimation, + selectedAnimation +}) => { + const animationContainerRef = useRef(null); + const { selectedAction } = useSelectedAction(); + + return ( +
+
+
+
Animation Sequences
+ +
+
+
+ {animationSequences.map((sequence) => ( +
+ + {animationSequences.length > 1 && ( + + )} +
+ ))} +
+ {animationSequences.length > 0 && ( + + )} +
+
+
+ ); +}; + +export default AnimationList; \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index d07022a..24bb2e1 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -1,27 +1,38 @@ import { useEffect, useState } from "react"; +import { MathUtils } from "three"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; -import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; import PickAndPlaceAction from "../actions/PickAndPlaceAction"; +import ActionsList from "../components/ActionsList"; +import AnimationList from "../components/AnimationList"; +import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; -import { useParams } from "react-router-dom"; import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; -import ActionsList from "../components/ActionsList"; +import { useParams } from "react-router-dom"; function HumanMechanics() { - const [activeOption, setActiveOption] = useState<"animatedTravel">("animatedTravel"); + const [activeOption, setActiveOption] = useState<"worker">("worker"); + const [animationOptions, setAnimationOptions] = useState([]); const [speed, setSpeed] = useState("0.5"); const [currentAction, setCurrentAction] = useState(); const [selectedPointData, setSelectedPointData] = useState(); const { selectedEventData } = useSelectedEventData(); - const { productStore } = useSceneContext(); - const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = productStore(); + const { productStore, assetStore } = useSceneContext(); + const { getAssetById } = assetStore(); + const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction, addAction, removeAction } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); + const [selectedAnimation, setSelectedAnimation] = useState<{ + animationUuid: string; + animationName: string; + animationType: "behaviour" | "animatedTravel"; + animation: string | null; + travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } + }>(); const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -35,10 +46,15 @@ function HumanMechanics() { selectedEventData.selectedPoint ) as HumanPointSchema | undefined; - if (point?.action) { + if (point?.actions) { setSelectedPointData(point); - const action = point.action; - setSelectedAction(action.actionUuid, action.actionName); + if (point.actions.length > 0) { + setSelectedAction(point.actions[0].actionUuid, point.actions[0].actionName); + const asset = getAssetById(selectedEventData.data.modelUuid); + if (asset && asset.animations) { + setAnimationOptions(asset.animations) + } + } } } else { clearSelectedAction(); @@ -62,11 +78,16 @@ function HumanMechanics() { selectedEventData.selectedPoint ) as HumanPointSchema | undefined; - if (point?.action) { + if (point?.actions) { setSelectedPointData(point); - const action = point.action; - setCurrentAction(action); - setActiveOption(action.actionType as "animatedTravel"); + const action = point.actions.find((a) => a.actionUuid === selectedAction.actionId); + if (action) { + setCurrentAction(action); + setActiveOption(action.actionType as "worker"); + if (action.animationSequences.length > 0) { + setSelectedAnimation(action.animationSequences[0]); + } + } } } else { clearSelectedAction(); @@ -91,80 +112,387 @@ function HumanMechanics() { }; const handleSelectActionType = (actionType: string) => { - if (!currentAction) return; - setActiveOption(actionType as "animatedTravel"); + if (!selectedAction.actionId || !currentAction || !selectedPointData) return; + + const updatedAction = { + ...currentAction, + actionType: actionType as "worker" + }; + + const updatedPoint = { + ...selectedPointData, + actions: selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId ? updatedAction : action + ) + }; const event = updateAction( selectedProduct.productUuid, - currentAction.actionUuid, - { actionType: actionType as "animatedTravel" } + selectedAction.actionId, + updatedAction ); - if (event && selectedPointData) { - const updatedAction = { ...selectedPointData.action, actionType: actionType as "animatedTravel" }; - setSelectedPointData({ ...selectedPointData, action: updatedAction }); - } - if (event) { updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); }; - const handleRenameAction = (newName: string) => { - if (!currentAction) return; + const handleChooseAnimation = (animationOption: string) => { + if (!selectedAction.actionId || !currentAction || !selectedAnimation || !selectedPointData) return; + + const updatedAnimation = { + ...selectedAnimation, + animation: animationOption + }; + + const updatedAction = { + ...currentAction, + animationSequences: currentAction.animationSequences.map(anim => + anim.animationUuid === selectedAnimation.animationUuid ? updatedAnimation : anim + ) + }; + + const updatedPoint = { + ...selectedPointData, + actions: selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId ? updatedAction : action + ) + }; const event = updateAction( selectedProduct.productUuid, - currentAction.actionUuid, - { actionName: newName } + selectedAction.actionId, + updatedAction ); - if (event && selectedPointData) { - const updatedAction = { ...selectedPointData.action, actionName: newName }; - setSelectedPointData({ ...selectedPointData, action: updatedAction }); - } - if (event) { updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); } + + setCurrentAction(updatedAction); + setSelectedAnimation(updatedAnimation); + setSelectedPointData(updatedPoint); }; const handleSpeedChange = (value: string) => { if (!selectedEventData) return; + const numericValue = parseFloat(value); + if (isNaN(numericValue)) return; + + const updatedEvent = { + ...selectedEventData.data, + speed: numericValue + } as HumanEventSchema; + const event = updateEvent( selectedProduct.productUuid, selectedEventData.data.modelUuid, - { speed: parseFloat(value) } + updatedEvent ); if (event) { updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); } + + setSpeed(value); }; - const handleClearPoints = () => { - if (!currentAction) return; + const handleClearPoints = (animationUuid: string) => { + if (!currentAction || !selectedPointData || !selectedAction.actionId) return; + + const updatedAnimation = currentAction.animationSequences.find(anim => + anim.animationUuid === animationUuid + ); + + if (!updatedAnimation) return; + + updatedAnimation.travelPoints = { + startPoint: null, + endPoint: null + }; + + const updatedAction = { + ...currentAction, + animationSequences: currentAction.animationSequences.map(anim => + anim.animationUuid === animationUuid ? updatedAnimation : anim + ) + }; + + const updatedPoint = { + ...selectedPointData, + actions: selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId ? updatedAction : action + ) + }; const event = updateAction( selectedProduct.productUuid, - currentAction.actionUuid, - { - travelPoints: { - startPoint: null, - endPoint: null, - }, - } + selectedAction.actionId, + updatedAction ); if (event) { updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + if (selectedAnimation?.animationUuid === animationUuid) { + setSelectedAnimation(updatedAnimation); + } + }; + + const handleAddAction = () => { + if (!selectedEventData || !selectedPointData) return; + + const newAction: HumanAction = { + actionUuid: MathUtils.generateUUID(), + actionName: `Action ${selectedPointData.actions.length + 1}`, + actionType: "worker", + animationSequences: [ + { + animationUuid: MathUtils.generateUUID(), + animationName: 'Animation 1', + animationType: 'behaviour', + animation: null + } + ], + loadCapacity: 1, + triggers: [], + }; + + const event = addAction( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint, + newAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + const updatedPoint = { ...selectedPointData, actions: [...selectedPointData.actions, newAction] }; + setSelectedPointData(updatedPoint); + setSelectedAction(newAction.actionUuid, newAction.actionName); + }; + + const handleDeleteAction = (actionUuid: string) => { + if (!selectedPointData) return; + + const event = removeAction(selectedProduct.productUuid, actionUuid); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + const index = selectedPointData.actions.findIndex((a) => a.actionUuid === actionUuid); + const newActions = selectedPointData.actions.filter((a) => a.actionUuid !== actionUuid); + const updatedPoint = { ...selectedPointData, actions: newActions }; + setSelectedPointData(updatedPoint); + + if (selectedAction.actionId === actionUuid) { + const nextAction = newActions[index] || newActions[index - 1]; + if (nextAction) { + setSelectedAction(nextAction.actionUuid, nextAction.actionName); + } else { + clearSelectedAction(); + } + } + }; + + const handleAddAnimation = () => { + if (!currentAction || !selectedPointData || !selectedAction.actionId) return; + + const newAnimation = { + animationUuid: MathUtils.generateUUID(), + animationName: `Animation ${currentAction.animationSequences.length + 1}`, + animationType: 'behaviour' as "behaviour", + animation: null + }; + + const updatedAction = { + ...currentAction, + animationSequences: [...currentAction.animationSequences, newAnimation] + }; + + const updatedPoint = { + ...selectedPointData, + actions: selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId ? updatedAction : action + ) + }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + setSelectedAnimation(newAnimation); + }; + + const handleRemoveAnimation = (animationUuid: string) => { + if (!currentAction || !selectedPointData || !selectedAction.actionId) return; + + const updatedAction = { + ...currentAction, + animationSequences: currentAction.animationSequences.filter( + anim => anim.animationUuid !== animationUuid + ) + }; + + const updatedPoint = { + ...selectedPointData, + actions: selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId ? updatedAction : action + ) + }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + + if (selectedAnimation?.animationUuid === animationUuid) { + setSelectedAnimation(updatedAction.animationSequences[0] || undefined); + } + }; + + const handleAnimationTypeChange = (animationUuid: string, newType: "behaviour" | "animatedTravel") => { + if (!currentAction || !selectedPointData || !selectedAction.actionId) return; + + const updatedAnimationSequences = currentAction.animationSequences.map(anim => { + if (anim.animationUuid === animationUuid) { + const updatedAnim = { + ...anim, + animationType: newType + }; + + if (newType === 'animatedTravel') { + updatedAnim.travelPoints = { + startPoint: null, + endPoint: null + }; + } else { + delete updatedAnim.travelPoints; + } + + return updatedAnim; + } + return anim; + }); + + const updatedAction = { + ...currentAction, + animationSequences: updatedAnimationSequences + }; + + const updatedPoint = { + ...selectedPointData, + actions: selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId ? updatedAction : action + ) + }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + + if (selectedAnimation?.animationUuid === animationUuid) { + const updatedAnimation = updatedAnimationSequences.find(anim => + anim.animationUuid === animationUuid + ); + if (updatedAnimation) { + setSelectedAnimation(updatedAnimation); + } + } + }; + + const handleAnimationSelect = (animationUuid: string) => { + if (!currentAction || !selectedAction.actionId) return; + + const animation = currentAction.animationSequences.find( + anim => anim.animationUuid === animationUuid + ); + + if (animation) { + setSelectedAnimation(animation); + } + }; + + const handleRenameAnimation = (animationUuid: string, newName: string) => { + if (!currentAction || !selectedPointData || !selectedAction.actionId) return; + + const updatedAnimation = currentAction.animationSequences.find(anim => + anim.animationUuid === animationUuid + ); + + if (!updatedAnimation) return; + + const renamedAnimation = { ...updatedAnimation, animationName: newName }; + + const updatedAction = { + ...currentAction, + animationSequences: currentAction.animationSequences.map(anim => + anim.animationUuid === animationUuid ? renamedAnimation : anim + ) + }; + + const updatedPoint = { + ...selectedPointData, + actions: selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId ? updatedAction : action + ) + }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + if (selectedAnimation?.animationUuid === animationUuid) { + setSelectedAnimation(renamedAnimation); + } }; const availableActions = { - defaultOption: "animatedTravel", - options: ["animatedTravel"], + defaultOption: "worker", + options: ["worker"], }; return ( @@ -186,21 +514,20 @@ function HumanMechanics() { +
+ - {currentAction && ( -
- - { }} - handleDeleteAction={() => { }} - /> + {selectedAction.actionId && currentAction && (
@@ -211,21 +538,63 @@ function HumanMechanics() { onSelect={handleSelectActionType} disabled={true} /> - {activeOption === 'animatedTravel' && ( - - )}
+ + {selectedAnimation && ( + <> +
+ +
+
+ + handleAnimationTypeChange( + selectedAnimation.animationUuid, + type as "behaviour" | "animatedTravel" + ) + } + /> + + + {selectedAnimation.animationType === "animatedTravel" && ( + handleClearPoints(selectedAnimation.animationUuid)} + /> + )} +
+ + )}
-
- )} + )} +
); } -export default HumanMechanics; +export default HumanMechanics; \ No newline at end of file diff --git a/app/src/components/ui/inputs/RenameInput.tsx b/app/src/components/ui/inputs/RenameInput.tsx index 593e1f1..637dec3 100644 --- a/app/src/components/ui/inputs/RenameInput.tsx +++ b/app/src/components/ui/inputs/RenameInput.tsx @@ -9,9 +9,10 @@ interface RenameInputProps { value: string; onRename?: (newText: string) => void; checkDuplicate?: (name: string) => boolean; + canEdit?: boolean; } -const RenameInput: React.FC = ({ value, onRename, checkDuplicate }) => { +const RenameInput: React.FC = ({ value, onRename, checkDuplicate, canEdit = true }) => { const [isEditing, setIsEditing] = useState(false); const [text, setText] = useState(value); const [isDuplicate, setIsDuplicate] = useState(false); @@ -28,13 +29,15 @@ const RenameInput: React.FC = ({ value, onRename, checkDuplica }, [text, checkDuplicate]); const handleDoubleClick = () => { - setIsEditing(true); - setTimeout(() => inputRef.current?.focus(), 0); + if (canEdit) { + setIsEditing(true); + setTimeout(() => inputRef.current?.focus(), 0); + } }; const handleBlur = () => { - - if(isDuplicate) return + + if (isDuplicate) return setIsEditing(false); if (onRename && !isDuplicate) { onRename(text); diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index fbc2738..a761d39 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -256,18 +256,23 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], - action: { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "animatedTravel", - loadCapacity: 1, - travelPoints: { - startPoint: null, - endPoint: null, - }, - triggers: [] - } - + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + animationSequences: [ + { + animationUuid: THREE.MathUtils.generateUUID(), + animationName: 'Animation 1', + animationType: 'behaviour', + animation: null + } + ], + loadCapacity: 1, + triggers: [] + } + ] } } addEvent(humanEvent); diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 471314f..8aa18f1 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -374,17 +374,23 @@ async function handleModelLoad( uuid: THREE.MathUtils.generateUUID(), position: [data.points[0].x, data.points[0].y, data.points[0].z], rotation: [0, 0, 0], - action: { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "animatedTravel", - loadCapacity: 1, - travelPoints: { - startPoint: null, - endPoint: null, - }, - triggers: [] - } + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + animationSequences: [ + { + animationUuid: THREE.MathUtils.generateUUID(), + animationName: 'Animation 1', + animationType: 'behaviour', + animation: null + } + ], + loadCapacity: 1, + triggers: [] + } + ] } } diff --git a/app/src/modules/simulation/actions/human/actionHandler/useAnimatedTravelHandler.ts b/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts similarity index 66% rename from app/src/modules/simulation/actions/human/actionHandler/useAnimatedTravelHandler.ts rename to app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts index d347f03..9e775ff 100644 --- a/app/src/modules/simulation/actions/human/actionHandler/useAnimatedTravelHandler.ts +++ b/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { useSceneContext } from "../../../../scene/sceneContext"; import { useProductContext } from "../../../products/productContext"; -export function useAnimatedTravelHandler() { +export function useWorkerHandler() { const { materialStore, humanStore, productStore } = useSceneContext(); const { getMaterialById } = materialStore(); const { } = humanStore(); @@ -10,12 +10,12 @@ export function useAnimatedTravelHandler() { const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const animatedTravelLogStatus = (materialUuid: string, status: string) => { + const workerLogStatus = (materialUuid: string, status: string) => { echo.info(`${materialUuid}, ${status}`); } - const handleAnimatedTravel = useCallback((action: HumanAction, materialId?: string) => { - if (!action || action.actionType !== 'animatedTravel' || !materialId) return; + const handleWorker = useCallback((action: HumanAction, materialId?: string) => { + if (!action || action.actionType !== 'worker' || !materialId) return; const material = getMaterialById(materialId); if (!material) return; @@ -24,11 +24,11 @@ export function useAnimatedTravelHandler() { if (!modelUuid) return; - animatedTravelLogStatus(material.materialName, `performing animatedTravel`); + workerLogStatus(material.materialName, `performing worker action`); }, [getMaterialById]); return { - handleAnimatedTravel, + handleWorker, }; } \ No newline at end of file diff --git a/app/src/modules/simulation/actions/human/useHumanActions.ts b/app/src/modules/simulation/actions/human/useHumanActions.ts index 96e40b0..8e30e45 100644 --- a/app/src/modules/simulation/actions/human/useHumanActions.ts +++ b/app/src/modules/simulation/actions/human/useHumanActions.ts @@ -1,24 +1,24 @@ import { useEffect, useCallback } from 'react'; -import { useAnimatedTravelHandler } from './actionHandler/useAnimatedTravelHandler'; +import { useWorkerHandler } from './actionHandler/useWorkerHandler'; export function useHumanActions() { - const { handleAnimatedTravel } = useAnimatedTravelHandler(); + const { handleWorker } = useWorkerHandler(); - const handleAnimatedTravelAction = useCallback((action: HumanAction) => { - handleAnimatedTravel(action); - }, [handleAnimatedTravel]); + const handleWorkerAction = useCallback((action: HumanAction) => { + handleWorker(action); + }, [handleWorker]); const handleHumanAction = useCallback((action: HumanAction, materialId: string) => { if (!action) return; switch (action.actionType) { - case 'animatedTravel': - handleAnimatedTravelAction(action); + case 'worker': + handleWorkerAction(action); break; default: console.warn(`Unknown Human action type: ${action.actionType}`); } - }, [handleAnimatedTravelAction]); + }, [handleWorkerAction]); const cleanup = useCallback(() => { }, []); diff --git a/app/src/modules/simulation/actions/useActionHandler.ts b/app/src/modules/simulation/actions/useActionHandler.ts index aef8d39..3bb95bf 100644 --- a/app/src/modules/simulation/actions/useActionHandler.ts +++ b/app/src/modules/simulation/actions/useActionHandler.ts @@ -39,7 +39,7 @@ export function useActionHandler() { case 'store': case 'retrieve': handleStorageAction(action as StorageAction, materialId as string); break; - case 'animatedTravel': + case 'worker': handleHumanAction(action as HumanAction, materialId as string); break; default: diff --git a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx index ec7406c..918007a 100644 --- a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx +++ b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx @@ -155,8 +155,8 @@ function TriggerConnector() { // Handle Human point else if (event.type === "human" && 'point' in event) { const point = event.point; - if (point.action?.triggers) { - point.action.triggers.forEach(trigger => { + point.actions?.forEach(action => { + action.triggers?.forEach(trigger => { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, @@ -166,7 +166,7 @@ function TriggerConnector() { }); } }); - } + }); } }); diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 3b483ea..18d636b 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -32,13 +32,13 @@ type ProductsStore = { productUuid: string, modelUuid: string, pointUuid: string, - action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['action'] + action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0] ) => EventsSchema | undefined; removeAction: (productUuid: string, actionUuid: string) => EventsSchema | undefined; updateAction: ( productUuid: string, actionUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Trigger-level actionss diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 2192101..b99feee 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -72,9 +72,15 @@ interface StorageAction { interface HumanAction { actionUuid: string; actionName: string; - actionType: "animatedTravel"; + actionType: "worker"; + animationSequences: { + animationUuid: string; + animationName: string; + animationType: "behaviour" | "animatedTravel"; + animation: string | null; + travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } + }[] loadCapacity: number; - travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } triggers: TriggerSchema[]; } @@ -120,7 +126,7 @@ interface HumanPointSchema { uuid: string; position: [number, number, number]; rotation: [number, number, number]; - action: HumanAction; + actions: HumanAction[]; } type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema; @@ -229,6 +235,7 @@ interface HumanStatus extends HumanEventSchema { currentAction?: { actionUuid: string; actionName: string; + animationUuid: string; materialType?: string | null; materialId?: string | null; }; From 8dd853dd03d047ceabd11393e38ad1c2cff1b01e Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 3 Jul 2025 15:18:49 +0530 Subject: [PATCH 07/13] feat: Implement selected animation handling and integrate with human mechanics --- .../components/AnimationList.tsx | 11 +- .../mechanics/humanMechanics.tsx | 27 +- .../builder/asset/functions/addAssetModel.ts | 1 - app/src/modules/simulation/human/human.tsx | 27 +- .../instance/animator/humanAnimator.tsx | 10 + .../instances/instance/humanInstance.tsx | 2 +- .../human/instances/instance/humanUi.tsx | 277 +++++++++++++++++- .../spatialUI/vehicle/vehicleUI.tsx | 4 +- .../store/simulation/useSimulationStore.ts | 34 +++ app/src/types/simulationTypes.d.ts | 3 +- 10 files changed, 351 insertions(+), 45 deletions(-) create mode 100644 app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx index f3283e3..836432d 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx @@ -1,7 +1,7 @@ import React, { useRef } from "react"; import { AddIcon, RemoveIcon, ResizeHeightIcon } from "../../../../../icons/ExportCommonIcons"; import { handleResize } from "../../../../../../functions/handleResizePannel"; -import { useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; +import { useSelectedAction, useSelectedAnimation } from "../../../../../../store/simulation/useSimulationStore"; import RenameInput from "../../../../../ui/inputs/RenameInput"; interface AnimationListProps { @@ -17,13 +17,6 @@ interface AnimationListProps { onRemoveAnimation: (animationUuid: string) => void; handleAnimationSelect: (animationUuid: string) => void; handleRenameAnimation: (animationUuid: string, newName: string) => void; - selectedAnimation: { - animationUuid: string; - animationName: string; - animationType: "behaviour" | "animatedTravel"; - animation: string | null; - travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null }; - } | undefined } const AnimationList: React.FC = ({ @@ -32,10 +25,10 @@ const AnimationList: React.FC = ({ onRemoveAnimation, handleAnimationSelect, handleRenameAnimation, - selectedAnimation }) => { const animationContainerRef = useRef(null); const { selectedAction } = useSelectedAction(); + const { selectedAnimation } = useSelectedAnimation(); return (
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index 24bb2e1..17e8363 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -7,7 +7,7 @@ import Trigger from "../trigger/Trigger"; import PickAndPlaceAction from "../actions/PickAndPlaceAction"; import ActionsList from "../components/ActionsList"; import AnimationList from "../components/AnimationList"; -import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; +import { useSelectedEventData, useSelectedAction, useSelectedAnimation } from "../../../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; @@ -26,14 +26,8 @@ function HumanMechanics() { const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction, addAction, removeAction } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const [selectedAnimation, setSelectedAnimation] = useState<{ - animationUuid: string; - animationName: string; - animationType: "behaviour" | "animatedTravel"; - animation: string | null; - travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } - }>(); const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); + const { selectedAnimation, setSelectedAnimation } = useSelectedAnimation(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); @@ -210,10 +204,8 @@ function HumanMechanics() { if (!updatedAnimation) return; - updatedAnimation.travelPoints = { - startPoint: null, - endPoint: null - }; + delete updatedAnimation.startPoint; + delete updatedAnimation.endPoint; const updatedAction = { ...currentAction, @@ -387,14 +379,8 @@ function HumanMechanics() { animationType: newType }; - if (newType === 'animatedTravel') { - updatedAnim.travelPoints = { - startPoint: null, - endPoint: null - }; - } else { - delete updatedAnim.travelPoints; - } + delete updatedAnim.startPoint; + delete updatedAnim.endPoint; return updatedAnim; } @@ -546,7 +532,6 @@ function HumanMechanics() { onRemoveAnimation={handleRemoveAnimation} handleAnimationSelect={handleAnimationSelect} handleRenameAnimation={handleRenameAnimation} - selectedAnimation={selectedAnimation} /> {selectedAnimation && ( <> diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 8aa18f1..f7614ee 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -422,7 +422,6 @@ async function handleModelLoad( userId: userId, }; - console.log('completeData: ', completeData); socket.emit("v1:model-asset:add", completeData); const asset: Asset = { diff --git a/app/src/modules/simulation/human/human.tsx b/app/src/modules/simulation/human/human.tsx index 6a552c6..49c9435 100644 --- a/app/src/modules/simulation/human/human.tsx +++ b/app/src/modules/simulation/human/human.tsx @@ -1,32 +1,43 @@ +import { useEffect, useState } from 'react' +import { useSelectedAnimation, useSelectedEventSphere } from '../../../store/simulation/useSimulationStore'; +import { usePlayButtonStore } from '../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../scene/sceneContext'; import HumanInstances from './instances/humanInstances' -import { useEffect, useState } from "react"; -import { useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; -import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -import { useSceneContext } from "../../scene/sceneContext"; +import HumanUi from './instances/instance/humanUi'; function Human() { const { humanStore } = useSceneContext(); const { getHumanById } = humanStore(); + const { selectedAnimation } = useSelectedAnimation(); const { selectedEventSphere } = useSelectedEventSphere(); const { isPlaying } = usePlayButtonStore(); - const [isHumanSelected, setIsHumanSelected] = useState(false); + const [isVehicleSelected, setIsHumanSelected] = useState(false); useEffect(() => { if (selectedEventSphere) { - const selectedVehicle = getHumanById(selectedEventSphere.userData.modelUuid); - if (selectedVehicle) { + const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); + if (selectedHuman && + selectedHuman.point.actions.some((action) => + action.animationSequences.some((animation) => + animation.animationUuid === selectedAnimation?.animationUuid && animation.animationType === 'animatedTravel' + ) + )) { setIsHumanSelected(true); } else { setIsHumanSelected(false); } } - }, [getHumanById, selectedEventSphere]) + }, [getHumanById, selectedEventSphere, selectedAnimation]) return ( <> + {isVehicleSelected && selectedEventSphere && !isPlaying && + + } + ) } diff --git a/app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx b/app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx new file mode 100644 index 0000000..01f569e --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +function HumanAnimator() { + return ( + <> + + ) +} + +export default HumanAnimator \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index 89e2b10..25fb044 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -10,7 +10,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { return ( <> - + ) diff --git a/app/src/modules/simulation/human/instances/instance/humanUi.tsx b/app/src/modules/simulation/human/instances/instance/humanUi.tsx index 7a2bb9c..cfb0fc9 100644 --- a/app/src/modules/simulation/human/instances/instance/humanUi.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanUi.tsx @@ -1,8 +1,281 @@ -import React from 'react' +import { useEffect, useRef, useState } from 'react' +import { useGLTF } from '@react-three/drei'; +import { useFrame, useThree } from '@react-three/fiber'; +import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore'; +import { useProductContext } from '../../../products/productContext'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { Group, Plane, Vector3 } from 'three'; +import { useVersionContext } from '../../../../builder/version/versionContext'; +import { useParams } from 'react-router-dom'; +import startPoint from "../../../../../assets/gltf-glb/ui/arrow_green.glb"; +import startEnd from "../../../../../assets/gltf-glb/ui/arrow_red.glb"; +import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi'; + +function HumanUi() { + const { scene: startScene } = useGLTF(startPoint) as any; + const { scene: endScene } = useGLTF(startEnd) as any; + const startMarker = useRef(null); + const endMarker = useRef(null); + const outerGroup = useRef(null); + const prevMousePos = useRef({ x: 0, y: 0 }); + const { controls, raycaster } = useThree(); + const { selectedEventSphere } = useSelectedEventSphere(); + const { selectedProductStore } = useProductContext(); + const { humanStore, productStore } = useSceneContext(); + const { selectedProduct } = selectedProductStore(); + const { humans, getHumanById } = humanStore(); + const { updateEvent } = productStore(); + const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]); + const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]); + const [startRotation, setStartRotation] = useState<[number, number, number]>([0, 0, 0]); + const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]); + const { isDragging, setIsDragging } = useIsDragging(); + const { isRotating, setIsRotating } = useIsRotating(); + const plane = useRef(new Plane(new Vector3(0, 1, 0), 0)); + + const [selectedHumanData, setSelectedHumanData] = useState<{ + position: [number, number, number]; + rotation: [number, number, number]; + }>({ position: [0, 0, 0], rotation: [0, 0, 0] }); + + const { selectedAction } = useSelectedAction(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productUuid: productUuid, + projectId: projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || '', + }); + }; + + useEffect(() => { + if (!selectedEventSphere) return; + const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); + + if (selectedHuman) { + setSelectedHumanData({ + position: selectedHuman.position, + rotation: selectedHuman.rotation, + }); + + if (selectedHuman.point?.actions && selectedAction.actionId) { + const action = selectedHuman.point.actions.find(a => a.actionUuid === selectedAction.actionId); + + if (action?.animationSequences[0]) { + const sequence = action.animationSequences[0]; + + if (sequence.startPoint?.position && outerGroup.current) { + const worldPos = new Vector3(...sequence.startPoint.position); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + setStartPosition([localPosition.x, 0.5, localPosition.z]); + setStartRotation(sequence.startPoint.rotation || [0, 0, 0]); + } + + if (sequence.endPoint?.position && outerGroup.current) { + const worldPos = new Vector3(...sequence.endPoint.position); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + setEndPosition([localPosition.x, 0.5, localPosition.z]); + setEndRotation(sequence.endPoint.rotation || [0, 0, 0]); + } + } + } + } + }, [selectedEventSphere, outerGroup.current, selectedAction, humans]); + + const handlePointerDown = ( + e: any, + state: "start" | "end", + rotation: "start" | "end" + ) => { + if (e.object.name === "handle") { + const normalizedX = (e.clientX / window.innerWidth) * 2 - 1; + const normalizedY = -(e.clientY / window.innerHeight) * 2 + 1; + prevMousePos.current = { x: normalizedX, y: normalizedY }; + setIsRotating(rotation); + if (controls) (controls as any).enabled = false; + setIsDragging(null); + } else { + setIsDragging(state); + setIsRotating(null); + if (controls) (controls as any).enabled = false; + } + }; + + const handlePointerUp = () => { + (controls as any).enabled = true; + setIsDragging(null); + setIsRotating(null); + + if (selectedEventSphere?.userData.modelUuid && selectedAction.actionId) { + const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); + + if (selectedHuman && outerGroup.current && startMarker.current && endMarker.current) { + const worldPosStart = new Vector3(...startPosition); + const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone()); + + const worldPosEnd = new Vector3(...endPosition); + const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone()); + + const updatedActions = selectedHuman.point.actions.map(action => { + if (action.actionUuid === selectedAction.actionId) { + const updatedSequences = action.animationSequences.map(sequence => { + return { + ...sequence, + startPoint: { + position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number], + rotation: startRotation + }, + endPoint: { + position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number], + rotation: endRotation + } + }; + }); + + return { + ...action, + animationSequences: updatedSequences + }; + } + return action; + }); + + const event = updateEvent( + selectedProduct.productUuid, + selectedEventSphere.userData.modelUuid, + { + ...selectedHuman, + point: { + ...selectedHuman.point, + actions: updatedActions + } + } + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + } + } + }; + + useFrame(() => { + if (!isDragging || !plane.current || !raycaster || !outerGroup.current) return; + const intersectPoint = new Vector3(); + const intersects = raycaster.ray.intersectPlane( + plane.current, + intersectPoint + ); + if (!intersects) return; + const localPoint = outerGroup?.current.worldToLocal(intersectPoint.clone()); + if (isDragging === "start") { + setStartPosition([localPoint.x, 0.5, localPoint.z]); + } else if (isDragging === "end") { + setEndPosition([localPoint.x, 0.5, localPoint.z]); + } + }); + + useFrame((state) => { + if (!isRotating) return; + const currentPointerX = state.pointer.x; + const deltaX = currentPointerX - prevMousePos.current.x; + prevMousePos.current.x = currentPointerX; + const marker = + isRotating === "start" ? startMarker.current : endMarker.current; + if (marker) { + const rotationSpeed = 10; + marker.rotation.y += deltaX * rotationSpeed; + if (isRotating === "start") { + setStartRotation([ + marker.rotation.x, + marker.rotation.y, + marker.rotation.z, + ]); + } else { + setEndRotation([ + marker.rotation.x, + marker.rotation.y, + marker.rotation.z, + ]); + } + } + }); + + useEffect(() => { + const handleGlobalPointerUp = () => { + setIsDragging(null); + setIsRotating(null); + if (controls) (controls as any).enabled = true; + handlePointerUp(); + }; + + if (isDragging || isRotating) { + window.addEventListener("pointerup", handleGlobalPointerUp); + } + + return () => { + window.removeEventListener("pointerup", handleGlobalPointerUp); + }; + }, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation]); -function HumanUi({ human }: { human: HumanStatus }) { return ( <> + {selectedHumanData && ( + + { + e.stopPropagation(); + handlePointerDown(e, "start", "start"); + }} + onPointerMissed={() => { + (controls as any).enabled = true; + setIsDragging(null); + setIsRotating(null); + }} + /> + + { + e.stopPropagation(); + handlePointerDown(e, "end", "end"); + }} + onPointerMissed={() => { + (controls as any).enabled = true; + setIsDragging(null); + setIsRotating(null); + }} + /> + + )} ) } diff --git a/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx b/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx index 9d889ed..02eb2ec 100644 --- a/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx +++ b/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx @@ -43,7 +43,7 @@ const VehicleUI = () => { const outerGroup = useRef(null); const state: Types.ThreeState = useThree(); const controls: any = state.controls; - const [selectedVehicleData, setSelectedVechicleData] = useState<{ position: [number, number, number]; rotation: [number, number, number]; }>({ position: [0, 0, 0], rotation: [0, 0, 0] }); + const [selectedVehicleData, setSelectedVehicleData] = useState<{ position: [number, number, number]; rotation: [number, number, number]; }>({ position: [0, 0, 0], rotation: [0, 0, 0] }); const CIRCLE_RADIUS = 0.8; const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -71,7 +71,7 @@ const VehicleUI = () => { ); if (selectedVehicle) { - setSelectedVechicleData({ + setSelectedVehicleData({ position: selectedVehicle.position, rotation: selectedVehicle.rotation, }); diff --git a/app/src/store/simulation/useSimulationStore.ts b/app/src/store/simulation/useSimulationStore.ts index 1bbab4c..b6cd076 100644 --- a/app/src/store/simulation/useSimulationStore.ts +++ b/app/src/store/simulation/useSimulationStore.ts @@ -146,6 +146,40 @@ export const useSelectedAction = create()( })) ); +interface SelectedAnimationState { + selectedAnimation: { + animationUuid: string; + animationName: string; + animationType: "behaviour" | "animatedTravel"; + animation: string | null; + travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } + } | null; + setSelectedAnimation: (animation: { + animationUuid: string; + animationName: string; + animationType: "behaviour" | "animatedTravel"; + animation: string | null; + travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } + }) => void; + clearSelectedAnimation: () => void; +} + +export const useSelectedAnimation = create()( + immer((set) => ({ + selectedAnimation: null, + setSelectedAnimation: (animation) => { + set((state) => { + state.selectedAnimation = animation; + }); + }, + clearSelectedAnimation: () => { + set((state) => { + state.selectedAnimation = null; + }); + }, + })) +); + interface IsDraggingState { isDragging: "start" | "end" | null; setIsDragging: (state: "start" | "end" | null) => void; diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index b99feee..02d66c8 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -78,7 +78,8 @@ interface HumanAction { animationName: string; animationType: "behaviour" | "animatedTravel"; animation: string | null; - travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } + startPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } + endPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } }[] loadCapacity: number; triggers: TriggerSchema[]; From 1e715cee504e12d1b89a10ad756b9779bc007c37 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 3 Jul 2025 16:55:30 +0530 Subject: [PATCH 08/13] feat: Enhance human event handling and animation management, including state updates and monitoring --- .../builder/asset/functions/addAssetModel.ts | 1 - .../builder/asset/models/model/model.tsx | 55 +++++++-- .../actions/human/useHumanActions.ts | 6 +- .../eventManager/useHumanEventManager.ts | 79 +++++++++++++ .../instances/instance/humanInstance.tsx | 107 +++++++++++++++++- .../checkActiveRoboticArmsInSubsequence.ts | 3 +- .../determineExecutionMachineSequences.ts | 3 +- .../functions/determineExecutionOrder.ts | 4 +- .../functions/determineExecutionSequences.ts | 4 +- .../getConveyorSequencesInProduct.ts | 3 +- .../triggerHandler/useTriggerHandler.ts | 56 ++++++++- .../instances/instance/vehicleInstance.tsx | 18 +-- app/src/store/simulation/useHumanStore.ts | 13 +++ 13 files changed, 316 insertions(+), 36 deletions(-) create mode 100644 app/src/modules/simulation/human/eventManager/useHumanEventManager.ts diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index f7614ee..43c4b8a 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -162,7 +162,6 @@ async function handleModelLoad( // SOCKET if (selectedItem.type) { - console.log('selectedItem: ', selectedItem); const data = PointsCalculator( selectedItem.type, gltf.scene.clone(), diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 3af4bde..ba47e9e 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -49,6 +49,10 @@ function Model({ asset }: { readonly asset: Asset }) { const { userId, organization } = getUserData(); const mixerRef = useRef(); const actions = useRef<{ [name: string]: THREE.AnimationAction }>({}); + const [currentAnimation, setCurrentAnimation] = useState(null); + const [previousAnimation, setPreviousAnimation] = useState(null); + const [blendFactor, setBlendFactor] = useState(0); + const blendDuration = 0.3; useEffect(() => { setDeletableFloorItem(null); @@ -278,8 +282,16 @@ function Model({ asset }: { readonly asset: Asset }) { } } + const handleAnimationComplete = useCallback(() => { + console.log(`Animation "${currentAnimation}" completed`); + }, [currentAnimation]); + useFrame((_, delta) => { if (mixerRef.current) { + if (blendFactor < 1) { + setBlendFactor(prev => Math.min(prev + delta / blendDuration, 1)); + } + mixerRef.current.update(delta); } }); @@ -288,17 +300,46 @@ function Model({ asset }: { readonly asset: Asset }) { if (asset.animationState && asset.animationState.isPlaying) { if (!mixerRef.current) return; - Object.values(actions.current).forEach((action) => action.stop()); - - const action = actions.current[asset.animationState.current]; - if (action && asset.animationState?.isPlaying) { - const loopMode = asset.animationState.loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce; - action.reset().setLoop(loopMode, loopMode === THREE.LoopRepeat ? Infinity : 1).play(); + if (asset.animationState.current !== currentAnimation) { + setPreviousAnimation(currentAnimation); + setCurrentAnimation(asset.animationState.current); + setBlendFactor(0); } + + const currentAction = actions.current[asset.animationState.current]; + const previousAction = previousAnimation ? actions.current[previousAnimation] : null; + + if (currentAction) { + const loopMode = asset.animationState.loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce; + + currentAction.reset(); + currentAction.setLoop(loopMode, loopMode === THREE.LoopRepeat ? Infinity : 1); + + currentAction.play(); + + mixerRef.current.addEventListener('finished', handleAnimationComplete); + + if (previousAction && blendFactor < 1) { + previousAction.crossFadeTo(currentAction, blendDuration, true); + } + } + + Object.entries(actions.current).forEach(([name, action]) => { + if ((asset.animationState && name !== asset.animationState.current) && name !== previousAnimation) { + action.stop(); + } + }); } else { Object.values(actions.current).forEach((action) => action.stop()); + setCurrentAnimation(null); } - }, [asset.animationState]) + + return () => { + if (mixerRef.current) { + mixerRef.current.removeEventListener('finished', handleAnimationComplete); + } + } + }, [asset.animationState, currentAnimation, previousAnimation, handleAnimationComplete]); return ( { - handleWorker(action); + const handleWorkerAction = useCallback((action: HumanAction, materialId: string) => { + handleWorker(action, materialId); }, [handleWorker]); const handleHumanAction = useCallback((action: HumanAction, materialId: string) => { @@ -13,7 +13,7 @@ export function useHumanActions() { switch (action.actionType) { case 'worker': - handleWorkerAction(action); + handleWorkerAction(action, materialId); break; default: console.warn(`Unknown Human action type: ${action.actionType}`); diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts new file mode 100644 index 0000000..1ab16f7 --- /dev/null +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -0,0 +1,79 @@ +import { useEffect, useRef } from 'react'; +import { useFrame } from '@react-three/fiber'; +import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../scene/sceneContext'; + +type HumanCallback = { + humanId: string; + actionId: string; + callback: () => void; +}; + +export function useHumanEventManager() { + const { humanStore } = useSceneContext(); + const { getHumanById } = humanStore(); + const callbacksRef = useRef([]); + const isMonitoringRef = useRef(false); + const { isPlaying } = usePlayButtonStore(); + const { isPaused } = usePauseButtonStore(); + const { isReset } = useResetButtonStore(); + + useEffect(() => { + if (isReset) { + callbacksRef.current = []; + } + }, [isReset]) + + // Add a new human to monitor + const addHumanToMonitor = (humanId: string, actionId: string, callback: () => void) => { + // Avoid duplicates + if (!callbacksRef.current.some((entry) => entry.humanId === humanId)) { + callbacksRef.current.push({ humanId, actionId, callback }); + } + + // Start monitoring if not already running + if (!isMonitoringRef.current) { + isMonitoringRef.current = true; + } + }; + + // Remove a human from monitoring + const removeHumanFromMonitor = (humanId: string) => { + callbacksRef.current = callbacksRef.current.filter( + (entry) => entry.humanId !== humanId + ); + + // Stop monitoring if no more humans to track + if (callbacksRef.current.length === 0) { + isMonitoringRef.current = false; + } + }; + + // Check human states every frame + useFrame(() => { + if (!isMonitoringRef.current || callbacksRef.current.length === 0 || !isPlaying || isPaused) return; + + callbacksRef.current.forEach(({ humanId, actionId, callback }) => { + const human = getHumanById(humanId); + if (!human) return; + const action = human.point.actions.find((action) => action.actionUuid === actionId); + if (action && human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < action.loadCapacity) { + callback(); + removeHumanFromMonitor(humanId); // Remove after triggering + } + }); + }); + + // Cleanup on unmount + useEffect(() => { + return () => { + callbacksRef.current = []; + isMonitoringRef.current = false; + }; + }, []); + + return { + addHumanToMonitor, + removeHumanFromMonitor, + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index 25fb044..6e1ebc0 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -1,16 +1,113 @@ -import { useEffect } from 'react' -import HumanUi from './humanUi'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; +import { NavMeshQuery } from '@recast-navigation/core'; +import { useNavMesh } from '../../../../../store/builder/store'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../products/productContext'; + +import HumanAnimator from './animator/humanAnimator'; function HumanInstance({ human }: { human: HumanStatus }) { + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); + const { materialStore, armBotStore, conveyorStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); + const { removeMaterial, setEndTime } = materialStore(); + const { getStorageUnitById } = storageUnitStore(); + const { getArmBotById } = armBotStore(); + const { getConveyorById } = conveyorStore(); + const { getVehicleById } = vehicleStore(); + const { triggerPointActions } = useTriggerHandler(); + const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { humans, setHumanActive, setHumanState, setHumanPicking, clearCurrentMaterials, setHumanLoad, decrementHumanLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = humanStore(); + + const [currentPhase, setCurrentPhase] = useState('init'); + const [path, setPath] = useState<[number, number, number][]>([]); + const pauseTimeRef = useRef(null); + const idleTimeRef = useRef(0); + const activeTimeRef = useRef(0); + const isPausedRef = useRef(false); + const isSpeedRef = useRef(0); + let startTime: number; + let fixedInterval: number; + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + const previousTimeRef = useRef(null); + const animationFrameIdRef = useRef(null); useEffect(() => { - console.log('human: ', human); - }, [human]) + isPausedRef.current = isPaused; + }, [isPaused]); + + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + + const computePath = useCallback( + (start: any, end: any) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + const { path: segmentPath } = navMeshQuery.computePath(start, end); + if ( + segmentPath.length > 0 && + Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(end.x) && + Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(end.z) + ) { + return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; + } else { + console.log("There is no path here...Choose valid path") + const { path: segmentPaths } = navMeshQuery.computePath(start, start); + return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; + } + } catch { + console.error("Failed to compute path"); + return []; + } + }, + [navMesh] + ); + + function humanStatus(modelId: string, status: string) { + // console.log(`${modelId} , ${status}`); + } + + function reset() { + setCurrentPhase('init'); + setHumanActive(human.modelUuid, false); + setHumanPicking(human.modelUuid, false); + setHumanState(human.modelUuid, 'idle'); + setHumanLoad(human.modelUuid, 0); + setPath([]); + startTime = 0; + isPausedRef.current = false; + pauseTimeRef.current = 0; + resetTime(human.modelUuid) + activeTimeRef.current = 0 + idleTimeRef.current = 0 + previousTimeRef.current = null + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current) + animationFrameIdRef.current = null + } + } + + useEffect(() => { + if (isPlaying) { + + } + else { + reset() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [human, currentPhase, path, isPlaying]); return ( <> - + ) diff --git a/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts index 8275fb3..58925f3 100644 --- a/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts +++ b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts @@ -124,7 +124,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch event.type === 'vehicle' || event.type === 'machine' || event.type === 'storageUnit' || - event.type === 'roboticArm' + event.type === 'roboticArm' || + event.type === 'human' ) { pointToEventMap.set(event.point.uuid, event); allPoints.push(event.point); diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts b/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts index 7c2f7ee..a87fe6a 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts @@ -16,7 +16,8 @@ export async function determineExecutionMachineSequences(products: productsSchem event.type === 'vehicle' || event.type === 'machine' || event.type === 'storageUnit' || - event.type === 'roboticArm' + event.type === 'roboticArm' || + event.type === 'human' ) { pointToEventMap.set(event.point.uuid, event); allPoints.push(event.point); diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts index 1f29ab3..d17e2d7 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts @@ -19,7 +19,9 @@ export function determineExecutionOrder(products: productsSchema): PointsScheme[ } else if (event.type === 'vehicle' || event.type === 'machine' || event.type === 'storageUnit' || - event.type === 'roboticArm') { + event.type === 'roboticArm' || + event.type === 'human' + ) { pointMap.set(event.point.uuid, event.point); allPoints.push(event.point); } diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts b/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts index bb88fb5..3ba5eb2 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts @@ -16,7 +16,9 @@ export async function determineExecutionSequences(products: productsSchema): Pro } else if (event.type === 'vehicle' || event.type === 'machine' || event.type === 'storageUnit' || - event.type === 'roboticArm') { + event.type === 'roboticArm' || + event.type === 'human' + ) { pointMap.set(event.point.uuid, event.point); allPoints.push(event.point); } diff --git a/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts b/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts index 8ad5e10..8f812bf 100644 --- a/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts +++ b/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts @@ -91,7 +91,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch event.type === 'vehicle' || event.type === 'machine' || event.type === 'storageUnit' || - event.type === 'roboticArm' + event.type === 'roboticArm' || + event.type === 'human' ) { pointToEventMap.set(event.point.uuid, event); allPoints.push(event.point); diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 5731d9e..9af9d78 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -6,9 +6,10 @@ import { useVehicleEventManager } from '../../vehicle/eventManager/useVehicleEve import { useMachineEventManager } from '../../machine/eventManager/useMachineEventManager'; import { useSceneContext } from '../../../scene/sceneContext'; import { useProductContext } from '../../products/productContext'; +import { useHumanEventManager } from '../../human/eventManager/useHumanEventManager'; export function useTriggerHandler() { - const { materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, productStore } = useSceneContext(); + const { materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); const { selectedProductStore } = useProductContext(); const { handleAction } = useActionHandler(); const { selectedProduct } = selectedProductStore(); @@ -19,7 +20,9 @@ export function useTriggerHandler() { const { addConveyorToMonitor } = useConveyorEventManager(); const { addVehicleToMonitor } = useVehicleEventManager(); const { addMachineToMonitor } = useMachineEventManager(); + const { addHumanToMonitor } = useHumanEventManager(); const { getVehicleById } = vehicleStore(); + const { getHumanById } = humanStore(); const { getMachineById } = machineStore(); const { getStorageUnitById } = storageUnitStore(); const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore(); @@ -256,6 +259,57 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'storageUnit') { // Transfer to Storage Unit + } else if (toEvent?.type === 'human') { + // Transfer to Human + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + const triggeredAction = action; + + // Handle current action of the material + handleAction(action, materialId); + + if (material.next) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: material.next.modelUuid, + pointUuid: material.next.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + if (action) { + + if (human) { + + if (human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < (triggeredAction as HumanAction).loadCapacity) { + + setIsVisible(materialId, false); + + // Handle current action from vehicle + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + addHumanToMonitor(human.modelUuid, triggeredAction.actionUuid, () => { + handleAction(action, materialId); + }) + } + } + } + } + } + } } } else if (fromEvent?.type === 'vehicle') { if (toEvent?.type === 'transfer') { diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 0709109..782d4a0 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -1,14 +1,15 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import VehicleAnimator from '../animator/vehicleAnimator'; import * as THREE from 'three'; import { NavMeshQuery } from '@recast-navigation/core'; import { useNavMesh } from '../../../../../store/builder/store'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; -import MaterialAnimator from '../animator/materialAnimator'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; +import MaterialAnimator from '../animator/materialAnimator'; +import VehicleAnimator from '../animator/vehicleAnimator'; + function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); @@ -103,10 +104,6 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]), agvDetail?.point?.action?.pickUpPoint?.position ); - // const toPickupPath = computePath( - // new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]), - // new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]) - // ); setPath(toPickupPath); setCurrentPhase('stationed-pickup'); setVehicleState(agvDetail.modelUuid, 'running'); @@ -150,7 +147,6 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) // eslint-disable-next-line react-hooks/exhaustive-deps }, [vehicles, currentPhase, path, isPlaying]); - function animate(currentTime: number) { if (previousTimeRef.current === null) { previousTimeRef.current = currentTime; @@ -527,10 +523,4 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) ); } -export default VehicleInstance; - - - - - - +export default VehicleInstance; \ No newline at end of file diff --git a/app/src/store/simulation/useHumanStore.ts b/app/src/store/simulation/useHumanStore.ts index a7ed7d5..0c6ebd9 100644 --- a/app/src/store/simulation/useHumanStore.ts +++ b/app/src/store/simulation/useHumanStore.ts @@ -15,6 +15,10 @@ interface HumansStore { setHumanActive: (modelUuid: string, isActive: boolean) => void; setHumanPicking: (modelUuid: string, isPicking: boolean) => void; setHumanLoad: (modelUuid: string, load: number) => void; + setHumanState: ( + modelUuid: string, + newState: HumanStatus["state"] + ) => void; incrementHumanLoad: (modelUuid: string, incrementBy: number) => void; decrementHumanLoad: (modelUuid: string, decrementBy: number) => void; @@ -106,6 +110,15 @@ export const createHumanStore = () => { }); }, + setHumanState: (modelUuid, newState) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.state = newState; + } + }); + }, + incrementHumanLoad: (modelUuid, incrementBy) => { set((state) => { const human = state.humans.find(h => h.modelUuid === modelUuid); From 46ff5c0208e4275ee55b5c831345972a9b57614f Mon Sep 17 00:00:00 2001 From: Gomathi9520 Date: Thu, 3 Jul 2025 17:55:19 +0530 Subject: [PATCH 09/13] refactor: Update zone handling by integrating zoneStore in multiple components and removing unused zones state --- .../properties/ZoneProperties.tsx | 27 +++++++----- app/src/components/ui/list/DropDownList.tsx | 11 +++-- app/src/components/ui/list/List.tsx | 23 +++++----- .../builder/zone/zoneCreator/zoneCreator.tsx | 5 ++- .../visualization/RealTimeVisulization.tsx | 19 ++++++--- .../visualization/zone/DisplayZone.tsx | 42 ++++++++++--------- .../visualization/zone/zoneCameraTarget.tsx | 1 + app/src/pages/UserAuth.tsx | 1 - .../factoryBuilder/zone/getZonesApi.ts | 5 +-- .../visulization/zone/zoneCameraUpdation.ts | 2 +- 10 files changed, 80 insertions(+), 56 deletions(-) diff --git a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx index 7415878..d129dd5 100644 --- a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx @@ -12,13 +12,17 @@ import { zoneCameraUpdate } from "../../../../services/visulization/zone/zoneCam import { useParams } from "react-router-dom"; import { getUserData } from "../../../../functions/getUserData"; import { useVersionContext } from "../../../../modules/builder/version/versionContext"; +import { useSceneContext } from "../../../../modules/scene/sceneContext"; const ZoneProperties: React.FC = () => { const { Edit, setEdit } = useEditPosition(); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); const { zonePosition, setZonePosition } = usezonePosition(); const { zoneTarget, setZoneTarget } = usezoneTarget(); - const { zones, setZones } = useZones(); + // const { zones, setZones } = useZones(); + const { assetStore, zoneStore } = useSceneContext(); + const { zones, setZoneName } = zoneStore() + const { projectId } = useParams(); const { userName, userId, organization, email } = getUserData(); const { selectedVersionStore } = useVersionContext(); @@ -34,10 +38,11 @@ const ZoneProperties: React.FC = () => { let zonesdata = { zoneUuid: selectedZone.zoneUuid, - viewPortposition: zonePosition, - viewPortCenter: zoneTarget, + viewPortPosition: zonePosition, + viewPortTarget: zoneTarget, }; + let response = await zoneCameraUpdate(zonesdata, organization, projectId, selectedVersion?.versionId || ""); // console.log('response: ', response); if (response.message === "zone updated") { @@ -63,13 +68,14 @@ const ZoneProperties: React.FC = () => { let response = await zoneCameraUpdate(zonesdata, organization, projectId, selectedVersion?.versionId || ""); if (response.message === "zone updated") { setSelectedZone((prev) => ({ ...prev, zoneName: newName })); - setZones((prevZones: any[]) => - prevZones.map((zone) => - zone.zoneUuid === selectedZone.zoneUuid - ? { ...zone, zoneName: newName } - : zone - ) - ); + setZoneName(selectedZone.zoneUuid, newName) + // setZones((prevZones: any[]) => + // prevZones.map((zone) => + // zone.zoneUuid === selectedZone.zoneUuid + // ? { ...zone, zoneName: newName } + // : zone + // ) + // ); } else { // console.log(response?.message); } @@ -81,6 +87,7 @@ const ZoneProperties: React.FC = () => { setSelectedZone((prev) => ({ ...prev, [key]: newValue })); } const checkZoneNameDuplicate = (name: string) => { + console.log('zones: ', zones); return zones.some( (zone: any) => zone.zoneName?.trim().toLowerCase() === name?.trim().toLowerCase() && diff --git a/app/src/components/ui/list/DropDownList.tsx b/app/src/components/ui/list/DropDownList.tsx index 753f154..d75387d 100644 --- a/app/src/components/ui/list/DropDownList.tsx +++ b/app/src/components/ui/list/DropDownList.tsx @@ -44,16 +44,19 @@ const DropDownList: React.FC = ({ remove, }) => { const [isOpen, setIsOpen] = useState(defaultOpen); - const { zones } = useZones(); + // const { zones } = useZones(); const handleToggle = () => { setIsOpen((prev) => !prev); // Toggle the state }; const [zoneDataList, setZoneDataList] = useState([]); - const { assetStore } = useSceneContext(); + // const { assetStore } = useSceneContext(); + const { assetStore, zoneStore } = useSceneContext(); const { assets } = assetStore(); - + const { zones } = zoneStore() + + const isPointInsidePolygon = ( point: [number, number], polygon: [number, number][] @@ -76,7 +79,7 @@ const DropDownList: React.FC = ({ }; useEffect(() => { - const updatedZoneList: ZoneData[] = zones?.map((zone: Zone) => { + const updatedZoneList: ZoneData[] = zones?.map((zone: any) => { const polygon2D = zone.points.map((p: [number, number, number]) => [ p[0], p[2], diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index cf13a4f..dac4b40 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -46,7 +46,7 @@ const List: React.FC = ({ items = [], remove }) => { const { activeModule } = useModuleStore(); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); - const { zones, setZones } = useZones(); + const { setSubModule } = useSubModuleStore(); const [expandedZones, setExpandedZones] = useState>({}); const { projectId } = useParams(); @@ -55,6 +55,8 @@ const List: React.FC = ({ items = [], remove }) => { const { organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const { zoneStore } = useSceneContext(); + const { zones, setZoneName } = zoneStore() useEffect(() => { useSelectedZoneStore.getState().setSelectedZone({ @@ -92,8 +94,8 @@ const List: React.FC = ({ items = [], remove }) => { lockedPanels: response?.lockedPanels ?? [], widgets: response?.widgets ?? [], zoneUuid: response?.zoneUuid, - zoneViewPortTarget: response?.viewPortCenter ?? [], - zoneViewPortPosition: response?.viewPortposition ?? [], + zoneViewPortTarget: response?.viewPortTarget ?? [], + zoneViewPortPosition: response?.viewPortPosition ?? [], }); } catch (error) { echo.error("Failed to select zone"); @@ -123,13 +125,14 @@ const List: React.FC = ({ items = [], remove }) => { const response = await zoneCameraUpdate(zonesdata, organization, projectId, selectedVersion?.versionId || ""); if (response.message === "zone updated") { setSelectedZone((prev) => ({ ...prev, zoneName: newName })); - setZones((prevZones: any[]) => - prevZones.map((zone) => - zone.zoneUuid === selectedZone.zoneUuid - ? { ...zone, zoneName: newName } - : zone - ) - ); + setZoneName(selectedZone.zoneUuid, newName) + // setZones((prevZones: any[]) => + // prevZones.map((zone) => + // zone.zoneUuid === selectedZone.zoneUuid + // ? { ...zone, zoneName: newName } + // : zone + // ) + // ); } } diff --git a/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx b/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx index 1df200f..f1f1b6a 100644 --- a/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx +++ b/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx @@ -20,7 +20,7 @@ function ZoneCreator() { const { activeLayer } = useActiveLayer(); const { socket } = useSocketStore(); const { zoneStore } = useSceneContext(); - const { addZone, getZonePointById, getZoneByPoints } = zoneStore(); + const { zones, addZone, getZonePointById, getZoneByPoints } = zoneStore(); const drag = useRef(false); const isLeftMouseDown = useRef(false); const { selectedVersionStore } = useVersionContext(); @@ -32,6 +32,7 @@ function ZoneCreator() { const [isCreating, setIsCreating] = useState(false); const { zoneColor, zoneHeight, snappedPosition, snappedPoint, setSnappedPoint, setSnappedPosition } = useBuilderStore(); + useEffect(() => { const canvasElement = gl.domElement; @@ -92,7 +93,7 @@ function ZoneCreator() { if (tempPoints.length > 2 && isCreating && snappedPoint && snappedPoint.pointUuid === tempPoints[0].pointUuid) { const zone: Zone = { zoneUuid: THREE.MathUtils.generateUUID(), - zoneName: "Zone", + zoneName: `Zone `, points: tempPoints, zoneColor, zoneHeight, diff --git a/app/src/modules/visualization/RealTimeVisulization.tsx b/app/src/modules/visualization/RealTimeVisulization.tsx index 4f24e05..bec92b3 100644 --- a/app/src/modules/visualization/RealTimeVisulization.tsx +++ b/app/src/modules/visualization/RealTimeVisulization.tsx @@ -17,6 +17,7 @@ import { useWidgetStore } from "../../store/useWidgetStore"; import { useNavigate, useParams } from "react-router-dom"; import { getUserData } from "../../functions/getUserData"; import { useVersionContext } from "../builder/version/versionContext"; +import { useSceneContext } from "../scene/sceneContext"; type Side = "top" | "bottom" | "left" | "right"; @@ -28,6 +29,7 @@ type FormattedZoneData = Record< points: []; lockedPanels: Side[]; zoneUuid: string; + zoneName: string; zoneViewPortTarget: number[]; zoneViewPortPosition: number[]; widgets: Widget[]; @@ -64,6 +66,9 @@ const RealTimeVisulization: React.FC = () => { const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const navigate = useNavigate(); + const { zoneStore } = useSceneContext(); + const { zones } = zoneStore(); + OuterClick({ contextClassName: [ @@ -82,6 +87,7 @@ const RealTimeVisulization: React.FC = () => { useEffect(() => { if (!projectId || !selectedVersion) return; getZone2dData(organization, projectId, selectedVersion?.versionId || '').then((response) => { + // console.log('response: ', response); if (!response) return; // if (response.status === 401) { // console.log("force logout"); @@ -94,19 +100,21 @@ const RealTimeVisulization: React.FC = () => { const formattedData = response.reduce( (acc, zone) => { - acc[zone.zoneName] = { + acc[zone.zoneUuid] = { activeSides: [], panelOrder: [], lockedPanels: [], points: zone.points, zoneUuid: zone.zoneUuid, - zoneViewPortTarget: zone.viewPortCenter, - zoneViewPortPosition: zone.viewPortposition, + zoneName: zone.zoneName, + zoneViewPortTarget: zone.viewPortTarget, + zoneViewPortPosition: zone.viewPortPosition, widgets: [], }; return acc; }, {} ); + // console.log('formattedData: ', formattedData); setZonesData(formattedData); }) @@ -119,13 +127,14 @@ const RealTimeVisulization: React.FC = () => { if (!selectedZone) return prev; return { ...prev, - [selectedZone.zoneName]: { - ...prev[selectedZone.zoneName], // Keep existing properties + [selectedZone.zoneUuid]: { + ...prev[selectedZone.zoneUuid], // Keep existing properties activeSides: selectedZone.activeSides || [], panelOrder: selectedZone.panelOrder || [], lockedPanels: selectedZone.lockedPanels || [], points: selectedZone.points || [], zoneUuid: selectedZone.zoneUuid || "", + zoneName: selectedZone.zoneName || "", zoneViewPortTarget: selectedZone.zoneViewPortTarget || [], zoneViewPortPosition: selectedZone.zoneViewPortPosition || [], widgets: selectedZone.widgets || [], diff --git a/app/src/modules/visualization/zone/DisplayZone.tsx b/app/src/modules/visualization/zone/DisplayZone.tsx index b817c1f..42fe4c4 100644 --- a/app/src/modules/visualization/zone/DisplayZone.tsx +++ b/app/src/modules/visualization/zone/DisplayZone.tsx @@ -34,6 +34,7 @@ interface DisplayZoneProps { points: []; widgets: Widget[]; zoneUuid: string; + zoneName: string; zoneViewPortTarget: number[]; zoneViewPortPosition: number[]; }; @@ -111,8 +112,8 @@ const DisplayZone: React.FC = ({ setShowLeftArrow(isOverflowing && canScrollLeft); setShowRightArrow(isOverflowing && canScrollRight); - // console.log('canScrollRight: ', canScrollRight); - // console.log('isOverflowing: ', isOverflowing); + // + // } }, []); @@ -180,9 +181,10 @@ const DisplayZone: React.FC = ({ // setSelectedChartId(null); let response = await getSelect2dZoneData(zoneUuid, organization, projectId, selectedVersion?.versionId || ''); - // console.log('response2d: ', response); + + // let res = await getFloatingZoneData(zoneUuid, organization, projectId, selectedVersion?.versionId || ''); - // console.log("resFloating: ", res); + // setFloatingWidget(res); // Set the selected zone in the store @@ -201,8 +203,8 @@ const DisplayZone: React.FC = ({ widgets: response.widgets || [], points: response.points || [], zoneUuid: zoneUuid, - zoneViewPortTarget: response.viewPortCenter || {}, - zoneViewPortPosition: response.viewPortposition || {}, + zoneViewPortTarget: response.viewPortTarget || [], + zoneViewPortPosition: response.viewPortPosition || [], }); } catch (error) { echo.error("Failed to select zone"); @@ -238,20 +240,22 @@ const DisplayZone: React.FC = ({ > {Object.keys(zonesData).length !== 0 ? ( <> - {Object.keys(zonesData).map((zoneName, index) => ( -
{ + {Object.values(zonesData).map((zone, index) => ( + <> + { } +
{ - console.log('zonesData: ', zonesData); - handleSelect2dZoneData(zonesData[zoneName]?.zoneUuid, zoneName) - } - } - > - {zoneName} -
+ handleSelect2dZoneData(zonesData[zone.zoneUuid]?.zoneUuid, zone.zoneName) + } + } + > + {zone.zoneName} +
+ ))} ) : ( diff --git a/app/src/modules/visualization/zone/zoneCameraTarget.tsx b/app/src/modules/visualization/zone/zoneCameraTarget.tsx index 6ac0d3d..82f014a 100644 --- a/app/src/modules/visualization/zone/zoneCameraTarget.tsx +++ b/app/src/modules/visualization/zone/zoneCameraTarget.tsx @@ -10,6 +10,7 @@ import { export default function ZoneCentreTarget() { const { selectedZone } = useSelectedZoneStore(); + // const [previousZoneCentre, setPreviousZoneCentre] = useState( null ); diff --git a/app/src/pages/UserAuth.tsx b/app/src/pages/UserAuth.tsx index fec9a47..cdc609e 100644 --- a/app/src/pages/UserAuth.tsx +++ b/app/src/pages/UserAuth.tsx @@ -56,7 +56,6 @@ const UserAuth: React.FC = () => { try { const projects = await recentlyViewed(organization, res.message.userId); - console.log('projects: ', projects); if (res.message.isShare) { if (Object.values(projects.RecentlyViewed).length > 0) { const firstId = (Object.values(projects?.RecentlyViewed || {})[0] as any)?._id; diff --git a/app/src/services/factoryBuilder/zone/getZonesApi.ts b/app/src/services/factoryBuilder/zone/getZonesApi.ts index f0ea687..2714685 100644 --- a/app/src/services/factoryBuilder/zone/getZonesApi.ts +++ b/app/src/services/factoryBuilder/zone/getZonesApi.ts @@ -1,9 +1,6 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; -export const getZonesApi = async ( - projectId: string, - versionId: string, -) => { +export const getZonesApi = async (projectId: string, versionId: string,) => { try { const response = await fetch(`${url_Backend_dwinzo}/api/V1/zones/${projectId}/${versionId}`, { method: "GET", diff --git a/app/src/services/visulization/zone/zoneCameraUpdation.ts b/app/src/services/visulization/zone/zoneCameraUpdation.ts index cfacfcd..9bb3b24 100644 --- a/app/src/services/visulization/zone/zoneCameraUpdation.ts +++ b/app/src/services/visulization/zone/zoneCameraUpdation.ts @@ -3,7 +3,7 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_UR export const zoneCameraUpdate = async (zoneData: {}, organization: string, projectId?: string, versionId?: string) => { try { - const response = await fetch(`${url_Backend_dwinzo}/api/V1/zones`, { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/upsertZone`, { method: "POST", headers: { Authorization: "Bearer ", // Replace with actual token From 7cf82629e945cb8a899f17b27eb4c48786f63930 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 3 Jul 2025 18:01:11 +0530 Subject: [PATCH 10/13] completed init movement for human --- .../mechanics/humanMechanics.tsx | 386 ++---------------- app/src/modules/builder/asset/assetsGroup.tsx | 25 +- .../builder/asset/functions/addAssetModel.ts | 24 +- .../functions/handleAddEventToProduct.ts | 2 +- .../triggerConnections/triggerConnector.tsx | 6 +- .../eventManager/useHumanEventManager.ts | 11 +- app/src/modules/simulation/human/human.tsx | 7 +- .../instances/animator/humanAnimator.tsx | 170 ++++++++ .../instance/animator/humanAnimator.tsx | 10 - .../instances/instance/humanInstance.tsx | 47 ++- .../human/instances/instance/humanUi.tsx | 79 ++-- .../triggerHandler/useTriggerHandler.ts | 2 +- .../instances/animator/vehicleAnimator.tsx | 79 ---- .../instances/instance/vehicleInstance.tsx | 5 +- app/src/store/simulation/useProductStore.ts | 4 +- app/src/types/simulationTypes.d.ts | 12 +- 16 files changed, 302 insertions(+), 567 deletions(-) create mode 100644 app/src/modules/simulation/human/instances/animator/humanAnimator.tsx delete mode 100644 app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index 17e8363..e823697 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -40,15 +40,10 @@ function HumanMechanics() { selectedEventData.selectedPoint ) as HumanPointSchema | undefined; - if (point?.actions) { + if (point?.action) { setSelectedPointData(point); - if (point.actions.length > 0) { - setSelectedAction(point.actions[0].actionUuid, point.actions[0].actionName); - const asset = getAssetById(selectedEventData.data.modelUuid); - if (asset && asset.animations) { - setAnimationOptions(asset.animations) - } - } + setCurrentAction(point.action); + setSelectedAction(point.action.actionUuid, point.action.actionName); } } else { clearSelectedAction(); @@ -57,31 +52,17 @@ function HumanMechanics() { useEffect(() => { if (selectedEventData && selectedProduct.productUuid) { - const event = getEventByModelUuid( - selectedProduct.productUuid, - selectedEventData.data.modelUuid - ) as HumanEventSchema | undefined; - - if (event?.speed !== undefined) { - setSpeed(event.speed.toString()); - } - const point = getPointByUuid( selectedProduct.productUuid, selectedEventData.data.modelUuid, selectedEventData.selectedPoint ) as HumanPointSchema | undefined; - if (point?.actions) { + if (point?.action) { setSelectedPointData(point); - const action = point.actions.find((a) => a.actionUuid === selectedAction.actionId); - if (action) { - setCurrentAction(action); - setActiveOption(action.actionType as "worker"); - if (action.animationSequences.length > 0) { - setSelectedAnimation(action.animationSequences[0]); - } - } + setCurrentAction(point.action); + setActiveOption(point.action.actionType); + setSelectedAction(point.action.actionUuid, point.action.actionName); } } else { clearSelectedAction(); @@ -108,17 +89,8 @@ function HumanMechanics() { const handleSelectActionType = (actionType: string) => { if (!selectedAction.actionId || !currentAction || !selectedPointData) return; - const updatedAction = { - ...currentAction, - actionType: actionType as "worker" - }; - - const updatedPoint = { - ...selectedPointData, - actions: selectedPointData.actions.map(action => - action.actionUuid === selectedAction.actionId ? updatedAction : action - ) - }; + const updatedAction = { ...currentAction, actionType: actionType as "worker" }; + const updatedPoint = { ...selectedPointData, action: updatedAction }; const event = updateAction( selectedProduct.productUuid, @@ -134,43 +106,6 @@ function HumanMechanics() { setSelectedPointData(updatedPoint); }; - const handleChooseAnimation = (animationOption: string) => { - if (!selectedAction.actionId || !currentAction || !selectedAnimation || !selectedPointData) return; - - const updatedAnimation = { - ...selectedAnimation, - animation: animationOption - }; - - const updatedAction = { - ...currentAction, - animationSequences: currentAction.animationSequences.map(anim => - anim.animationUuid === selectedAnimation.animationUuid ? updatedAnimation : anim - ) - }; - - const updatedPoint = { - ...selectedPointData, - actions: selectedPointData.actions.map(action => - action.actionUuid === selectedAction.actionId ? updatedAction : action - ) - }; - - const event = updateAction( - selectedProduct.productUuid, - selectedAction.actionId, - updatedAction - ); - - if (event) { - updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); - } - - setCurrentAction(updatedAction); - setSelectedAnimation(updatedAnimation); - setSelectedPointData(updatedPoint); - }; - const handleSpeedChange = (value: string) => { if (!selectedEventData) return; @@ -195,31 +130,14 @@ function HumanMechanics() { setSpeed(value); }; - const handleClearPoints = (animationUuid: string) => { + const handleClearPoints = () => { if (!currentAction || !selectedPointData || !selectedAction.actionId) return; - const updatedAnimation = currentAction.animationSequences.find(anim => - anim.animationUuid === animationUuid - ); + const updatedAction = { ...currentAction }; + delete updatedAction.pickUpPoint; + delete updatedAction.dropPoint; - if (!updatedAnimation) return; - - delete updatedAnimation.startPoint; - delete updatedAnimation.endPoint; - - const updatedAction = { - ...currentAction, - animationSequences: currentAction.animationSequences.map(anim => - anim.animationUuid === animationUuid ? updatedAnimation : anim - ) - }; - - const updatedPoint = { - ...selectedPointData, - actions: selectedPointData.actions.map(action => - action.actionUuid === selectedAction.actionId ? updatedAction : action - ) - }; + const updatedPoint = { ...selectedPointData, action: updatedAction }; const event = updateAction( selectedProduct.productUuid, @@ -233,9 +151,6 @@ function HumanMechanics() { setCurrentAction(updatedAction); setSelectedPointData(updatedPoint); - if (selectedAnimation?.animationUuid === animationUuid) { - setSelectedAnimation(updatedAnimation); - } }; const handleAddAction = () => { @@ -243,16 +158,8 @@ function HumanMechanics() { const newAction: HumanAction = { actionUuid: MathUtils.generateUUID(), - actionName: `Action ${selectedPointData.actions.length + 1}`, + actionName: `Action`, actionType: "worker", - animationSequences: [ - { - animationUuid: MathUtils.generateUUID(), - animationName: 'Animation 1', - animationType: 'behaviour', - animation: null - } - ], loadCapacity: 1, triggers: [], }; @@ -268,217 +175,27 @@ function HumanMechanics() { updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); } - const updatedPoint = { ...selectedPointData, actions: [...selectedPointData.actions, newAction] }; + const updatedPoint = { ...selectedPointData, action: newAction }; setSelectedPointData(updatedPoint); setSelectedAction(newAction.actionUuid, newAction.actionName); }; - const handleDeleteAction = (actionUuid: string) => { + const handleDeleteAction = () => { if (!selectedPointData) return; - const event = removeAction(selectedProduct.productUuid, actionUuid); - - if (event) { - updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); - } - - const index = selectedPointData.actions.findIndex((a) => a.actionUuid === actionUuid); - const newActions = selectedPointData.actions.filter((a) => a.actionUuid !== actionUuid); - const updatedPoint = { ...selectedPointData, actions: newActions }; - setSelectedPointData(updatedPoint); - - if (selectedAction.actionId === actionUuid) { - const nextAction = newActions[index] || newActions[index - 1]; - if (nextAction) { - setSelectedAction(nextAction.actionUuid, nextAction.actionName); - } else { - clearSelectedAction(); - } - } - }; - - const handleAddAnimation = () => { - if (!currentAction || !selectedPointData || !selectedAction.actionId) return; - - const newAnimation = { - animationUuid: MathUtils.generateUUID(), - animationName: `Animation ${currentAction.animationSequences.length + 1}`, - animationType: 'behaviour' as "behaviour", - animation: null - }; - - const updatedAction = { - ...currentAction, - animationSequences: [...currentAction.animationSequences, newAnimation] - }; - - const updatedPoint = { - ...selectedPointData, - actions: selectedPointData.actions.map(action => - action.actionUuid === selectedAction.actionId ? updatedAction : action - ) - }; - - const event = updateAction( + const event = removeAction( selectedProduct.productUuid, - selectedAction.actionId, - updatedAction + selectedPointData.action.actionUuid ); if (event) { updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); } - setCurrentAction(updatedAction); + const updatedPoint = { ...selectedPointData, action: undefined as any }; setSelectedPointData(updatedPoint); - setSelectedAnimation(newAnimation); - }; - - const handleRemoveAnimation = (animationUuid: string) => { - if (!currentAction || !selectedPointData || !selectedAction.actionId) return; - - const updatedAction = { - ...currentAction, - animationSequences: currentAction.animationSequences.filter( - anim => anim.animationUuid !== animationUuid - ) - }; - - const updatedPoint = { - ...selectedPointData, - actions: selectedPointData.actions.map(action => - action.actionUuid === selectedAction.actionId ? updatedAction : action - ) - }; - - const event = updateAction( - selectedProduct.productUuid, - selectedAction.actionId, - updatedAction - ); - - if (event) { - updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); - } - - setCurrentAction(updatedAction); - setSelectedPointData(updatedPoint); - - if (selectedAnimation?.animationUuid === animationUuid) { - setSelectedAnimation(updatedAction.animationSequences[0] || undefined); - } - }; - - const handleAnimationTypeChange = (animationUuid: string, newType: "behaviour" | "animatedTravel") => { - if (!currentAction || !selectedPointData || !selectedAction.actionId) return; - - const updatedAnimationSequences = currentAction.animationSequences.map(anim => { - if (anim.animationUuid === animationUuid) { - const updatedAnim = { - ...anim, - animationType: newType - }; - - delete updatedAnim.startPoint; - delete updatedAnim.endPoint; - - return updatedAnim; - } - return anim; - }); - - const updatedAction = { - ...currentAction, - animationSequences: updatedAnimationSequences - }; - - const updatedPoint = { - ...selectedPointData, - actions: selectedPointData.actions.map(action => - action.actionUuid === selectedAction.actionId ? updatedAction : action - ) - }; - - const event = updateAction( - selectedProduct.productUuid, - selectedAction.actionId, - updatedAction - ); - - if (event) { - updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); - } - - setCurrentAction(updatedAction); - setSelectedPointData(updatedPoint); - - if (selectedAnimation?.animationUuid === animationUuid) { - const updatedAnimation = updatedAnimationSequences.find(anim => - anim.animationUuid === animationUuid - ); - if (updatedAnimation) { - setSelectedAnimation(updatedAnimation); - } - } - }; - - const handleAnimationSelect = (animationUuid: string) => { - if (!currentAction || !selectedAction.actionId) return; - - const animation = currentAction.animationSequences.find( - anim => anim.animationUuid === animationUuid - ); - - if (animation) { - setSelectedAnimation(animation); - } - }; - - const handleRenameAnimation = (animationUuid: string, newName: string) => { - if (!currentAction || !selectedPointData || !selectedAction.actionId) return; - - const updatedAnimation = currentAction.animationSequences.find(anim => - anim.animationUuid === animationUuid - ); - - if (!updatedAnimation) return; - - const renamedAnimation = { ...updatedAnimation, animationName: newName }; - - const updatedAction = { - ...currentAction, - animationSequences: currentAction.animationSequences.map(anim => - anim.animationUuid === animationUuid ? renamedAnimation : anim - ) - }; - - const updatedPoint = { - ...selectedPointData, - actions: selectedPointData.actions.map(action => - action.actionUuid === selectedAction.actionId ? updatedAction : action - ) - }; - - const event = updateAction( - selectedProduct.productUuid, - selectedAction.actionId, - updatedAction - ); - - if (event) { - updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); - } - - setCurrentAction(updatedAction); - setSelectedPointData(updatedPoint); - if (selectedAnimation?.animationUuid === animationUuid) { - setSelectedAnimation(renamedAnimation); - } - }; - - const availableActions = { - defaultOption: "worker", - options: ["worker"], + clearSelectedAction(); + setCurrentAction(undefined); }; return ( @@ -503,7 +220,7 @@ function HumanMechanics() {
@@ -511,69 +228,20 @@ function HumanMechanics() { {selectedAction.actionId && currentAction && (
- +
- - {selectedAnimation && ( - <> -
- -
-
- - handleAnimationTypeChange( - selectedAnimation.animationUuid, - type as "behaviour" | "animatedTravel" - ) - } - /> - - - {selectedAnimation.animationType === "animatedTravel" && ( - handleClearPoints(selectedAnimation.animationUuid)} - /> - )} -
- - )} +
- +
)} diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index a761d39..547445e 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -256,23 +256,14 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], - actions: [ - { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "worker", - animationSequences: [ - { - animationUuid: THREE.MathUtils.generateUUID(), - animationName: 'Animation 1', - animationType: 'behaviour', - animation: null - } - ], - loadCapacity: 1, - triggers: [] - } - ] + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCapacity: 1, + triggers: [] + } + } } addEvent(humanEvent); diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 43c4b8a..8a09bb7 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -373,23 +373,13 @@ async function handleModelLoad( uuid: THREE.MathUtils.generateUUID(), position: [data.points[0].x, data.points[0].y, data.points[0].z], rotation: [0, 0, 0], - actions: [ - { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "worker", - animationSequences: [ - { - animationUuid: THREE.MathUtils.generateUUID(), - animationName: 'Animation 1', - animationType: 'behaviour', - animation: null - } - ], - loadCapacity: 1, - triggers: [] - } - ] + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCapacity: 1, + triggers: [] + } } } diff --git a/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts b/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts index 9439c3c..0ba13a7 100644 --- a/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts +++ b/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts @@ -30,7 +30,7 @@ export const handleAddEventToProduct = ({ projectId: projectId || '', eventDatas: event }).then((data) => { - console.log(data); + // console.log(data); }) if (clearSelectedAsset) { diff --git a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx index 918007a..ec7406c 100644 --- a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx +++ b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx @@ -155,8 +155,8 @@ function TriggerConnector() { // Handle Human point else if (event.type === "human" && 'point' in event) { const point = event.point; - point.actions?.forEach(action => { - action.triggers?.forEach(trigger => { + if (point.action?.triggers) { + point.action.triggers.forEach(trigger => { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, @@ -166,7 +166,7 @@ function TriggerConnector() { }); } }); - }); + } } }); diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts index 1ab16f7..94ce8b3 100644 --- a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -5,7 +5,6 @@ import { useSceneContext } from '../../../scene/sceneContext'; type HumanCallback = { humanId: string; - actionId: string; callback: () => void; }; @@ -25,10 +24,10 @@ export function useHumanEventManager() { }, [isReset]) // Add a new human to monitor - const addHumanToMonitor = (humanId: string, actionId: string, callback: () => void) => { + const addHumanToMonitor = (humanId: string, callback: () => void) => { // Avoid duplicates if (!callbacksRef.current.some((entry) => entry.humanId === humanId)) { - callbacksRef.current.push({ humanId, actionId, callback }); + callbacksRef.current.push({ humanId, callback }); } // Start monitoring if not already running @@ -53,11 +52,9 @@ export function useHumanEventManager() { useFrame(() => { if (!isMonitoringRef.current || callbacksRef.current.length === 0 || !isPlaying || isPaused) return; - callbacksRef.current.forEach(({ humanId, actionId, callback }) => { + callbacksRef.current.forEach(({ humanId, callback }) => { const human = getHumanById(humanId); - if (!human) return; - const action = human.point.actions.find((action) => action.actionUuid === actionId); - if (action && human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < action.loadCapacity) { + if (human && human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < human.point.action.loadCapacity) { callback(); removeHumanFromMonitor(humanId); // Remove after triggering } diff --git a/app/src/modules/simulation/human/human.tsx b/app/src/modules/simulation/human/human.tsx index 49c9435..767269c 100644 --- a/app/src/modules/simulation/human/human.tsx +++ b/app/src/modules/simulation/human/human.tsx @@ -16,12 +16,7 @@ function Human() { useEffect(() => { if (selectedEventSphere) { const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); - if (selectedHuman && - selectedHuman.point.actions.some((action) => - action.animationSequences.some((animation) => - animation.animationUuid === selectedAnimation?.animationUuid && animation.animationType === 'animatedTravel' - ) - )) { + if (selectedHuman) { setIsHumanSelected(true); } else { setIsHumanSelected(false); diff --git a/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx b/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx new file mode 100644 index 0000000..82934eb --- /dev/null +++ b/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx @@ -0,0 +1,170 @@ +import { useEffect, useRef, useState } from 'react' +import { useFrame, useThree } from '@react-three/fiber'; +import * as THREE from 'three'; +import { Line } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../../scene/sceneContext'; + +interface HumanAnimatorProps { + path: [number, number, number][]; + handleCallBack: () => void; + reset: () => void; + startUnloadingProcess: () => void; + currentPhase: string; + human: HumanStatus; +} + +function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, startUnloadingProcess }: Readonly) { + const { humanStore } = useSceneContext(); + const { getHumanById } = humanStore(); + const { isPaused } = usePauseButtonStore(); + const { isPlaying } = usePlayButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const { isReset, setReset } = useResetButtonStore(); + const progressRef = useRef(0); + const movingForward = useRef(true); + const completedRef = useRef(false); + const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>(human.point?.action?.pickUpPoint?.rotation || [0, 0, 0]) + const [restRotation, setRestingRotation] = useState(true); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + const { scene } = useThree(); + + useEffect(() => { + if (currentPhase === 'init-pickup' && path.length > 0) { + setCurrentPath(path); + setObjectRotation(human.point.action.pickUpPoint?.rotation ?? null) + } else if (currentPhase === 'pickup-drop' && path.length > 0) { + setObjectRotation(human.point.action?.dropPoint?.rotation ?? null) + setCurrentPath(path); + } else if (currentPhase === 'drop-pickup' && path.length > 0) { + setObjectRotation(human.point.action?.pickUpPoint?.rotation ?? null) + setCurrentPath(path); + } + }, [currentPhase, path, objectRotation]); + + useEffect(() => { + completedRef.current = false; + }, [currentPath]); + + useEffect(() => { + if (isReset || !isPlaying) { + reset(); + setCurrentPath([]); + completedRef.current = false; + movingForward.current = true; + progressRef.current = 0; + setReset(false); + setRestingRotation(true); + const object = scene.getObjectByProperty('uuid', human.modelUuid); + const humanData = getHumanById(human.modelUuid); + if (object && humanData) { + object.position.set(humanData.position[0], humanData.position[1], humanData.position[2]); + object.rotation.set(humanData.rotation[0], humanData.rotation[1], humanData.rotation[2]); + } + } + }, [isReset, isPlaying]) + + const lastTimeRef = useRef(performance.now()); + + useFrame(() => { + const now = performance.now(); + const delta = (now - lastTimeRef.current) / 1000; + lastTimeRef.current = now; + + const object = scene.getObjectByProperty('uuid', human.modelUuid); + if (!object || currentPath.length < 2) return; + if (isPaused) return; + + let totalDistance = 0; + const distances = []; + let accumulatedDistance = 0; + let index = 0; + const rotationSpeed = 1; + + for (let i = 0; i < currentPath.length - 1; i++) { + const start = new THREE.Vector3(...currentPath[i]); + const end = new THREE.Vector3(...currentPath[i + 1]); + const segmentDistance = start.distanceTo(end); + distances.push(segmentDistance); + totalDistance += segmentDistance; + } + + while (index < distances.length && progressRef.current > accumulatedDistance + distances[index]) { + accumulatedDistance += distances[index]; + index++; + } + + if (index < distances.length) { + const start = new THREE.Vector3(...currentPath[index]); + const end = new THREE.Vector3(...currentPath[index + 1]); + const segmentDistance = distances[index]; + + const currentDirection = new THREE.Vector3().subVectors(end, start).normalize(); + const targetAngle = Math.atan2(currentDirection.x, currentDirection.z); + const currentAngle = object.rotation.y; + + let angleDifference = targetAngle - currentAngle; + if (angleDifference > Math.PI) angleDifference -= 2 * Math.PI; + if (angleDifference < -Math.PI) angleDifference += 2 * Math.PI; + + const maxRotationStep = (rotationSpeed * speed * human.speed) * delta; + object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep); + const isAligned = Math.abs(angleDifference) < 0.01; + + if (isAligned) { + progressRef.current += delta * (speed * human.speed); + const t = (progressRef.current - accumulatedDistance) / segmentDistance; + const position = start.clone().lerp(end, t); + object.position.copy(position); + } + } + + if (progressRef.current >= totalDistance) { + if (restRotation && objectRotation) { + const targetEuler = new THREE.Euler( + objectRotation[0], + objectRotation[1] + Math.PI, + objectRotation[2] + ); + const targetQuaternion = new THREE.Quaternion().setFromEuler(targetEuler); + object.quaternion.slerp(targetQuaternion, delta * (rotationSpeed * speed * human.speed)); + if (object.quaternion.angleTo(targetQuaternion) < 0.01) { + object.quaternion.copy(targetQuaternion); + object.rotation.copy(targetEuler); + setRestingRotation(false); + } + return; + } + } + + if (progressRef.current >= totalDistance) { + setRestingRotation(true); + progressRef.current = 0; + movingForward.current = !movingForward.current; + setCurrentPath([]); + handleCallBack(); + if (currentPhase === 'pickup-drop') { + requestAnimationFrame(startUnloadingProcess); + } + } + }); + + return ( + <> + {currentPath.length > 0 && ( + // helper + + + {currentPath.map((point, index) => ( + + + + + ))} + + )} + + ) +} + +export default HumanAnimator \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx b/app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx deleted file mode 100644 index 01f569e..0000000 --- a/app/src/modules/simulation/human/instances/instance/animator/humanAnimator.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -function HumanAnimator() { - return ( - <> - - ) -} - -export default HumanAnimator \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index 6e1ebc0..d3fe30a 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -7,7 +7,7 @@ import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHa import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; -import HumanAnimator from './animator/humanAnimator'; +import HumanAnimator from '../animator/humanAnimator'; function HumanInstance({ human }: { human: HumanStatus }) { const { navMesh } = useNavMesh(); @@ -66,12 +66,10 @@ function HumanInstance({ human }: { human: HumanStatus }) { console.error("Failed to compute path"); return []; } - }, - [navMesh] - ); + }, [navMesh]); function humanStatus(modelId: string, status: string) { - // console.log(`${modelId} , ${status}`); + console.log(`${modelId} , ${status}`); } function reset() { @@ -96,6 +94,25 @@ function HumanInstance({ human }: { human: HumanStatus }) { useEffect(() => { if (isPlaying) { + if (!human.point.action.pickUpPoint || !human.point.action.dropPoint) return; + + if (!human.isActive && human.state === 'idle' && currentPhase === 'init') { + const toPickupPath = computePath( + new THREE.Vector3(human?.position[0], human?.position[1], human?.position[2]), + new THREE.Vector3( + human?.point?.action?.pickUpPoint?.position?.[0] ?? 0, + human?.point?.action?.pickUpPoint?.position?.[1] ?? 0, + human?.point?.action?.pickUpPoint?.position?.[2] ?? 0 + ) + ); + setPath(toPickupPath); + setCurrentPhase('init-pickup'); + setHumanState(human.modelUuid, 'running'); + setHumanPicking(human.modelUuid, false); + setHumanActive(human.modelUuid, true); + humanStatus(human.modelUuid, 'Started from init, heading to pickup'); + return; + } } else { @@ -104,10 +121,28 @@ function HumanInstance({ human }: { human: HumanStatus }) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [human, currentPhase, path, isPlaying]); + function handleCallBack() { + if (currentPhase === 'init-pickup') { + setCurrentPhase('picking'); + } else if (currentPhase === 'pickup-drop') { + } else if (currentPhase === 'drop-pickup') { + } + } + + function startUnloadingProcess() { + } + return ( <> - + ) diff --git a/app/src/modules/simulation/human/instances/instance/humanUi.tsx b/app/src/modules/simulation/human/instances/instance/humanUi.tsx index cfb0fc9..f3688c5 100644 --- a/app/src/modules/simulation/human/instances/instance/humanUi.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanUi.tsx @@ -60,35 +60,29 @@ function HumanUi() { useEffect(() => { if (!selectedEventSphere) return; + const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); + if (!selectedHuman || !selectedHuman.point?.action) return; - if (selectedHuman) { - setSelectedHumanData({ - position: selectedHuman.position, - rotation: selectedHuman.rotation, - }); + setSelectedHumanData({ + position: selectedHuman.position, + rotation: selectedHuman.rotation, + }); - if (selectedHuman.point?.actions && selectedAction.actionId) { - const action = selectedHuman.point.actions.find(a => a.actionUuid === selectedAction.actionId); + const action = selectedHuman.point.action; - if (action?.animationSequences[0]) { - const sequence = action.animationSequences[0]; + if (action.pickUpPoint?.position && outerGroup.current) { + const worldPos = new Vector3(...action.pickUpPoint.position); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + setStartPosition([localPosition.x, 0.5, localPosition.z]); + setStartRotation(action.pickUpPoint.rotation || [0, 0, 0]); + } - if (sequence.startPoint?.position && outerGroup.current) { - const worldPos = new Vector3(...sequence.startPoint.position); - const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); - setStartPosition([localPosition.x, 0.5, localPosition.z]); - setStartRotation(sequence.startPoint.rotation || [0, 0, 0]); - } - - if (sequence.endPoint?.position && outerGroup.current) { - const worldPos = new Vector3(...sequence.endPoint.position); - const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); - setEndPosition([localPosition.x, 0.5, localPosition.z]); - setEndRotation(sequence.endPoint.rotation || [0, 0, 0]); - } - } - } + if (action.dropPoint?.position && outerGroup.current) { + const worldPos = new Vector3(...action.dropPoint.position); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + setEndPosition([localPosition.x, 0.5, localPosition.z]); + setEndRotation(action.dropPoint.rotation || [0, 0, 0]); } }, [selectedEventSphere, outerGroup.current, selectedAction, humans]); @@ -126,29 +120,17 @@ function HumanUi() { const worldPosEnd = new Vector3(...endPosition); const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone()); - const updatedActions = selectedHuman.point.actions.map(action => { - if (action.actionUuid === selectedAction.actionId) { - const updatedSequences = action.animationSequences.map(sequence => { - return { - ...sequence, - startPoint: { - position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number], - rotation: startRotation - }, - endPoint: { - position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number], - rotation: endRotation - } - }; - }); - - return { - ...action, - animationSequences: updatedSequences - }; + const updatedAction = { + ...selectedHuman.point.action, + pickUpPoint: { + position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number], + rotation: startRotation + }, + dropPoint: { + position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number], + rotation: endRotation } - return action; - }); + }; const event = updateEvent( selectedProduct.productUuid, @@ -157,7 +139,7 @@ function HumanUi() { ...selectedHuman, point: { ...selectedHuman.point, - actions: updatedActions + action: updatedAction } } ); @@ -195,8 +177,7 @@ function HumanUi() { const currentPointerX = state.pointer.x; const deltaX = currentPointerX - prevMousePos.current.x; prevMousePos.current.x = currentPointerX; - const marker = - isRotating === "start" ? startMarker.current : endMarker.current; + const marker =isRotating === "start" ? startMarker.current : endMarker.current; if (marker) { const rotationSpeed = 10; marker.rotation.y += deltaX * rotationSpeed; diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 9af9d78..cd1a67c 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -301,7 +301,7 @@ export function useTriggerHandler() { } else { // Handle current action using Event Manager - addHumanToMonitor(human.modelUuid, triggeredAction.actionUuid, () => { + addHumanToMonitor(human.modelUuid, () => { handleAction(action, materialId); }) } diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index 9d45fd8..a5255e0 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -32,7 +32,6 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai useEffect(() => { if (currentPhase === 'stationed-pickup' && path.length > 0) { - // console.log('path: ', path); setCurrentPath(path); setObjectRotation(agvDetail.point.action?.pickUpPoint?.rotation) } else if (currentPhase === 'pickup-drop' && path.length > 0) { @@ -66,84 +65,6 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai } }, [isReset, isPlaying]) - // useFrame((_, delta) => { - // const object = scene.getObjectByProperty('uuid', agvUuid); - // if (!object || currentPath.length < 2) return; - // if (isPaused) return; - - // let totalDistance = 0; - // const distances = []; - // let accumulatedDistance = 0; - // let index = 0; - // const rotationSpeed = 1; - - // for (let i = 0; i < currentPath.length - 1; i++) { - // const start = new THREE.Vector3(...currentPath[i]); - // const end = new THREE.Vector3(...currentPath[i + 1]); - // const segmentDistance = start.distanceTo(end); - // distances.push(segmentDistance); - // totalDistance += segmentDistance; - // } - - // while (index < distances.length && progressRef.current > accumulatedDistance + distances[index]) { - // accumulatedDistance += distances[index]; - // index++; - // } - - // if (index < distances.length) { - // const start = new THREE.Vector3(...currentPath[index]); - // const end = new THREE.Vector3(...currentPath[index + 1]); - // const segmentDistance = distances[index]; - - // const currentDirection = new THREE.Vector3().subVectors(end, start).normalize(); - // const targetAngle = Math.atan2(currentDirection.x, currentDirection.z); - - // const currentAngle = object.rotation.y; - - // let angleDifference = targetAngle - currentAngle; - // if (angleDifference > Math.PI) angleDifference -= 2 * Math.PI; - // if (angleDifference < -Math.PI) angleDifference += 2 * Math.PI; - - // const maxRotationStep = (rotationSpeed * speed * agvDetail.speed) * delta; - // object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep); - // const isAligned = Math.abs(angleDifference) < 0.01; - - // if (isAligned) { - // progressRef.current += delta * (speed * agvDetail.speed); - // const t = (progressRef.current - accumulatedDistance) / segmentDistance; - // const position = start.clone().lerp(end, t); - // object.position.copy(position); - // } - // } - - // if (progressRef.current >= totalDistance) { - // if (restRotation && objectRotation) { - // const targetEuler = new THREE.Euler( - // objectRotation.x, - // objectRotation.y - (agvDetail.point.action.steeringAngle), - // objectRotation.z - // ); - // const targetQuaternion = new THREE.Quaternion().setFromEuler(targetEuler); - // object.quaternion.slerp(targetQuaternion, delta * (rotationSpeed * speed * agvDetail.speed)); - // if (object.quaternion.angleTo(targetQuaternion) < 0.01) { - // object.quaternion.copy(targetQuaternion); - // object.rotation.copy(targetEuler); - // setRestingRotation(false); - // } - // return; - // } - // } - // if (progressRef.current >= totalDistance) { - // setRestingRotation(true); - // progressRef.current = 0; - // movingForward.current = !movingForward.current; - // setCurrentPath([]); - // handleCallBack(); - // if (currentPhase === 'pickup-drop') { - // requestAnimationFrame(startUnloadingProcess); - // } - // } - // }); const lastTimeRef = useRef(performance.now()); useFrame(() => { diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 782d4a0..09b498c 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -104,6 +104,10 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]), agvDetail?.point?.action?.pickUpPoint?.position ); + // const toPickupPath = computePath( + // new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]), + // new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]) + // ); setPath(toPickupPath); setCurrentPhase('stationed-pickup'); setVehicleState(agvDetail.modelUuid, 'running'); @@ -193,7 +197,6 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) }; }, [agvDetail, isPlaying]); - function handleCallBack() { if (currentPhase === 'stationed-pickup') { setCurrentPhase('picking'); diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 18d636b..3b483ea 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -32,13 +32,13 @@ type ProductsStore = { productUuid: string, modelUuid: string, pointUuid: string, - action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0] + action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['action'] ) => EventsSchema | undefined; removeAction: (productUuid: string, actionUuid: string) => EventsSchema | undefined; updateAction: ( productUuid: string, actionUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Trigger-level actionss diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 02d66c8..678f117 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -73,14 +73,8 @@ interface HumanAction { actionUuid: string; actionName: string; actionType: "worker"; - animationSequences: { - animationUuid: string; - animationName: string; - animationType: "behaviour" | "animatedTravel"; - animation: string | null; - startPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } - endPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } - }[] + pickUpPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } + dropPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } loadCapacity: number; triggers: TriggerSchema[]; } @@ -127,7 +121,7 @@ interface HumanPointSchema { uuid: string; position: [number, number, number]; rotation: [number, number, number]; - actions: HumanAction[]; + action: HumanAction; } type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema; From 02490214d9fc1fafb16f5a2eb1b75d48fe04fad2 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 4 Jul 2025 13:14:39 +0530 Subject: [PATCH 11/13] feat: Enhance InputWithDropDown to support disabled state fix: Update Model component to manage animation states and transitions more effectively feat: Implement worker action handling in useWorkerHandler for material management feat: Add MaterialAnimator to HumanInstance for dynamic material loading feat: Extend useTriggerHandler to support interactions between humans and various entities feat: Create WorkerAction component for managing load capacity and actions feat: Introduce MaterialAnimator for human instances to visualize material loads refactor: Update asset store to manage animation completion state fix: Ensure proper handling of human materials in useHumanStore --- .../properties/AssetProperties.tsx | 2 +- .../eventProperties/actions/TravelAction.tsx | 143 ++--- .../eventProperties/actions/workerAction.tsx | 54 ++ .../components/AnimationList.tsx | 97 ---- .../mechanics/humanMechanics.tsx | 55 +- .../mechanics/vehicleMechanics.tsx | 8 - .../ui/inputs/InputWithDropDown.tsx | 3 + .../builder/asset/models/model/model.tsx | 68 +-- .../human/actionHandler/useWorkerHandler.ts | 4 +- .../instances/animator/humanAnimator.tsx | 19 +- .../instances/animator/materialAnimator.tsx | 44 ++ .../instances/instance/humanInstance.tsx | 535 +++++++++++++++++- .../triggerHandler/useTriggerHandler.ts | 188 +++++- .../instances/animator/materialAnimator.tsx | 7 +- app/src/store/builder/useAssetStore.ts | 21 +- app/src/store/simulation/useHumanStore.ts | 2 +- app/src/types/builderTypes.d.ts | 1 + 17 files changed, 990 insertions(+), 261 deletions(-) create mode 100644 app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx delete mode 100644 app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx create mode 100644 app/src/modules/simulation/human/instances/animator/materialAnimator.tsx diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index 5cb4b4e..6607937 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -35,7 +35,7 @@ const AssetProperties: React.FC = () => { const handleAnimationClick = (animation: string) => { if (selectedFloorItem) { - setCurrentAnimation(selectedFloorItem.uuid, animation, true, loopAnimation); + setCurrentAnimation(selectedFloorItem.uuid, animation, true, loopAnimation, true); } } diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx index 219fce3..db11181 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx @@ -1,95 +1,70 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -import EyeDropInput from "../../../../../ui/inputs/EyeDropInput"; interface TravelActionProps { - loadCapacity: { - value: string; - min: number; - max: number; - defaultValue: string; - onChange: (value: string) => void; - }; - unloadDuration: { - value: string; - min: number; - max: number; - defaultValue: string; - onChange: (value: string) => void; - }; - pickPoint?: { - value: string; - onChange: (value: string) => void; - }; - unloadPoint?: { - value: string; - onChange: (value: string) => void; - }; - clearPoints: () => void; + loadCapacity: { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; + }; + unloadDuration: { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; + }; + clearPoints: () => void; } const TravelAction: React.FC = ({ - loadCapacity, - unloadDuration, - pickPoint, - unloadPoint, - clearPoints, + loadCapacity, + unloadDuration, + clearPoints, }) => { - return ( - <> - {}} - onChange={loadCapacity.onChange} - /> - {}} - onChange={unloadDuration.onChange} - /> -
-
-
Reset
- -
-
- {pickPoint && ( - - )} - {unloadPoint && ( - - )} - - ); + return ( + <> + { }} + onChange={loadCapacity.onChange} + /> + { }} + onChange={unloadDuration.onChange} + /> +
+
+
Reset
+ +
+
+ + ); }; export default TravelAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx new file mode 100644 index 0000000..6cda9a0 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; + +interface WorkerActionProps { + loadCapacity: { + value: string; + min: number; + max: number; + step: number; + defaultValue: string; + disabled?: boolean, + onChange: (value: string) => void; + }; + clearPoints: () => void; +} + +const WorkerAction: React.FC = ({ + loadCapacity, + clearPoints, +}) => { + return ( + <> + { }} + onChange={loadCapacity.onChange} + /> +
+
+
Reset
+ +
+
+ + ); +}; + +export default WorkerAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx deleted file mode 100644 index 836432d..0000000 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/components/AnimationList.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React, { useRef } from "react"; -import { AddIcon, RemoveIcon, ResizeHeightIcon } from "../../../../../icons/ExportCommonIcons"; -import { handleResize } from "../../../../../../functions/handleResizePannel"; -import { useSelectedAction, useSelectedAnimation } from "../../../../../../store/simulation/useSimulationStore"; -import RenameInput from "../../../../../ui/inputs/RenameInput"; - -interface AnimationListProps { - animationOptions: string[]; - animationSequences: { - animationUuid: string; - animationName: string; - animationType: "behaviour" | "animatedTravel"; - animation: string | null; - travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null }; - }[]; - onAddAnimation: () => void; - onRemoveAnimation: (animationUuid: string) => void; - handleAnimationSelect: (animationUuid: string) => void; - handleRenameAnimation: (animationUuid: string, newName: string) => void; -} - -const AnimationList: React.FC = ({ - animationSequences, - onAddAnimation, - onRemoveAnimation, - handleAnimationSelect, - handleRenameAnimation, -}) => { - const animationContainerRef = useRef(null); - const { selectedAction } = useSelectedAction(); - const { selectedAnimation } = useSelectedAnimation(); - - return ( -
-
-
-
Animation Sequences
- -
-
-
- {animationSequences.map((sequence) => ( -
- - {animationSequences.length > 1 && ( - - )} -
- ))} -
- {animationSequences.length > 0 && ( - - )} -
-
-
- ); -}; - -export default AnimationList; \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index e823697..1e7c423 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -4,30 +4,27 @@ import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; -import PickAndPlaceAction from "../actions/PickAndPlaceAction"; import ActionsList from "../components/ActionsList"; -import AnimationList from "../components/AnimationList"; import { useSelectedEventData, useSelectedAction, useSelectedAnimation } from "../../../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; import { useParams } from "react-router-dom"; +import WorkerAction from "../actions/workerAction"; function HumanMechanics() { const [activeOption, setActiveOption] = useState<"worker">("worker"); - const [animationOptions, setAnimationOptions] = useState([]); const [speed, setSpeed] = useState("0.5"); + const [loadCapacity, setLoadCapacity] = useState("1"); const [currentAction, setCurrentAction] = useState(); const [selectedPointData, setSelectedPointData] = useState(); const { selectedEventData } = useSelectedEventData(); - const { productStore, assetStore } = useSceneContext(); - const { getAssetById } = assetStore(); - const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction, addAction, removeAction } = productStore(); + const { productStore } = useSceneContext(); + const { getPointByUuid, updateEvent, updateAction, addAction, removeAction, getEventByModelUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); - const { selectedAnimation, setSelectedAnimation } = useSelectedAnimation(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); @@ -44,6 +41,13 @@ function HumanMechanics() { setSelectedPointData(point); setCurrentAction(point.action); setSelectedAction(point.action.actionUuid, point.action.actionName); + setSpeed(( + getEventByModelUuid( + selectedProduct.productUuid, + selectedEventData?.data.modelUuid || "" + ) as HumanEventSchema | undefined + )?.speed?.toString() || "1"); + setLoadCapacity(point.action.loadCapacity.toString()); } } else { clearSelectedAction(); @@ -68,6 +72,7 @@ function HumanMechanics() { clearSelectedAction(); setCurrentAction(undefined); setSpeed("0.5"); + setLoadCapacity("1"); } }, [selectedEventData, selectedProduct, selectedAction]); @@ -130,6 +135,29 @@ function HumanMechanics() { setSpeed(value); }; + const handleLoadCapacityChange = (value: string) => { + if (!currentAction || !selectedPointData || !selectedAction.actionId) return; + + const updatedAction = { ...currentAction }; + updatedAction.loadCapacity = parseInt(value) + + const updatedPoint = { ...selectedPointData, action: updatedAction }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + setLoadCapacity(value); + }; + const handleClearPoints = () => { if (!currentAction || !selectedPointData || !selectedAction.actionId) return; @@ -239,7 +267,18 @@ function HumanMechanics() { disabled={true} />
- +
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx index 78583d8..eb9e41c 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -272,14 +272,6 @@ function VehicleMechanics() { onChange: handleUnloadDurationChange, }} clearPoints={handleClearPoints} - // pickPoint={{ - // value: currentPickPoint, - // onChange: handlePickPointChange, - // }} - // unloadPoint={{ - // value: currentUnloadPoint, - // onChange: handleUnloadPointChange, - // }} /> )} diff --git a/app/src/components/ui/inputs/InputWithDropDown.tsx b/app/src/components/ui/inputs/InputWithDropDown.tsx index f749af7..61fbaf4 100644 --- a/app/src/components/ui/inputs/InputWithDropDown.tsx +++ b/app/src/components/ui/inputs/InputWithDropDown.tsx @@ -8,6 +8,7 @@ type InputWithDropDownProps = { max?: number; step?: number; defaultValue?: string; + disabled?: boolean; options?: string[]; // Array of dropdown options activeOption?: string; // The currently active dropdown option onClick?: () => void; @@ -23,6 +24,7 @@ const InputWithDropDown: React.FC = ({ max, step, defaultValue, + disabled = false, options, activeOption, onClick, @@ -54,6 +56,7 @@ const InputWithDropDown: React.FC = ({ type="number" defaultValue={value} // value={value} + disabled={disabled} onChange={(e) => { onChange(e.target.value); }} diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index ba47e9e..f3baadd 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -24,7 +24,7 @@ function Model({ asset }: { readonly asset: Asset }) { const { subModule } = useSubModuleStore(); const { activeModule } = useModuleStore(); const { assetStore, eventStore, productStore } = useSceneContext(); - const { removeAsset, setAnimations, resetAnimation } = assetStore(); + const { removeAsset, setAnimations, resetAnimation, setAnimationComplete, setCurrentAnimation: setAnmationAnimation } = assetStore(); const { setTop } = useTopData(); const { setLeft } = useLeftData(); const { getIsEventInProduct } = productStore(); @@ -49,10 +49,9 @@ function Model({ asset }: { readonly asset: Asset }) { const { userId, organization } = getUserData(); const mixerRef = useRef(); const actions = useRef<{ [name: string]: THREE.AnimationAction }>({}); - const [currentAnimation, setCurrentAnimation] = useState(null); const [previousAnimation, setPreviousAnimation] = useState(null); - const [blendFactor, setBlendFactor] = useState(0); - const blendDuration = 0.3; + const blendFactor = useRef(0); + const blendDuration = 0.5; useEffect(() => { setDeletableFloorItem(null); @@ -283,63 +282,48 @@ function Model({ asset }: { readonly asset: Asset }) { } const handleAnimationComplete = useCallback(() => { - console.log(`Animation "${currentAnimation}" completed`); - }, [currentAnimation]); + if (asset.animationState) { + setAnimationComplete(asset.modelUuid, true); + } + }, [asset.animationState]); useFrame((_, delta) => { if (mixerRef.current) { - if (blendFactor < 1) { - setBlendFactor(prev => Math.min(prev + delta / blendDuration, 1)); - } - mixerRef.current.update(delta); } }); useEffect(() => { - if (asset.animationState && asset.animationState.isPlaying) { - if (!mixerRef.current) return; + if (!asset.animationState || !mixerRef.current) return; - if (asset.animationState.current !== currentAnimation) { - setPreviousAnimation(currentAnimation); - setCurrentAnimation(asset.animationState.current); - setBlendFactor(0); + const { current, loopAnimation, isPlaying } = asset.animationState; + const currentAction = actions.current[current]; + const previousAction = previousAnimation ? actions.current[previousAnimation] : null; + + if (isPlaying && currentAction) { + blendFactor.current = 0; + + currentAction.reset(); + currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1); + currentAction.clampWhenFinished = true; + + if (previousAction && previousAction !== currentAction) { + previousAction.crossFadeTo(currentAction, blendDuration, false); } - const currentAction = actions.current[asset.animationState.current]; - const previousAction = previousAnimation ? actions.current[previousAnimation] : null; - - if (currentAction) { - const loopMode = asset.animationState.loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce; - - currentAction.reset(); - currentAction.setLoop(loopMode, loopMode === THREE.LoopRepeat ? Infinity : 1); - - currentAction.play(); - - mixerRef.current.addEventListener('finished', handleAnimationComplete); - - if (previousAction && blendFactor < 1) { - previousAction.crossFadeTo(currentAction, blendDuration, true); - } - } - - Object.entries(actions.current).forEach(([name, action]) => { - if ((asset.animationState && name !== asset.animationState.current) && name !== previousAnimation) { - action.stop(); - } - }); + currentAction.play(); + mixerRef.current.addEventListener('finished', handleAnimationComplete); + setPreviousAnimation(current); } else { Object.values(actions.current).forEach((action) => action.stop()); - setCurrentAnimation(null); } return () => { if (mixerRef.current) { mixerRef.current.removeEventListener('finished', handleAnimationComplete); } - } - }, [asset.animationState, currentAnimation, previousAnimation, handleAnimationComplete]); + }; + }, [asset.animationState?.current, asset.animationState?.isPlaying]); return ( { echo.info(`${materialUuid}, ${status}`); @@ -23,6 +23,8 @@ export function useWorkerHandler() { const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid); if (!modelUuid) return; + incrementHumanLoad(modelUuid, 1); + addCurrentMaterial(modelUuid, material.materialType, material.materialId); workerLogStatus(material.materialName, `performing worker action`); diff --git a/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx b/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx index 82934eb..8e818fa 100644 --- a/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx @@ -15,8 +15,9 @@ interface HumanAnimatorProps { } function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, startUnloadingProcess }: Readonly) { - const { humanStore } = useSceneContext(); + const { humanStore, assetStore } = useSceneContext(); const { getHumanById } = humanStore(); + const { setCurrentAnimation } = assetStore(); const { isPaused } = usePauseButtonStore(); const { isPlaying } = usePlayButtonStore(); const { speed } = useAnimationPlaySpeed(); @@ -116,6 +117,17 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start const t = (progressRef.current - accumulatedDistance) / segmentDistance; const position = start.clone().lerp(end, t); object.position.copy(position); + if (human.currentMaterials.length > 0) { + setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true); + } else { + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + } + } else { + if (human.currentMaterials.length > 0) { + setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true); + } else { + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + } } } @@ -133,6 +145,11 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start object.rotation.copy(targetEuler); setRestingRotation(false); } + if (human.currentMaterials.length > 0) { + setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true); + } else { + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + } return; } } diff --git a/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx new file mode 100644 index 0000000..ffea67a --- /dev/null +++ b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx @@ -0,0 +1,44 @@ +import { useEffect, useRef, useState } from 'react'; +import { useThree } from '@react-three/fiber'; +import * as THREE from 'three'; +import { MaterialModel } from '../../../materials/instances/material/materialModel'; + +const MaterialAnimator = ({ human }: { human: HumanStatus }) => { + const meshRef = useRef(null!); + const [hasLoad, setHasLoad] = useState(false); + const { scene } = useThree(); + + useEffect(() => { + setHasLoad(human.currentLoad > 0); + }, [human.currentLoad]); + + useEffect(() => { + if (!hasLoad || !meshRef.current) return; + + const humanModel = scene.getObjectByProperty("uuid", human.modelUuid) as THREE.Object3D; + if (!humanModel) return; + + const bone = humanModel.getObjectByName('PlaceObjectRefBone') as THREE.Bone; + if (bone) { + bone.add(meshRef.current); + + meshRef.current.position.set(0, 0, 0); + meshRef.current.rotation.set(0, 0, 0); + meshRef.current.scale.set(1, 1, 1); + } + }, [hasLoad, human.modelUuid]); + + return ( + <> + {hasLoad && human.currentMaterials.length > 0 && ( + + )} + + ); +}; + +export default MaterialAnimator; diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index d3fe30a..7d75aa7 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -8,21 +8,24 @@ import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; import HumanAnimator from '../animator/humanAnimator'; +import MaterialAnimator from '../animator/materialAnimator'; function HumanInstance({ human }: { human: HumanStatus }) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); - const { materialStore, armBotStore, conveyorStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); + const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); const { removeMaterial, setEndTime } = materialStore(); const { getStorageUnitById } = storageUnitStore(); const { getArmBotById } = armBotStore(); const { getConveyorById } = conveyorStore(); const { getVehicleById } = vehicleStore(); + const { getMachineById } = machineStore(); const { triggerPointActions } = useTriggerHandler(); + const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore(); const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { humans, setHumanActive, setHumanState, setHumanPicking, clearCurrentMaterials, setHumanLoad, decrementHumanLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = humanStore(); + const { setHumanActive, setHumanState, setHumanPicking, clearCurrentMaterials, setHumanLoad, decrementHumanLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = humanStore(); const [currentPhase, setCurrentPhase] = useState('init'); const [path, setPath] = useState<[number, number, number][]>([]); @@ -32,11 +35,11 @@ function HumanInstance({ human }: { human: HumanStatus }) { const isPausedRef = useRef(false); const isSpeedRef = useRef(0); let startTime: number; - let fixedInterval: number; const { speed } = useAnimationPlaySpeed(); const { isPaused } = usePauseButtonStore(); const previousTimeRef = useRef(null); const animationFrameIdRef = useRef(null); + const humanAsset = getAssetById(human.modelUuid); useEffect(() => { isPausedRef.current = isPaused; @@ -69,7 +72,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { }, [navMesh]); function humanStatus(modelId: string, status: string) { - console.log(`${modelId} , ${status}`); + // console.log(`${modelId} , ${status}`); } function reset() { @@ -78,6 +81,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { setHumanPicking(human.modelUuid, false); setHumanState(human.modelUuid, 'idle'); setHumanLoad(human.modelUuid, 0); + resetAnimation(human.modelUuid); setPath([]); startTime = 0; isPausedRef.current = false; @@ -110,8 +114,57 @@ function HumanInstance({ human }: { human: HumanStatus }) { setHumanState(human.modelUuid, 'running'); setHumanPicking(human.modelUuid, false); setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); humanStatus(human.modelUuid, 'Started from init, heading to pickup'); return; + } else if (!human.isActive && human.state === 'idle' && currentPhase === 'picking') { + if (humanAsset && human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0 && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState?.isCompleted) { + if (human.point.action.pickUpPoint && human.point.action.dropPoint) { + const toDrop = computePath( + new THREE.Vector3( + human.point.action.pickUpPoint.position?.[0] ?? 0, + human.point.action.pickUpPoint.position?.[1] ?? 0, + human.point.action.pickUpPoint.position?.[2] ?? 0 + ), + new THREE.Vector3( + human.point.action.dropPoint.position?.[0] ?? 0, + human.point.action.dropPoint.position?.[1] ?? 0, + human.point.action.dropPoint.position?.[2] ?? 0 + ) + ); + setPath(toDrop); + setCurrentPhase('pickup-drop'); + setHumanState(human.modelUuid, 'running'); + setHumanPicking(human.modelUuid, false); + setHumanPicking(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true); + humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point'); + } + } else if (human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0) { + setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); + } + } else if (!human.isActive && human.state === 'idle' && currentPhase === 'dropping' && human.currentLoad === 0) { + if (human.point.action.pickUpPoint && human.point.action.dropPoint) { + const dropToPickup = computePath( + new THREE.Vector3( + human.point.action.dropPoint.position?.[0] ?? 0, + human.point.action.dropPoint.position?.[1] ?? 0, + human.point.action.dropPoint.position?.[2] ?? 0 + ), + new THREE.Vector3( + human.point.action.pickUpPoint.position?.[0] ?? 0, + human.point.action.pickUpPoint.position?.[1] ?? 0, + human.point.action.pickUpPoint.position?.[2] ?? 0 + ) + ); + setPath(dropToPickup); + setCurrentPhase('drop-pickup'); + setHumanState(human.modelUuid, 'running'); + setHumanPicking(human.modelUuid, false); + setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + humanStatus(human.modelUuid, 'Started from dropping point, heading to pickup point'); + } } } @@ -119,17 +172,488 @@ function HumanInstance({ human }: { human: HumanStatus }) { reset() } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [human, currentPhase, path, isPlaying]); + }, [human, currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); function handleCallBack() { if (currentPhase === 'init-pickup') { setCurrentPhase('picking'); + setHumanState(human.modelUuid, 'idle'); + setHumanPicking(human.modelUuid, true); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached pickup point, waiting for material'); + setPath([]); } else if (currentPhase === 'pickup-drop') { + setCurrentPhase('dropping'); + setHumanState(human.modelUuid, 'idle'); + setHumanPicking(human.modelUuid, false); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + humanStatus(human.modelUuid, 'Reached drop point'); + setPath([]); } else if (currentPhase === 'drop-pickup') { + setCurrentPhase('picking'); + setHumanState(human.modelUuid, 'idle'); + setHumanPicking(human.modelUuid, true); + setHumanActive(human.modelUuid, false); + setPath([]); + clearCurrentMaterials(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached pickup point again, cycle complete'); } } + function animate(currentTime: number) { + if (previousTimeRef.current === null) { + previousTimeRef.current = currentTime; + } + + const deltaTime = (currentTime - previousTimeRef.current) / 1000; + previousTimeRef.current = currentTime; + + if (human.isActive) { + if (!isPausedRef.current) { + activeTimeRef.current += deltaTime * isSpeedRef.current; + } + } else { + if (!isPausedRef.current) { + idleTimeRef.current += deltaTime * isSpeedRef.current; + } + } + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + useEffect(() => { + if (!isPlaying) return + if (!human.isActive) { + const roundedActiveTime = Math.round(activeTimeRef.current); + incrementActiveTime(human.modelUuid, roundedActiveTime); + activeTimeRef.current = 0; + } else { + const roundedIdleTime = Math.round(idleTimeRef.current); + incrementIdleTime(human.modelUuid, roundedIdleTime); + idleTimeRef.current = 0; + } + + if (animationFrameIdRef.current === null) { + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + return () => { + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current); + animationFrameIdRef.current = null; + } + }; + }, [human, isPlaying]); + + function startUnloadingProcess() { + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { + if (human.point.action.triggers.length > 0) { + const trigger = getTriggerByUuid(selectedProduct.productUuid, human.point.action.triggers[0]?.triggerUuid); + const model = getEventByModelUuid(selectedProduct.productUuid, trigger?.triggeredAsset?.triggeredModel?.modelUuid || ''); + + if (trigger && model) { + if (model.type === 'transfer') { + const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); + if (action) { + handleMaterialDropToConveyor(model); + } + } else if (model.type === 'machine') { + const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); + if (action) { + handleMaterialDropToMachine(model); + } + } else if (model.type === 'roboticArm') { + const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); + if (action) { + handleMaterialDropToArmBot(model); + } + } else if (model.type === 'storageUnit') { + const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); + if (action) { + handleMaterialDropToStorageUnit(model); + } + } else if (model.type === 'vehicle') { + const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); + if (action) { + handleMaterialDropToVehicle(model); + } + } + } else { + const droppedMaterial = human.currentLoad; + startTime = performance.now(); + handleMaterialDropByDefault(droppedMaterial); + } + } else { + const droppedMaterial = human.currentLoad; + startTime = performance.now(); + handleMaterialDropByDefault(droppedMaterial); + } + } else { + requestAnimationFrame(startUnloadingProcess); + } + } + + function handleMaterialDropToStorageUnit(model: StorageEventSchema) { + if (model && humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.isCompleted) { + if (model.point.action.actionType === 'store') { + loopMaterialDropToStorage( + human.modelUuid, + human.currentLoad, + model.modelUuid, + model.point.action.storageCapacity, + human.point.action + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToStorage( + humanId: string, + humanCurrentLoad: number, + storageUnitId: string, + storageMaxCapacity: number, + action: HumanAction + ) { + const storageUnit = getStorageUnitById(storageUnitId); + + if (!storageUnit || humanCurrentLoad <= 0 || storageUnit.currentLoad >= storageMaxCapacity) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0 && storageUnit.currentLoad < storageMaxCapacity) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextDrop = () => { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToStorage( + humanId, + humanCurrentLoad, + storageUnitId, + storageMaxCapacity, + action + ); + } else { + requestAnimationFrame(waitForNextDrop); + } + }; + waitForNextDrop(); + } + } + + function handleMaterialDropToConveyor(model: ConveyorEventSchema) { + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.isCompleted) { + const conveyor = getConveyorById(model.modelUuid); + if (conveyor) { + loopMaterialDropToConveyor( + human.modelUuid, + human.currentLoad, + conveyor.modelUuid, + human.point.action + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToConveyor( + humanId: string, + humanCurrentLoad: number, + conveyorId: string, + action: HumanAction + ) { + const conveyor = getConveyorById(conveyorId); + + if (!conveyor || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextDrop = () => { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToConveyor( + humanId, + humanCurrentLoad, + conveyorId, + action + ); + } else { + requestAnimationFrame(waitForNextDrop); + } + }; + waitForNextDrop(); + } + } + + function handleMaterialDropToArmBot(model: RoboticArmEventSchema) { + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.isCompleted) { + const armBot = getArmBotById(model.modelUuid); + if (armBot && armBot.state === 'idle' && !armBot.isActive) { + loopMaterialDropToArmBot( + human.modelUuid, + human.currentLoad, + model.modelUuid, + human.point.action + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToArmBot( + humanId: string, + humanCurrentLoad: number, + armBotId: string, + action: HumanAction + ) { + const armBot = getArmBotById(armBotId); + + if (!armBot || armBot.state !== 'idle' || armBot.isActive || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextTransfer = () => { + const currentArmBot = getArmBotById(armBotId); + if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToArmBot( + humanId, + humanCurrentLoad, + armBotId, + action + ); + } else { + requestAnimationFrame(waitForNextTransfer); + } + } else { + requestAnimationFrame(waitForNextTransfer); + } + }; + waitForNextTransfer(); + } + } + + function handleMaterialDropToVehicle(model: VehicleEventSchema) { + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.isCompleted) { + const vehicle = getVehicleById(model.modelUuid); + if (vehicle && vehicle.state === 'idle' && !vehicle.isActive) { + loopMaterialDropToVehicle( + human.modelUuid, + human.currentLoad, + model.modelUuid, + human.point.action + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToVehicle( + humanId: string, + humanCurrentLoad: number, + vehicleId: string, + action: HumanAction + ) { + const vehicle = getVehicleById(vehicleId); + + if (!vehicle || vehicle.state !== 'idle' || vehicle.isActive || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextTransfer = () => { + const currentArmBot = getVehicleById(vehicleId); + if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToArmBot( + humanId, + humanCurrentLoad, + vehicleId, + action + ); + } else { + requestAnimationFrame(waitForNextTransfer); + } + } else { + requestAnimationFrame(waitForNextTransfer); + } + }; + waitForNextTransfer(); + } + } + + function handleMaterialDropToMachine(model: MachineEventSchema) { + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.isCompleted) { + const machine = getMachineById(model.modelUuid); + if (machine && machine.state === 'idle' && !machine.isActive) { + loopMaterialDropToMachine( + human.modelUuid, + human.currentLoad, + model.modelUuid, + human.point.action + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToMachine( + humanId: string, + humanCurrentLoad: number, + machineId: string, + action: HumanAction + ) { + const machine = getMachineById(machineId); + + if (!machine || machine.state !== 'idle' || machine.isActive || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextTransfer = () => { + const currentArmBot = getMachineById(machineId); + if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToArmBot( + humanId, + humanCurrentLoad, + machineId, + action + ); + } else { + requestAnimationFrame(waitForNextTransfer); + } + } else { + requestAnimationFrame(waitForNextTransfer); + } + }; + waitForNextTransfer(); + } + } + + function handleMaterialDropByDefault(droppedMaterial: number) { + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + if (humanAsset?.animationState?.isCompleted) { + const remainingMaterials = droppedMaterial - 1; + decrementHumanLoad(human.modelUuid, 1); + const material = removeLastMaterial(human.modelUuid); + + if (material) { + setEndTime(material.materialId, performance.now()); + removeMaterial(material.materialId); + } + + if (remainingMaterials > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + requestAnimationFrame(() => handleMaterialDropByDefault(remainingMaterials)); + } + return; + } + + requestAnimationFrame(() => handleMaterialDropByDefault(droppedMaterial)); } return ( @@ -144,6 +668,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { startUnloadingProcess={startUnloadingProcess} /> + ) } diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index cd1a67c..557fcd2 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -264,7 +264,6 @@ export function useTriggerHandler() { if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { const material = getMaterialById(materialId); if (material) { - const triggeredAction = action; // Handle current action of the material handleAction(action, materialId); @@ -291,7 +290,7 @@ export function useTriggerHandler() { if (human) { - if (human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < (triggeredAction as HumanAction).loadCapacity) { + if (human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < human.point.action.loadCapacity) { setIsVisible(materialId, false); @@ -302,6 +301,9 @@ export function useTriggerHandler() { // Handle current action using Event Manager addHumanToMonitor(human.modelUuid, () => { + + setIsVisible(materialId, false); + handleAction(action, materialId); }) } @@ -433,6 +435,9 @@ export function useTriggerHandler() { } } } + } else if (toEvent?.type === 'human') { + // Vehicle to Human + } } else if (fromEvent?.type === 'machine') { if (toEvent?.type === 'transfer') { @@ -539,6 +544,9 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'storageUnit') { // Machine to Storage Unit + } else if (toEvent?.type === 'human') { + // Machine to Human + } } else if (fromEvent?.type === 'roboticArm') { if (toEvent?.type === 'transfer') { @@ -812,6 +820,53 @@ export function useTriggerHandler() { } } } + } else if (toEvent?.type === 'human') { + // Robotic Arm to Vehicle + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + + setIsPaused(material.materialId, false); + + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setNextLocation(material.materialId, null); + + if (action) { + + if (human) { + + if (human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < human.point.action.loadCapacity) { + + setIsVisible(materialId, false); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + // Handle current action from human + handleAction(action, materialId); + + } else { + + // Event Manager Needed + + } + } + } + + } + } + } } else if (fromEvent?.type === 'storageUnit') { if (toEvent?.type === 'transfer') { @@ -829,6 +884,135 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'storageUnit') { // Storage Unit to Storage Unit + } else if (toEvent?.type === 'human') { + // Storage Unit to Human + + } + } else if (fromEvent?.type === 'human') { + if (toEvent?.type === 'transfer') { + // Human to Transfer + + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setIsVisible(materialId, true); + + if (action && + 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, materialId); + } + + } + } + + } else if (toEvent?.type === 'vehicle') { + // Human to Vehicle + + } else if (toEvent?.type === 'machine') { + // Human to Machine + + } else if (toEvent?.type === 'roboticArm') { + // Human to Robotic Arm + + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const armBot = getArmBotById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + setIsVisible(materialId, false); + + if (action && armBot) { + + if (armBot.isActive === false && armBot.state === 'idle') { + + // Handle current action from arm bot + handleAction(action, materialId); + + } else { + + // Event Manager Needed + + } + } + } + } + + } else if (toEvent?.type === 'storageUnit') { + // Human to Storage Unit + + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const storageUnit = getStorageUnitById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + setIsVisible(materialId, false); + + if (action && storageUnit) { + + if (storageUnit.currentLoad < storageUnit.point.action.storageCapacity) { + + // Handle current action from vehicle + handleAction(action, materialId); + + } else { + + // Event Manager Needed + + } + } + } + } } } } diff --git a/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx index fb4ee8e..6a6a32a 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx @@ -3,12 +3,7 @@ import { useThree, useFrame } from '@react-three/fiber'; import * as THREE from 'three'; import { MaterialModel } from '../../../materials/instances/material/materialModel'; -type MaterialAnimatorProps = { - agvDetail: VehicleStatus; -}; - - -const MaterialAnimator = ({ agvDetail }: MaterialAnimatorProps) => { +const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => { const meshRef = useRef(null!); const [hasLoad, setHasLoad] = useState(false); const { scene } = useThree(); diff --git a/app/src/store/builder/useAssetStore.ts b/app/src/store/builder/useAssetStore.ts index a9111f4..ca97bf0 100644 --- a/app/src/store/builder/useAssetStore.ts +++ b/app/src/store/builder/useAssetStore.ts @@ -22,7 +22,8 @@ interface AssetsStore { // Animation controls setAnimations: (modelUuid: string, animations: string[]) => void; - setCurrentAnimation: (modelUuid: string, current: string, isisPlaying: boolean, loopAnimation: boolean) => void; + setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean, loopAnimation: boolean, isCompleted: boolean) => void; + setAnimationComplete: (modelUuid: string, isCompleted: boolean) => void; resetAnimation: (modelUuid: string) => void; addAnimation: (modelUuid: string, animation: string) => void; removeAnimation: (modelUuid: string, animation: string) => void; @@ -150,19 +151,29 @@ export const createAssetStore = () => { if (asset) { asset.animations = animations; if (!asset.animationState) { - asset.animationState = { current: '', isPlaying: false, loopAnimation: true }; + asset.animationState = { current: '', isPlaying: false, loopAnimation: true, isCompleted: true }; } } }); }, - setCurrentAnimation: (modelUuid, current, isisPlaying, loopAnimation) => { + setCurrentAnimation: (modelUuid, current, isPlaying, loopAnimation, isCompleted) => { set((state) => { const asset = state.assets.find(a => a.modelUuid === modelUuid); if (asset?.animationState) { asset.animationState.current = current; - asset.animationState.isPlaying = isisPlaying; + asset.animationState.isPlaying = isPlaying; asset.animationState.loopAnimation = loopAnimation; + asset.animationState.isCompleted = isCompleted; + } + }); + }, + + setAnimationComplete: (modelUuid, isCompleted) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset?.animationState) { + asset.animationState.isCompleted = isCompleted; } }); }, @@ -171,7 +182,7 @@ export const createAssetStore = () => { set((state) => { const asset = state.assets.find(a => a.modelUuid === modelUuid); if (asset?.animationState) { - asset.animationState = { current: '', isPlaying: false, loopAnimation: true }; + asset.animationState = { current: '', isPlaying: false, loopAnimation: true, isCompleted: true }; } }); }, diff --git a/app/src/store/simulation/useHumanStore.ts b/app/src/store/simulation/useHumanStore.ts index 0c6ebd9..496228f 100644 --- a/app/src/store/simulation/useHumanStore.ts +++ b/app/src/store/simulation/useHumanStore.ts @@ -160,7 +160,7 @@ export const createHumanStore = () => { set((state) => { const human = state.humans.find(h => h.modelUuid === modelUuid); if (human && human.currentMaterials.length > 0) { - removed = human.currentMaterials.pop(); + removed = JSON.parse(JSON.stringify(human.currentMaterials.pop())); } }); return removed; diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index a8b0175..b48cd12 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -28,6 +28,7 @@ interface Asset { current: string; isPlaying: boolean; loopAnimation: boolean; + isCompleted: boolean; }; eventData?: { type: string; From ff02f01430714ccd0a65da2e2b8d95ca9b725030 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 4 Jul 2025 14:02:44 +0530 Subject: [PATCH 12/13] fix: Update asset animation state handling to ensure correct playback status --- .../human/instances/instance/humanInstance.tsx | 10 ++-------- app/src/store/builder/useAssetStore.ts | 6 ++++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index 7d75aa7..214c1e6 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -25,7 +25,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { setHumanActive, setHumanState, setHumanPicking, clearCurrentMaterials, setHumanLoad, decrementHumanLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = humanStore(); + const { setHumanActive, setHumanState, setHumanPicking, clearCurrentMaterials, setHumanLoad, decrementHumanLoad, removeLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = humanStore(); const [currentPhase, setCurrentPhase] = useState('init'); const [path, setPath] = useState<[number, number, number][]>([]); @@ -34,7 +34,6 @@ function HumanInstance({ human }: { human: HumanStatus }) { const activeTimeRef = useRef(0); const isPausedRef = useRef(false); const isSpeedRef = useRef(0); - let startTime: number; const { speed } = useAnimationPlaySpeed(); const { isPaused } = usePauseButtonStore(); const previousTimeRef = useRef(null); @@ -83,7 +82,6 @@ function HumanInstance({ human }: { human: HumanStatus }) { setHumanLoad(human.modelUuid, 0); resetAnimation(human.modelUuid); setPath([]); - startTime = 0; isPausedRef.current = false; pauseTimeRef.current = 0; resetTime(human.modelUuid) @@ -167,8 +165,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { } } - } - else { + } else { reset() } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -247,7 +244,6 @@ function HumanInstance({ human }: { human: HumanStatus }) { }; }, [human, isPlaying]); - function startUnloadingProcess() { const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { @@ -284,12 +280,10 @@ function HumanInstance({ human }: { human: HumanStatus }) { } } else { const droppedMaterial = human.currentLoad; - startTime = performance.now(); handleMaterialDropByDefault(droppedMaterial); } } else { const droppedMaterial = human.currentLoad; - startTime = performance.now(); handleMaterialDropByDefault(droppedMaterial); } } else { diff --git a/app/src/store/builder/useAssetStore.ts b/app/src/store/builder/useAssetStore.ts index ca97bf0..2223d0a 100644 --- a/app/src/store/builder/useAssetStore.ts +++ b/app/src/store/builder/useAssetStore.ts @@ -182,8 +182,10 @@ export const createAssetStore = () => { set((state) => { const asset = state.assets.find(a => a.modelUuid === modelUuid); if (asset?.animationState) { - asset.animationState = { current: '', isPlaying: false, loopAnimation: true, isCompleted: true }; - } + asset.animationState.current = ''; + asset.animationState.isPlaying = true; + asset.animationState.loopAnimation = true; + asset.animationState.isCompleted = true; } }); }, From 508c88dce2018df696e1cb389f5d00053f00cc65 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 4 Jul 2025 17:15:10 +0530 Subject: [PATCH 13/13] feat: Implement human event handling in copy and duplication controls - Added support for handling "Human" type events in the copy and duplication controls. - Created a new HumanEventSchema to manage human-related events, including position, rotation, and action details. - Updated the HumanAnimator to improve rotation handling using quaternions for smoother transitions. - Enhanced the HumanInstance to ensure proper synchronization of human positions and rotations. - Integrated human event handling in the trigger handler for various interactions, including vehicle and machine actions. - Introduced material drop functionality from vehicles to humans, allowing for dynamic interactions in the simulation. --- .../mechanics/humanMechanics.tsx | 2 +- .../builder/asset/models/model/model.tsx | 4 +- .../selectionControls/copyPasteControls.tsx | 28 + .../selectionControls/duplicationControls.tsx | 28 + .../instances/animator/humanAnimator.tsx | 42 +- .../instances/instance/humanInstance.tsx | 38 +- .../human/instances/instance/humanUi.tsx | 1 - .../triggerHandler/useTriggerHandler.ts | 683 ++++++++++++++++-- .../instances/instance/vehicleInstance.tsx | 81 ++- 9 files changed, 821 insertions(+), 86 deletions(-) diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index 1e7c423..9f2fd2d 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -30,7 +30,7 @@ function HumanMechanics() { const { projectId } = useParams(); useEffect(() => { - if (selectedEventData) { + if (selectedEventData && selectedEventData.data.type === "human") { const point = getPointByUuid( selectedProduct.productUuid, selectedEventData.data.modelUuid, diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index f3baadd..ff5e0bf 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -16,6 +16,7 @@ import { getUserData } from '../../../../../functions/getUserData'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useVersionContext } from '../../../version/versionContext'; import { SkeletonUtils } from 'three-stdlib'; +import { useAnimationPlaySpeed } from '../../../../../store/usePlayButtonStore'; function Model({ asset }: { readonly asset: Asset }) { const { camera, controls, gl } = useThree(); @@ -23,6 +24,7 @@ function Model({ asset }: { readonly asset: Asset }) { const { toggleView } = useToggleView(); const { subModule } = useSubModuleStore(); const { activeModule } = useModuleStore(); + const { speed } = useAnimationPlaySpeed(); const { assetStore, eventStore, productStore } = useSceneContext(); const { removeAsset, setAnimations, resetAnimation, setAnimationComplete, setCurrentAnimation: setAnmationAnimation } = assetStore(); const { setTop } = useTopData(); @@ -289,7 +291,7 @@ function Model({ asset }: { readonly asset: Asset }) { useFrame((_, delta) => { if (mixerRef.current) { - mixerRef.current.update(delta); + mixerRef.current.update(delta * speed); } }); diff --git a/app/src/modules/scene/controls/selectionControls/copyPasteControls.tsx b/app/src/modules/scene/controls/selectionControls/copyPasteControls.tsx index 3b9d232..29a3007 100644 --- a/app/src/modules/scene/controls/selectionControls/copyPasteControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/copyPasteControls.tsx @@ -332,6 +332,34 @@ const CopyPasteControls = ({ position: storageEvent.point.position, rotation: storageEvent.point.rotation }; + } else if (obj.userData.eventData.type === "Human") { + const humanEvent: HumanEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], + state: "idle", + type: "human", + speed: 1, + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], + rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCapacity: 1, + triggers: [] + } + } + } + addEvent(humanEvent); + eventData.point = { + uuid: humanEvent.point.uuid, + position: humanEvent.point.position, + rotation: humanEvent.point.rotation + }; } newFloorItem.eventData = eventData; diff --git a/app/src/modules/scene/controls/selectionControls/duplicationControls.tsx b/app/src/modules/scene/controls/selectionControls/duplicationControls.tsx index 5194952..b4a1916 100644 --- a/app/src/modules/scene/controls/selectionControls/duplicationControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/duplicationControls.tsx @@ -306,6 +306,34 @@ const DuplicationControls = ({ position: storageEvent.point.position, rotation: storageEvent.point.rotation }; + } else if (obj.userData.eventData.type === "Human") { + const humanEvent: HumanEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], + state: "idle", + type: "human", + speed: 1, + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], + rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCapacity: 1, + triggers: [] + } + } + } + addEvent(humanEvent); + eventData.point = { + uuid: humanEvent.point.uuid, + position: humanEvent.point.position, + rotation: humanEvent.point.rotation + }; } newFloorItem.eventData = eventData; diff --git a/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx b/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx index 8e818fa..89d7a82 100644 --- a/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx @@ -100,17 +100,18 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start const end = new THREE.Vector3(...currentPath[index + 1]); const segmentDistance = distances[index]; - const currentDirection = new THREE.Vector3().subVectors(end, start).normalize(); - const targetAngle = Math.atan2(currentDirection.x, currentDirection.z); - const currentAngle = object.rotation.y; + const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0))); + const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI); + targetQuaternion.multiply(y180); - let angleDifference = targetAngle - currentAngle; - if (angleDifference > Math.PI) angleDifference -= 2 * Math.PI; - if (angleDifference < -Math.PI) angleDifference += 2 * Math.PI; + const angle = object.quaternion.angleTo(targetQuaternion); + if (angle < 0.01) { + object.quaternion.copy(targetQuaternion); + } else { + object.quaternion.slerp(targetQuaternion, delta * rotationSpeed * speed * human.speed * 5); + } - const maxRotationStep = (rotationSpeed * speed * human.speed) * delta; - object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep); - const isAligned = Math.abs(angleDifference) < 0.01; + const isAligned = angle < 0.01; if (isAligned) { progressRef.current += delta * (speed * human.speed); @@ -133,23 +134,26 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start if (progressRef.current >= totalDistance) { if (restRotation && objectRotation) { - const targetEuler = new THREE.Euler( - objectRotation[0], - objectRotation[1] + Math.PI, - objectRotation[2] - ); - const targetQuaternion = new THREE.Quaternion().setFromEuler(targetEuler); - object.quaternion.slerp(targetQuaternion, delta * (rotationSpeed * speed * human.speed)); - if (object.quaternion.angleTo(targetQuaternion) < 0.01) { + const targetEuler = new THREE.Euler(0, objectRotation[1], 0); + + const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler); + const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI); + const targetQuaternion = baseQuaternion.multiply(y180); + + const angle = object.quaternion.angleTo(targetQuaternion); + if (angle < 0.01) { object.quaternion.copy(targetQuaternion); - object.rotation.copy(targetEuler); setRestingRotation(false); + } else { + object.quaternion.slerp(targetQuaternion, delta * rotationSpeed * speed * human.speed * 4); } + if (human.currentMaterials.length > 0) { setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true); } else { setCurrentAnimation(human.modelUuid, 'idle', true, true, true); } + return; } } @@ -170,7 +174,7 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start <> {currentPath.length > 0 && ( // helper - + {currentPath.map((point, index) => ( diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index 214c1e6..12d2d61 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import * as THREE from 'three'; +import { useThree } from '@react-three/fiber'; import { NavMeshQuery } from '@recast-navigation/core'; import { useNavMesh } from '../../../../../store/builder/store'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; @@ -13,6 +14,7 @@ import MaterialAnimator from '../animator/materialAnimator'; function HumanInstance({ human }: { human: HumanStatus }) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); + const { scene } = useThree(); const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); const { removeMaterial, setEndTime } = materialStore(); const { getStorageUnitById } = storageUnitStore(); @@ -92,6 +94,11 @@ function HumanInstance({ human }: { human: HumanStatus }) { cancelAnimationFrame(animationFrameIdRef.current) animationFrameIdRef.current = null } + const object = scene.getObjectByProperty('uuid', human.modelUuid); + if (object && human) { + object.position.set(human.position[0], human.position[1], human.position[2]); + object.rotation.set(human.rotation[0], human.rotation[1], human.rotation[2]); + } } useEffect(() => { @@ -292,6 +299,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { } function handleMaterialDropToStorageUnit(model: StorageEventSchema) { + const humanAsset = getAssetById(human.modelUuid); if (model && humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } @@ -322,6 +330,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { action: HumanAction ) { const storageUnit = getStorageUnitById(storageUnitId); + const humanAsset = getAssetById(human.modelUuid); if (!storageUnit || humanCurrentLoad <= 0 || storageUnit.currentLoad >= storageMaxCapacity) { return; @@ -340,7 +349,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); const waitForNextDrop = () => { - if (humanAsset?.animationState?.isCompleted) { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { loopMaterialDropToStorage( humanId, humanCurrentLoad, @@ -357,6 +366,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { } function handleMaterialDropToConveyor(model: ConveyorEventSchema) { + const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } @@ -386,6 +396,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { action: HumanAction ) { const conveyor = getConveyorById(conveyorId); + const humanAsset = getAssetById(human.modelUuid); if (!conveyor || humanCurrentLoad <= 0) { return; @@ -420,12 +431,13 @@ function HumanInstance({ human }: { human: HumanStatus }) { } function handleMaterialDropToArmBot(model: RoboticArmEventSchema) { + const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } const checkAnimation = () => { - if (humanAsset?.animationState?.isCompleted) { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { const armBot = getArmBotById(model.modelUuid); if (armBot && armBot.state === 'idle' && !armBot.isActive) { loopMaterialDropToArmBot( @@ -449,6 +461,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { action: HumanAction ) { const armBot = getArmBotById(armBotId); + const humanAsset = getAssetById(human.modelUuid); if (!armBot || armBot.state !== 'idle' || armBot.isActive || humanCurrentLoad <= 0) { return; @@ -488,12 +501,13 @@ function HumanInstance({ human }: { human: HumanStatus }) { } function handleMaterialDropToVehicle(model: VehicleEventSchema) { + const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } const checkAnimation = () => { - if (humanAsset?.animationState?.isCompleted) { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { const vehicle = getVehicleById(model.modelUuid); if (vehicle && vehicle.state === 'idle' && !vehicle.isActive) { loopMaterialDropToVehicle( @@ -517,6 +531,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { action: HumanAction ) { const vehicle = getVehicleById(vehicleId); + const humanAsset = getAssetById(human.modelUuid); if (!vehicle || vehicle.state !== 'idle' || vehicle.isActive || humanCurrentLoad <= 0) { return; @@ -535,10 +550,10 @@ function HumanInstance({ human }: { human: HumanStatus }) { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); const waitForNextTransfer = () => { - const currentArmBot = getVehicleById(vehicleId); - if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) { + const currentVehicle = getVehicleById(vehicleId); + if (currentVehicle && currentVehicle.state === 'idle' && !currentVehicle.isActive) { if (humanAsset?.animationState?.isCompleted) { - loopMaterialDropToArmBot( + loopMaterialDropToVehicle( humanId, humanCurrentLoad, vehicleId, @@ -556,12 +571,13 @@ function HumanInstance({ human }: { human: HumanStatus }) { } function handleMaterialDropToMachine(model: MachineEventSchema) { + const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } const checkAnimation = () => { - if (humanAsset?.animationState?.isCompleted) { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { const machine = getMachineById(model.modelUuid); if (machine && machine.state === 'idle' && !machine.isActive) { loopMaterialDropToMachine( @@ -585,6 +601,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { action: HumanAction ) { const machine = getMachineById(machineId); + const humanAsset = getAssetById(human.modelUuid); if (!machine || machine.state !== 'idle' || machine.isActive || humanCurrentLoad <= 0) { return; @@ -603,10 +620,10 @@ function HumanInstance({ human }: { human: HumanStatus }) { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); const waitForNextTransfer = () => { - const currentArmBot = getMachineById(machineId); - if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) { + const currentMachine = getMachineById(machineId); + if (currentMachine && currentMachine.state === 'idle' && !currentMachine.isActive) { if (humanAsset?.animationState?.isCompleted) { - loopMaterialDropToArmBot( + loopMaterialDropToMachine( humanId, humanCurrentLoad, machineId, @@ -624,6 +641,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { } function handleMaterialDropByDefault(droppedMaterial: number) { + const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } diff --git a/app/src/modules/simulation/human/instances/instance/humanUi.tsx b/app/src/modules/simulation/human/instances/instance/humanUi.tsx index f3688c5..64b1f84 100644 --- a/app/src/modules/simulation/human/instances/instance/humanUi.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanUi.tsx @@ -219,7 +219,6 @@ function HumanUi() { {selectedHumanData && ( { + const armBot = getArmBotById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (armBot) { + if (armBot.isActive === false && armBot.state === 'idle') { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsPaused(materialId, true); handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + handleAction(action, materialId); + } + ) } - ) + } + } else { + setIsPaused(materialId, true); + addArmBotToMonitor(armBot.modelUuid, () => { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + handleAction(action, materialId); + } + ) + } + } + }) } } } else if (model?.type === 'machine') { @@ -194,26 +222,25 @@ export function useTriggerHandler() { } } else { setIsPaused(materialId, true); - addArmBotToMonitor(armBot.modelUuid, - () => { - const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (machine) { - if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { - setIsPaused(materialId, true); - handleAction(action, materialId); - } else { + addArmBotToMonitor(armBot.modelUuid, () => { + const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (machine) { + if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { + setIsPaused(materialId, true); + handleAction(action, materialId); + } else { - // Handle current action using Event Manager - setIsPaused(materialId, true); + // Handle current action using Event Manager + setIsPaused(materialId, true); - addMachineToMonitor(machine.modelUuid, - () => { - handleAction(action, materialId); - } - ) - } + addMachineToMonitor(machine.modelUuid, + () => { + handleAction(action, materialId); + } + ) } } + } ); } } @@ -289,23 +316,206 @@ export function useTriggerHandler() { if (action) { if (human) { + if (action && action.triggers.length > 0 && + action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid && + action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid && + action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) { + const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); - if (human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < human.point.action.loadCapacity) { + if (model?.type === 'vehicle') { + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (human) { + if (human.isActive === false && human.state === 'idle') { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsVisible(materialId, false); + setIsPaused(materialId, true); + handleAction(action, materialId); - setIsVisible(materialId, false); + } else { - // Handle current action from vehicle - handleAction(action, materialId); + // Handle current action using Event Manager + setIsPaused(materialId, true); + addVehicleToMonitor(vehicle.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + } else { + setIsPaused(materialId, true); + addHumanToMonitor(human.modelUuid, () => { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsVisible(materialId, false); + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + }) + } + } + } else if (model?.type === 'transfer') { + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (human) { + if (human.isActive === false && human.state === 'idle') { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + if (conveyor) { + if (!conveyor.isPaused) { + // Handle current action from vehicle + setIsVisible(materialId, false); + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addConveyorToMonitor(conveyor.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + } else { + setIsPaused(materialId, true); + addHumanToMonitor(human.modelUuid, () => { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (conveyor) { + if (!conveyor.isPaused) { + // Handle current action from vehicle + setIsVisible(materialId, false); + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addConveyorToMonitor(conveyor.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + }) + } + } + } else if (model?.type === 'machine') { + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (human) { + if (human.isActive === false && human.state === 'idle') { + const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + if (machine) { + if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { + setIsPaused(materialId, true); + setIsVisible(materialId, false); + handleAction(action, materialId); + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addMachineToMonitor(machine.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + } else { + setIsPaused(materialId, true); + addHumanToMonitor(human.modelUuid, () => { + const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (machine) { + if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { + setIsPaused(materialId, true); + setIsVisible(materialId, false); + handleAction(action, materialId); + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addMachineToMonitor(machine.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + } + ); + } + } + } else { + if (human.isActive === false && human.state === 'idle') { + + // Handle current action from arm bot + setIsPaused(materialId, true); + setIsVisible(materialId, false); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + addHumanToMonitor(human.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId) + } + ); + + } + } } else { + if (human.isActive === false && human.state === 'idle') { - // Handle current action using Event Manager - addHumanToMonitor(human.modelUuid, () => { - + // Handle current action from arm bot + setIsPaused(materialId, true); setIsVisible(materialId, false); - handleAction(action, materialId); - }) + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + addHumanToMonitor(human.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId) + } + ); + + } } } } @@ -378,18 +588,25 @@ export function useTriggerHandler() { setNextLocation(material.materialId, null); - setIsVisible(materialId, false); if (action && armBot) { if (armBot.isActive === false && armBot.state === 'idle') { // Handle current action from arm bot + setIsVisible(materialId, false); + handleAction(action, materialId); } else { - // Event Manager Needed + addArmBotToMonitor(armBot.modelUuid, + () => { + setIsVisible(materialId, false); + + handleAction(action, materialId); + } + ) } } @@ -438,6 +655,49 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'human') { // Vehicle to Human + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + + if (action && human) { + + if (human.isActive === false && human.state === 'idle') { + + // Handle current action from arm bot + setIsVisible(materialId, false); + + handleAction(action, materialId); + + } else { + + addHumanToMonitor(human.modelUuid, + () => { + setIsVisible(materialId, false); + + handleAction(action, materialId); + } + ) + } + } + } + } } } else if (fromEvent?.type === 'machine') { if (toEvent?.type === 'transfer') { @@ -546,7 +806,133 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'human') { // Machine to Human + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + setIsPaused(materialId, true); + + if (material) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + setIsVisible(materialId, false); + + if (action && human) { + + if (human.isActive === false && human.state === 'idle') { + + // Handle current action from arm bot + const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (model?.type === 'transfer') { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (conveyor) { + const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || ''); + if (previousModel) { + if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) { + handleAction(action, materialId) + } else { + addConveyorToMonitor(conveyor.modelUuid, + () => { + handleAction(action, materialId) + } + ) + } + } else { + handleAction(action, materialId) + } + // handleAction(action, materialId) + } + } else if (model?.type === 'vehicle') { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + handleAction(action, materialId); + } + ) + } + } + } else { + handleAction(action, materialId) + } + + } else { + + // Handle current action using Event Manager + + addHumanToMonitor(human.modelUuid, () => { + const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (model?.type === 'transfer') { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (conveyor) { + const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || ''); + if (previousModel) { + if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) { + handleAction(action, materialId) + } else { + addConveyorToMonitor(conveyor.modelUuid, + () => { + handleAction(action, materialId) + } + ) + } + } else { + handleAction(action, materialId) + } + } + } else if (model?.type === 'vehicle') { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + handleAction(action, materialId); + } + ) + } + } + } else { + handleAction(action, materialId) + } + } + ); + } + } + } + } } } else if (fromEvent?.type === 'roboticArm') { if (toEvent?.type === 'transfer') { @@ -821,7 +1207,7 @@ export function useTriggerHandler() { } } } else if (toEvent?.type === 'human') { - // Robotic Arm to Vehicle + // Robotic Arm to Human if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { const material = getMaterialById(materialId); if (material) { @@ -895,7 +1281,6 @@ export function useTriggerHandler() { if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { const material = getMaterialById(materialId); if (material) { - const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); setPreviousLocation(material.materialId, { modelUuid: material.current.modelUuid, @@ -909,18 +1294,120 @@ export function useTriggerHandler() { actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, }); - setIsVisible(materialId, true); + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); - if (action && + 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, - }); + action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid && + action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) { + const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); - handleAction(action, materialId); + if (model?.type === 'roboticArm') { + + handleAction(action, material.materialId); + + } else if (model?.type === 'vehicle') { + const nextAction = getActionByUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredAction.actionUuid); + + if (action) { + handleAction(action, material.materialId); + + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + if (nextAction) { + + if (vehicle) { + + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '', + actionUuid: action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid || '', + }); + + setNextLocation(material.materialId, null); + + setIsVisible(materialId, false); + setIsPaused(materialId, false); + + // Handle current action from vehicle + handleAction(nextAction, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '', + actionUuid: action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid || '', + }); + + setNextLocation(material.materialId, null); + + setIsPaused(materialId, false); + setIsVisible(materialId, false); + handleAction(nextAction, materialId); + } + ) + } + } + } + } + + } else if (model?.type === 'transfer') { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + if (conveyor) { + setNextLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid, + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid, + }) + setIsPaused(material.materialId, false); + setIsVisible(material.materialId, true); + handleAction(action, material.materialId); + } + } else { + setNextLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid, + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid, + }) + + handleAction(action, material.materialId); + } + } else if (action) { + setNextLocation(material.materialId, null) + + handleAction(action, material.materialId); } } @@ -929,9 +1416,96 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'vehicle') { // Human to Vehicle + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + + setIsPaused(material.materialId, false); + + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const vehicle = getVehicleById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setNextLocation(material.materialId, null); + + if (action) { + + if (vehicle) { + + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + + setIsVisible(materialId, false); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + // Handle current action from vehicle + handleAction(action, materialId); + + } else { + + // Event Manager Needed + + } + } + } + + } + } + } else if (toEvent?.type === 'machine') { // Human to Machine + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + + // setIsPaused(material.materialId, false); + + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const machine = getMachineById(trigger.triggeredAsset?.triggeredModel.modelUuid); + setNextLocation(material.materialId, null); + + if (action) { + + if (machine) { + + if (machine.isActive === false && machine.state === 'idle') { + + setIsVisible(materialId, false); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + // Handle current action from machine + handleAction(action, materialId); + + } else { + + // Event Manager Needed + + } + } + } + } + } } else if (toEvent?.type === 'roboticArm') { // Human to Robotic Arm @@ -1013,6 +1587,9 @@ export function useTriggerHandler() { } } } + } else if (toEvent?.type === 'human') { + // Human to Human + } } } diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 09b498c..5c49901 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -13,9 +13,10 @@ import VehicleAnimator from '../animator/vehicleAnimator'; function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); - const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, productStore } = useSceneContext(); + const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, productStore } = useSceneContext(); const { removeMaterial, setEndTime } = materialStore(); const { getStorageUnitById } = storageUnitStore(); + const { getHumanById } = humanStore(); const { getArmBotById } = armBotStore(); const { getConveyorById } = conveyorStore(); const { triggerPointActions } = useTriggerHandler(); @@ -246,6 +247,11 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) if (action) { handleMaterialDropToStorageUnit(model); } + } else if (model.type === 'human') { + const action = getActionByUuid(selectedProduct.productUuid, agvDetail.point.action.actionUuid); + if (action) { + handleMaterialDropToHuman(model); + } } } else { const droppedMaterial = agvDetail.currentLoad; @@ -259,6 +265,79 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) } } + function handleMaterialDropToHuman(model: HumanEventSchema) { + if (model) { + if (model.point.action.actionType === 'worker') { + loopMaterialDropToHuman( + agvDetail.modelUuid, + agvDetail.currentLoad, + agvDetail.point.action.unLoadDuration, + model.modelUuid, + model.point.action.loadCapacity, + agvDetail.point.action + ); + } + } + } + + function loopMaterialDropToHuman( + vehicleId: string, + vehicleCurrentLoad: number, + unLoadDuration: number, + humanId: string, + storageMaxCapacity: number, + action: VehicleAction + ) { + startTime = performance.now(); + const fixedInterval = ((unLoadDuration / vehicleCurrentLoad) * (1000 / isSpeedRef.current)); + + const unloadLoop = () => { + if (isPausedRef.current) { + pauseTimeRef.current ??= performance.now(); + requestAnimationFrame(unloadLoop); + return; + } + + if (pauseTimeRef.current) { + const pauseDuration = performance.now() - pauseTimeRef.current; + startTime += pauseDuration; + pauseTimeRef.current = null; + } + + const elapsedTime = performance.now() - startTime; + const human = getHumanById(humanId); + + if (elapsedTime >= fixedInterval) { + if (human && agvDetail && + human.currentLoad < storageMaxCapacity && + vehicleCurrentLoad > 0) { + + decrementVehicleLoad(vehicleId, 1); + vehicleCurrentLoad -= 1; + + const material = removeLastMaterial(vehicleId); + if (material) { + + triggerPointActions(action, material.materialId); + + } + + if (vehicleCurrentLoad > 0 && human.currentLoad < storageMaxCapacity) { + startTime = performance.now(); + requestAnimationFrame(unloadLoop); + } + } + } else { + requestAnimationFrame(unloadLoop); + } + }; + + const human = getHumanById(humanId); + if (human && vehicleCurrentLoad > 0 && human?.currentLoad < storageMaxCapacity) { + unloadLoop(); + } + } + function handleMaterialDropToStorageUnit(model: StorageEventSchema) { if (model) { if (model.point.action.actionType === 'store') {