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 9d8e393..4b29f8c 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -17,7 +17,7 @@ import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; import { useParams } from "react-router-dom"; function HumanMechanics() { - const [activeOption, setActiveOption] = useState<"worker" | "assembly">("worker"); + const [activeOption, setActiveOption] = useState<"worker" | "assembly" | "operator">("worker"); const [speed, setSpeed] = useState("0.5"); const [loadCount, setLoadCount] = useState(0); const [assemblyCount, setAssemblyCount] = useState(0); @@ -78,7 +78,7 @@ function HumanMechanics() { const newCurrentAction = getActionByUuid(selectedProduct.productUuid, actionUuid); - if (newCurrentAction && (newCurrentAction.actionType === 'assembly' || newCurrentAction?.actionType === 'worker')) { + if (newCurrentAction && (newCurrentAction.actionType === 'assembly' || newCurrentAction?.actionType === 'worker' || newCurrentAction?.actionType === "operator")) { if (!selectedAction.actionId) { setSelectedAction(newCurrentAction.actionUuid, newCurrentAction.actionName); } @@ -117,7 +117,7 @@ function HumanMechanics() { const handleSelectActionType = (actionType: string) => { if (!selectedAction.actionId || !currentAction || !selectedPointData) return; - const updatedAction = { ...currentAction, actionType: actionType as "worker" | "assembly" }; + const updatedAction = { ...currentAction, actionType: actionType as "worker" | "assembly" | "operator" }; const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action); const updatedPoint = { ...selectedPointData, actions: updatedActions }; @@ -397,12 +397,12 @@ function HumanMechanics() { - {currentAction.actionType === 'worker' && + {(currentAction.actionType === 'worker' || currentAction.actionType === "operator") && >(new Map()); const retrievalCountRef = useRef>(new Map()); const monitoredHumansRef = useRef>(new Set()); + const cranePickupLockRef = useRef>(new Map()); const [initialDelayComplete, setInitialDelayComplete] = useState(false); const delayTimerRef = useRef(null); @@ -450,7 +452,48 @@ export function useRetrieveHandler() { } } } + } else if (triggeredModel?.type === 'crane') { + const crane = getCraneById(triggeredModel.modelUuid); + const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); + const currentCount = retrievalCountRef.current.get(actionUuid) ?? 0; + + if (!crane) return; + + const hasLock = cranePickupLockRef.current.get(crane.modelUuid) || false; + + if (action && action.actionType === 'pickAndDrop' && !hasLock && !crane.isCarrying && !crane.isActive && crane.currentLoad < (action?.maxPickUpCount || 0)) { + const material = getLastMaterial(storageUnit.modelUuid); + if (material) { + incrementCraneLoad(crane.modelUuid, 1); + addCurrentActionToCrane(crane.modelUuid, action.actionUuid, material.materialType, material.materialId); + addCurrentMaterialToCrane(crane.modelUuid, material.materialType, material.materialId); + + cranePickupLockRef.current.set(crane.modelUuid, true); + } + } else if (crane.isCarrying && crane.currentPhase === 'pickup-drop' && hasLock) { + cranePickupLockRef.current.set(crane.modelUuid, false); + + const lastMaterial = getLastMaterial(storageUnit.modelUuid); + if (lastMaterial) { + const material = createNewMaterial( + lastMaterial.materialId, + lastMaterial.materialType, + storageUnit.point.action + ); + if (material) { + removeLastMaterial(storageUnit.modelUuid); + updateCurrentLoad(storageUnit.modelUuid, -1); + retrievalCountRef.current.set(actionUuid, currentCount + 1); + } + } + } else if (!action) { + const action = getActionByUuid(selectedProduct.productUuid, retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid || ''); + if (action) { + addCurrentActionToCrane(crane.modelUuid, action.actionUuid, null, null); + } + } } + }); if (hasChanges || completedActions.length > 0) { diff --git a/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts b/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts index f8d40d9..dd8af03 100644 --- a/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts +++ b/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts @@ -5,10 +5,13 @@ import { useSceneContext } from '../../../scene/sceneContext'; import { useProductContext } from '../../products/productContext'; export function useCraneEventManager() { - const { craneStore, productStore, assetStore, craneEventManagerRef } = useSceneContext(); + const { craneStore, productStore, assetStore, vehicleStore, armBotStore, machineStore, craneEventManagerRef } = useSceneContext(); const { getCraneById, setCurrentPhase, removeCurrentAction } = craneStore(); const { getAssetById } = assetStore(); - const { getActionByUuid } = productStore(); + const { getVehicleById } = vehicleStore(); + const { getArmBotById } = armBotStore(); + const { getMachineById } = machineStore(); + const { getActionByUuid, getEventByModelUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); @@ -78,17 +81,85 @@ export function useCraneEventManager() { if (crane.isActive || crane.state !== "idle") continue; if (currentCraneAction.actionType === 'pickAndDrop' && crane.currentLoad < currentCraneAction.maxPickUpCount) { - if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { - setCurrentPhase(crane.modelUuid, 'init'); - removeCurrentAction(crane.modelUuid); + const humanAction = getActionByUuid(selectedProduct.productUuid, currentCraneAction.triggers[0].triggeredAsset?.triggeredAction?.actionUuid || ''); + if (humanAction) { + const nextEvent = getEventByModelUuid(selectedProduct.productUuid, humanAction.triggers[0].triggeredAsset?.triggeredModel?.modelUuid || '') + if (nextEvent) { + if (nextEvent.type === 'transfer') { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } else if (nextEvent.type === 'vehicle') { + const vehicle = getVehicleById(nextEvent.modelUuid); + + if (vehicle && !vehicle.isActive && vehicle.currentPhase === 'picking') { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } + } else if (nextEvent.type === 'roboticArm') { + const armBot = getArmBotById(nextEvent.modelUuid); + + if (armBot && !armBot.isActive) { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } + } else if (nextEvent.type === 'machine') { + const machine = getMachineById(nextEvent.modelUuid); + + if (machine && !machine.isActive) { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } + } else { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } + } } - - craneState.isProcessing = false; - currentAction.callback(); - - setTimeout(() => { - completeCurrentAction(craneState); - }, 1000); } } }, 0); diff --git a/app/src/modules/simulation/crane/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/materialAnimator.tsx new file mode 100644 index 0000000..796a897 --- /dev/null +++ b/app/src/modules/simulation/crane/instances/animator/materialAnimator.tsx @@ -0,0 +1,57 @@ +import { useFrame, useThree } from '@react-three/fiber'; +import { useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; +import { MaterialModel } from '../../../materials/instances/material/materialModel'; + +type MaterialAnimatorProps = { + crane: CraneStatus; +}; + +export default function MaterialAnimator({ crane }: Readonly) { + const materialRef = useRef(null); + const { scene } = useThree(); + const [isRendered, setIsRendered] = useState(false); + + useEffect(() => { + if (crane.isCarrying) { + setIsRendered(true); + } else { + setIsRendered(false); + } + }, [crane.isCarrying]); + + useFrame(() => { + const craneModel = scene.getObjectByProperty('uuid', crane.modelUuid); + if (!materialRef.current || !craneModel) return; + + const base = craneModel.getObjectByName('hook'); + if (!base) return; + + if (crane.isCarrying) { + const boneWorldPos = new THREE.Vector3(); + base.getWorldPosition(boneWorldPos); + + const yOffset = -0.65; + boneWorldPos.y += yOffset; + + materialRef.current.position.copy(boneWorldPos); + + materialRef.current.up.set(0, 1, 0); + materialRef.current.lookAt( + materialRef.current.position.clone().add(new THREE.Vector3(0, 0, 1)) + ); + } + }); + + return ( + <> + {isRendered && ( + + )} + + ); +} diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 1b1332d..96455b2 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -3,6 +3,8 @@ import * as THREE from 'three'; import { useFrame, useThree } from '@react-three/fiber'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../products/productContext'; +import { dragAction } from '@use-gesture/react'; function PillarJibAnimator({ crane, @@ -20,8 +22,12 @@ function PillarJibAnimator({ onAnimationComplete: (action: string) => void; }) { const { scene } = useThree(); - const { assetStore } = useSceneContext(); + const { assetStore, productStore, materialStore } = useSceneContext(); + const { getActionByUuid, getPointByUuid } = productStore(); const { resetAsset } = assetStore(); + const { setIsVisible } = materialStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); const { isPaused } = usePauseButtonStore(); const { isPlaying } = usePlayButtonStore(); const { isReset } = useResetButtonStore(); @@ -49,7 +55,9 @@ function PillarJibAnimator({ if (crane.currentPhase === 'init-pickup') { if (crane.currentMaterials.length > 0) { - const material = scene.getObjectByProperty('uuid', crane.currentMaterials[0].materialId); + const materials = scene.getObjectsByProperty('uuid', crane.currentMaterials[0].materialId); + console.log('materials: ', materials); + const material = materials.find((material) => material.visible === true); if (material) { const materialWorld = new THREE.Vector3(); material.getWorldPosition(materialWorld); @@ -62,12 +70,34 @@ function PillarJibAnimator({ ); } } + } else if (crane.currentPhase === 'pickup-drop') { + if (crane.currentMaterials.length > 0) { + const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); + const humanAction = getActionByUuid(selectedProduct.productUuid, action?.triggers[0].triggeredAsset?.triggeredAction?.actionUuid || ''); + + if (humanAction) { + const point = getPointByUuid(selectedProduct.productUuid, humanAction.triggers[0].triggeredAsset?.triggeredModel?.modelUuid || '', humanAction.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid || ''); + const eventModel = scene.getObjectByProperty('uuid', humanAction.triggers[0].triggeredAsset?.triggeredModel?.modelUuid); + if (point && eventModel) { + const pointLocal = new THREE.Vector3(...point.position); + const pointWorld = pointLocal.clone().applyMatrix4(eventModel.matrixWorld); + + const startPoint = new THREE.Vector3(hookWorld.x, hookWorld.y, hookWorld.z); + const endPoint = new THREE.Vector3(pointWorld.x, pointWorld.y + 0.5, pointWorld.z); + + setIsVisible(crane.currentMaterials[0].materialId, false); + + setAnimationPhase('init-hook-adjust'); + setPoints([startPoint, endPoint]); + } + } + } } }, [crane.currentPhase]) useEffect(() => { const model = scene.getObjectByProperty('uuid', crane.modelUuid); - if (!model) return; + if (!model || !model.userData.fieldData) return; const base = model.getObjectByName('base'); const trolley = model.getObjectByName('trolley'); @@ -84,10 +114,10 @@ function PillarJibAnimator({ const hookWorld = new THREE.Vector3(); hook.getWorldPosition(hookWorld); - const trolleyMinOffset = -1; - const trolleyMaxOffset = 1.75; - const hookMinOffset = 0.25; - const hookMaxOffset = -1.5; + const trolleyMinOffset = model.userData.trolleyMinOffset || -1; + const trolleyMaxOffset = model.userData.trolleyMaxOffset || 1.75; + const hookMinOffset = model.userData.hookMinOffset || 0.25; + const hookMaxOffset = model.userData.hookMaxOffset || -1.5; const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05); @@ -159,18 +189,27 @@ function PillarJibAnimator({ } const { animationData } = model.userData; - const hookSpeed = 0.01 * speed; - const rotationSpeed = 0.005 * speed; - const trolleySpeed = 0.01 * speed; + const hookSpeed = (model.userData.hookSpeed || 0.01) * speed; + const rotationSpeed = (model.userData.rotationSpeed || 0.005) * speed; + const trolleySpeed = (model.userData.trolleySpeed || 0.01) * speed; + + const threshold = Math.max(0.01, 0.05 / speed); switch (animationPhase) { case 'init-hook-adjust': { const hookWorld = new THREE.Vector3(); hook.getWorldPosition(hookWorld); - const direction = Math.sign((clampedPoints[0].y - baseWorld.y) - hookWorld.y); - hook.position.y += direction * hookSpeed; + const targetY = clampedPoints[0].y; + const direction = Math.sign(targetY - hookWorld.y); - if (parseFloat(Math.abs(hookWorld.y - clampedPoints[0].y).toFixed(2)) < 0.05) { + hook.position.y = THREE.MathUtils.lerp( + hook.position.y, + hook.position.y + direction * 0.1, + Math.min(hookSpeed, 0.9) + ); + + if (Math.abs(hookWorld.y - targetY) < threshold) { + hook.position.y = targetY - baseWorld.y; setAnimationPhase('init-rotate-base'); } break; @@ -182,7 +221,6 @@ function PillarJibAnimator({ baseForward.sub(baseWorld); const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize(); - const targetWorld = clampedPoints[0]; const targetDir = new THREE.Vector2( targetWorld.x - baseWorld.x, @@ -192,13 +230,16 @@ function PillarJibAnimator({ const currentAngle = Math.atan2(currentDir.y, currentDir.x); const targetAngle = Math.atan2(targetDir.y, targetDir.x); let angleDiff = currentAngle - targetAngle; - angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff)); - if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) { - base.rotation.y += Math.sign(angleDiff) * rotationSpeed; + if (Math.abs(angleDiff) > threshold) { + base.rotation.y = THREE.MathUtils.lerp( + base.rotation.y, + base.rotation.y - angleDiff, + Math.min(rotationSpeed, 0.9) + ); } else { - base.rotation.y += angleDiff; + base.rotation.y -= angleDiff; const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone()); animationData.targetTrolleyX = localTarget?.x; setAnimationPhase('init-move-trolley'); @@ -208,25 +249,32 @@ function PillarJibAnimator({ case 'init-move-trolley': { const dx = animationData.targetTrolleyX - trolley.position.x; - const direction = Math.sign(dx); - trolley.position.x += direction * trolleySpeed; - if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) { + trolley.position.x = THREE.MathUtils.lerp( + trolley.position.x, + animationData.targetTrolleyX, + Math.min(trolleySpeed, 0.9) + ); + + if (Math.abs(dx) < threshold) { trolley.position.x = animationData.targetTrolleyX; - - animationData.finalHookTargetY = hook.position.y; + animationData.finalHookTargetY = clampedPoints[0].y - baseWorld.y; setAnimationPhase('init-final-hook-adjust'); } break; } case 'init-final-hook-adjust': { - const dy = animationData.finalHookTargetY - hook.position.y; - const direction = Math.sign(dy); - hook.position.y += direction * hookSpeed; + const targetY = clampedPoints[0].y - baseWorld.y; - if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { - hook.position.y = animationData.finalHookTargetY; + hook.position.y = THREE.MathUtils.lerp( + hook.position.y, + targetY, + Math.min(hookSpeed, 0.9) + ); + + if (Math.abs(hook.position.y - targetY) < threshold) { + hook.position.y = targetY; model.userData.animationData = { originalHookY: hook.position.y, diff --git a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx index 8998865..40ac35a 100644 --- a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx +++ b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx @@ -1,22 +1,51 @@ -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import * as THREE from "three"; import { useThree } from "@react-three/fiber"; import { Box, Sphere } from "@react-three/drei"; function PillarJibHelper({ crane, - points + points, + isHelperNeeded }: { crane: CraneStatus, points: [THREE.Vector3, THREE.Vector3] | null; + isHelperNeeded: boolean; }) { const { scene } = useThree(); const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]); + const baseWorldRef = useRef(null); + const trolleyWorldRef = useRef(null); + const hookWorldRef = useRef(null); + + useEffect(() => { + const model = scene.getObjectByProperty("uuid", crane.modelUuid); + if (!model) return; + + const base = model.getObjectByName("base"); + const trolley = model.getObjectByName("trolley"); + const hook = model.getObjectByName("hook"); + + if (!base || !trolley || !hook) return; + + const bw = new THREE.Vector3(); + const tw = new THREE.Vector3(); + const hw = new THREE.Vector3(); + + base.getWorldPosition(bw); + trolley.getWorldPosition(tw); + hook.getWorldPosition(hw); + + baseWorldRef.current = bw; + trolleyWorldRef.current = tw; + hookWorldRef.current = hw; + }, [scene, crane.modelUuid]); + const { geometry, position } = useMemo(() => { const model = scene.getObjectByProperty('uuid', crane.modelUuid); - if (!model) return { geometry: null, position: null }; + if (!model || !baseWorldRef.current || !trolleyWorldRef.current || !hookWorldRef.current) return { geometry: null, position: null }; const base = model.getObjectByName('base'); const trolley = model.getObjectByName('trolley'); @@ -24,19 +53,16 @@ function PillarJibHelper({ if (!base || !trolley || !hook || !points) return { geometry: null, position: null }; - const baseWorld = new THREE.Vector3(); - base.getWorldPosition(baseWorld); + const baseWorld = baseWorldRef.current; - const trolleyWorld = new THREE.Vector3(); - trolley.getWorldPosition(trolleyWorld); + const trolleyWorld = trolleyWorldRef.current; - const hookWorld = new THREE.Vector3(); - hook.getWorldPosition(hookWorld); + const hookWorld = hookWorldRef.current; - const trolleyMinOffset = -1; - const trolleyMaxOffset = 1.75; - const hookMinOffset = 0.25; - const hookMaxOffset = -1.5; + const trolleyMinOffset = model.userData.trolleyMinOffset || -1; + const trolleyMaxOffset = model.userData.trolleyMaxOffset || 1.75; + const hookMinOffset = model.userData.hookMinOffset || 0.25; + const hookMaxOffset = model.userData.hookMaxOffset || -1.5; const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); const outerRadius = distFromBase + trolleyMaxOffset; @@ -100,40 +126,44 @@ function PillarJibHelper({ return ( <> - - - + {isHelperNeeded && + <> + + + - {points && points.map((point, i) => ( - - - - ))} + {points && points.map((point, i) => ( + + + + ))} - {clampedPoints && clampedPoints.map((point, i) => ( - - - - ))} + {clampedPoints && clampedPoints.map((point, i) => ( + + + + ))} + + } ); } diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index c95fb95..11c06a4 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -3,36 +3,86 @@ import * as THREE from 'three' import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; +import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; import PillarJibAnimator from '../animator/pillarJibAnimator' import PillarJibHelper from '../helper/pillarJibHelper' +import MaterialAnimator from '../animator/materialAnimator'; function PillarJibInstance({ crane }: { crane: CraneStatus }) { const { isPlaying } = usePlayButtonStore(); - const { craneStore, productStore } = useSceneContext(); - const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); - const { getCraneById, setCurrentPhase } = craneStore(); + const { craneStore, productStore, humanStore, assetStore } = useSceneContext(); + const { triggerPointActions } = useTriggerHandler(); + const { getActionByUuid } = productStore(); + const { setCurrentPhase, setCraneActive, setIsCaryying, removeCurrentAction, removeLastMaterial, decrementCraneLoad } = craneStore(); + const { setCurrentPhase: setCurrentPhaseHuman, setHumanActive, setHumanState, getHumanById } = humanStore(); + const { setCurrentAnimation, getAssetById } = assetStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const [animationPhase, setAnimationPhase] = useState('idle'); const [points, setPoints] = useState<[THREE.Vector3, THREE.Vector3] | null>(null); + const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); + const actionTriggers = action?.triggers || []; + const humanId = actionTriggers?.[0]?.triggeredAsset?.triggeredModel?.modelUuid ?? null; + const humanAsset = getAssetById(humanId || ''); + const humanAction = getActionByUuid(selectedProduct.productUuid, actionTriggers?.[0]?.triggeredAsset?.triggeredAction?.actionUuid ?? ''); useEffect(() => { if (isPlaying) { - const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); - if (!action || action.actionType !== 'pickAndDrop') return; + const human = getHumanById(humanId || ''); + if (!human || !humanAsset || !humanId || !action || action.actionType !== 'pickAndDrop') return; if (!crane.isActive && crane.currentPhase === 'init' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length) { setCurrentPhase(crane.modelUuid, 'init-pickup'); + } else if (crane.currentPhase === 'picking' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length && !crane.isCarrying) { + if (action.triggers.length > 0) { + if (humanAsset?.animationState?.current === "working_standing" && humanAsset?.animationState?.isCompleted && humanId && humanAction && humanAction.actionType === 'operator') { + setCurrentAnimation(humanId, 'idle', true, true, true); + setIsCaryying(crane.modelUuid, true); + setCurrentPhase(crane.modelUuid, 'pickup-drop'); + } else { + setCurrentPhaseHuman(humanId, 'hooking'); + setHumanActive(humanId, true); + setHumanState(humanId, 'running'); + setCurrentAnimation(humanId, 'working_standing', true, false, false); + } + } + } else if (crane.currentPhase === 'dropping' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length && crane.isCarrying && human.currentPhase === 'hooking') { + setCurrentPhaseHuman(humanId, 'loadPoint-unloadPoint'); + } else if (human.state === 'running' && human.currentPhase === 'unhooking') { + if (humanAsset?.animationState?.current === "working_standing" && humanAsset?.animationState?.isCompleted) { + setCurrentPhase(crane.modelUuid, 'init'); + setCraneActive(crane.modelUuid, false); + setCurrentAnimation(humanId, 'idle', true, true, true); + setCurrentPhaseHuman(humanId, 'init'); + setHumanActive(humanId, false); + setHumanState(humanId, 'idle'); + handleMaterialDrop(); + } } } - }, [crane]) + }, [crane, humanAsset?.animationState?.isCompleted]) + + const handleMaterialDrop = () => { + if (humanAction && humanAction.actionType === 'operator') { + setIsCaryying(crane.modelUuid, false); + removeCurrentAction(crane.modelUuid); + const removedMaterial = removeLastMaterial(crane.modelUuid); + decrementCraneLoad(crane.modelUuid, 1); + + if (removedMaterial && humanAction.triggers[0].triggeredAsset?.triggeredAction?.actionUuid) { + triggerPointActions(humanAction, removedMaterial.materialId); + } + } + } const handleAnimationComplete = (action: string) => { if (action === 'starting') { setAnimationPhase('first-hook-adjust'); } else if (action === 'picking') { setCurrentPhase(crane.modelUuid, 'picking'); + } else if (action === 'dropping') { + setCurrentPhase(crane.modelUuid, 'dropping'); } } @@ -49,9 +99,12 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) { onAnimationComplete={handleAnimationComplete} /> + + diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts index f023852..1dec0d9 100644 --- a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -25,7 +25,7 @@ export function useHumanEventManager() { const addHumanToMonitor = (humanId: string, callback: () => void, actionUuid: string) => { const human = getHumanById(humanId); const action = getActionByUuid(selectedProduct.productUuid, actionUuid); - if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker') || !humanEventManagerRef.current) return; + if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker' && action.actionType !== 'operator') || !humanEventManagerRef.current) return; let state = humanEventManagerRef.current.humanStates.find(h => h.humanId === humanId); if (!state) { @@ -36,7 +36,7 @@ export function useHumanEventManager() { const existingAction = state.actionQueue.find(a => a.actionUuid === actionUuid); if (existingAction) { const currentCount = existingAction.count ?? 0; - if (existingAction.actionType === 'worker') { + if (existingAction.actionType === 'worker' || existingAction.actionType === 'operator') { if (currentCount < existingAction.maxLoadCount) { existingAction.callback = callback; existingAction.isMonitored = true; @@ -98,8 +98,8 @@ export function useHumanEventManager() { let conditionMet = false; - if (currentAction.actionType === 'worker') { - if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) { + if (currentAction.actionType === 'worker' || currentAction.actionType === 'operator') { + if ((action.actionType === 'worker' || action.actionType === 'operator') && human.currentLoad < currentAction.loadCapacity) { conditionMet = true; } else if (action.actionType === 'assembly') { conditionMet = true; @@ -107,7 +107,7 @@ export function useHumanEventManager() { } else if (currentAction.actionType === 'assembly') { if (action.actionType === 'assembly') { conditionMet = true; - } else if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) { + } else if ((action.actionType === 'worker' || action.actionType === 'operator') && human.currentLoad < currentAction.loadCapacity) { conditionMet = true; } } @@ -121,7 +121,7 @@ export function useHumanEventManager() { action.callback(); action.count = (action.count ?? 0) + 1; action.isMonitored = false; - if ((action.actionType === 'worker' && action.count >= action.maxLoadCount) || + if (((action.actionType === 'worker' || action.actionType === 'operator') && action.count >= action.maxLoadCount) || (action.actionType === 'assembly' && action.count >= action.maxAssemblyCount)) { action.isCompleted = true; } diff --git a/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx b/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx new file mode 100644 index 0000000..14934df --- /dev/null +++ b/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx @@ -0,0 +1,182 @@ +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'; +import { useProductContext } from '../../../products/productContext'; + +interface WorkerAnimatorProps { + path: [number, number, number][]; + handleCallBack: () => void; + reset: () => void; + human: HumanStatus; +} + +function OperatorAnimator({ path, handleCallBack, human, reset }: Readonly) { + const { humanStore, assetStore, productStore } = useSceneContext(); + const { getActionByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { getHumanById } = humanStore(); + const { setCurrentAnimation } = assetStore(); + 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 action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>((action as HumanAction)?.pickUpPoint?.rotation || [0, 0, 0]); + const [restRotation, setRestingRotation] = useState(true); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + const { scene } = useThree(); + + useEffect(() => { + if (!human.currentAction?.actionUuid) return; + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + if (human.currentPhase === 'init-loadPoint' && path.length > 0) { + setCurrentPath(path); + setObjectRotation((action as HumanAction).pickUpPoint?.rotation ?? null); + } else if (human.currentPhase === 'loadPoint-unloadPoint' && path.length > 0) { + setObjectRotation((action as HumanAction)?.dropPoint?.rotation ?? null); + setCurrentPath(path); + } else if (human.currentPhase === 'unloadPoint-loadPoint' && path.length > 0) { + setObjectRotation((action as HumanAction)?.pickUpPoint?.rotation ?? null); + setCurrentPath(path); + } + }, [human.currentPhase, path, objectRotation, selectedProduct, human.currentAction?.actionUuid]); + + 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 || !isPlaying) return; + + let totalDistance = 0; + const distances = []; + let accumulatedDistance = 0; + let index = 0; + const rotationSpeed = 1.5; + + 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 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); + + const angle = object.quaternion.angleTo(targetQuaternion); + if (angle < 0.01) { + object.quaternion.copy(targetQuaternion); + } else { + const step = rotationSpeed * delta * speed * human.speed; + object.quaternion.rotateTowards(targetQuaternion, step); + } + + const isAligned = angle < 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); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + } else { + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + } + } + + if (progressRef.current >= totalDistance) { + if (restRotation && objectRotation) { + 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); + setRestingRotation(false); + } else { + const step = rotationSpeed * delta * speed * human.speed; + object.quaternion.rotateTowards(targetQuaternion, step); + } + + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + return; + } + } + + if (progressRef.current >= totalDistance) { + setRestingRotation(true); + progressRef.current = 0; + movingForward.current = !movingForward.current; + setCurrentPath([]); + handleCallBack(); + } + }); + + return ( + <> + {currentPath.length > 0 && ( + + + {currentPath.map((point, index) => ( + + + + + ))} + + )} + + ); +} + +export default OperatorAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/actions/operatorInstance.tsx b/app/src/modules/simulation/human/instances/instance/actions/operatorInstance.tsx new file mode 100644 index 0000000..d938353 --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/actions/operatorInstance.tsx @@ -0,0 +1,168 @@ +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'; +import { useTriggerHandler } from '../../../../triggers/triggerHandler/useTriggerHandler'; +import { useSceneContext } from '../../../../../scene/sceneContext'; +import { useProductContext } from '../../../../products/productContext'; + +import OperatorAnimator from '../../animator/operatorAnimator'; + +function OperatorInstance({ human }: { human: HumanStatus }) { + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); + const { scene } = useThree(); + const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, craneStore, productStore } = useSceneContext(); + const { removeMaterial, setEndTime, setIsVisible } = materialStore(); + const { getStorageUnitById } = storageUnitStore(); + const { getArmBotById } = armBotStore(); + const { getConveyorById } = conveyorStore(); + const { getVehicleById } = vehicleStore(); + const { getMachineById } = machineStore(); + const { getCraneById } = craneStore(); + const { triggerPointActions } = useTriggerHandler(); + const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore(); + const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { setHumanActive, setHumanState, clearCurrentMaterials, setHumanLoad, setHumanScheduled, decrementHumanLoad, removeLastMaterial, incrementIdleTime, incrementActiveTime, resetTime, setCurrentPhase } = humanStore(); + + 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); + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + const previousTimeRef = useRef(null); + const animationFrameIdRef = useRef(null); + const humanAsset = getAssetById(human.modelUuid); + + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); + + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + + const computePath = useCallback((start: [number, number, number], end: [number, number, number]) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + let startPoint = new THREE.Vector3(start[0], start[1], start[2]); + let endPoint = new THREE.Vector3(end[0], end[1], end[2]); + const { path: segmentPath } = navMeshQuery.computePath(startPoint, endPoint); + if ( + segmentPath.length > 0 && + Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(endPoint.x) && + Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(endPoint.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(startPoint, startPoint); + 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(human.modelUuid, 'init'); + setHumanActive(human.modelUuid, false); + setHumanState(human.modelUuid, 'idle'); + setHumanScheduled(human.modelUuid, false); + setHumanLoad(human.modelUuid, 0); + resetAnimation(human.modelUuid); + setPath([]); + 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 + } + 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(() => { + if (isPlaying) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + if (!action || action.actionType !== 'operator' || !action.pickUpPoint || !action.dropPoint) return; + + if (!human.isActive && human.state === 'idle' && human.currentPhase === 'init') { + const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); + if (!humanMesh) return; + + const toPickupPath = computePath(humanMesh.position.toArray(), action?.pickUpPoint?.position || [0, 0, 0]); + + setPath(toPickupPath); + setCurrentPhase(human.modelUuid, 'init-loadPoint'); + setHumanState(human.modelUuid, 'running'); + setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + humanStatus(human.modelUuid, 'Started from init, heading to loadPoint'); + } else if (human.isActive && human.currentPhase === 'loadPoint-unloadPoint') { + if (action.pickUpPoint && action.dropPoint && humanAsset?.animationState?.current === 'idle') { + const toDrop = computePath(action.pickUpPoint.position || [0, 0, 0], action.dropPoint.position || [0, 0, 0]); + setPath(toDrop); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + humanStatus(human.modelUuid, 'Started from loadPoint, heading to unloadPoint'); + } + } else if (human.state === 'idle' && human.currentPhase === 'unhooking') { + setHumanState(human.modelUuid, 'running'); + setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'working_standing', true, false, false); + } + } else { + reset() + } + }, [human, human.currentAction, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); + + function handleCallBack() { + if (human.currentPhase === 'init-loadPoint') { + setCurrentPhase(human.modelUuid, 'waiting'); + setHumanState(human.modelUuid, 'idle'); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached loadPoint point, waiting for material'); + setPath([]); + } else if (human.currentPhase === 'loadPoint-unloadPoint') { + setCurrentPhase(human.modelUuid, 'unhooking'); + setHumanState(human.modelUuid, 'idle'); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached loadPoint point, waiting for material'); + setPath([]); + } + } + + return ( + <> + + + ) +} + +export default OperatorInstance; \ 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 8d1c51f..1532e5c 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -6,6 +6,7 @@ import { useProductContext } from '../../../products/productContext'; import MaterialAnimator from '../animator/materialAnimator'; import AssemblerInstance from './actions/assemberInstance'; import WorkerInstance from './actions/workerInstance'; +import OperatorInstance from './actions/operatorInstance'; function HumanInstance({ human }: { human: HumanStatus }) { const { isPlaying } = usePlayButtonStore(); @@ -86,6 +87,9 @@ function HumanInstance({ human }: { human: HumanStatus }) { {action && action.actionType === 'assembly' && } + {action && action.actionType === 'operator' && + + } diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx index 32c1162..f65e33f 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -183,7 +183,7 @@ function Products() { addCrane(selectedProduct.productUuid, events); if (events.point.actions.length > 0) { - addCurrentActionCrane(events.modelUuid, events.point.actions[0].actionUuid); + addCurrentActionCrane(events.modelUuid, events.point.actions[0].actionUuid, null, null); } } }); diff --git a/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts index 58925f3..4d3f3de 100644 --- a/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts +++ b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts @@ -125,7 +125,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch event.type === 'machine' || event.type === 'storageUnit' || event.type === 'roboticArm' || - event.type === 'human' + event.type === 'human' || + event.type === 'crane' ) { 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 a87fe6a..089f461 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts @@ -17,7 +17,8 @@ export async function determineExecutionMachineSequences(products: productsSchem event.type === 'machine' || event.type === 'storageUnit' || event.type === 'roboticArm' || - event.type === 'human' + event.type === 'human' || + event.type === 'crane' ) { 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 d17e2d7..09be2f2 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts @@ -20,7 +20,8 @@ export function determineExecutionOrder(products: productsSchema): PointsScheme[ event.type === 'machine' || event.type === 'storageUnit' || event.type === 'roboticArm' || - event.type === 'human' + event.type === 'human' || + event.type === 'crane' ) { 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 3ba5eb2..ebc26ac 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts @@ -17,7 +17,8 @@ export async function determineExecutionSequences(products: productsSchema): Pro event.type === 'machine' || event.type === 'storageUnit' || event.type === 'roboticArm' || - event.type === 'human' + event.type === 'human' || + event.type === 'crane' ) { 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 8f812bf..76f2854 100644 --- a/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts +++ b/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts @@ -92,7 +92,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch event.type === 'machine' || event.type === 'storageUnit' || event.type === 'roboticArm' || - event.type === 'human' + event.type === 'human' || + event.type === 'crane' ) { 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 fbc4dde..9660a6d 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -25,7 +25,7 @@ export function useTriggerHandler() { const { addCraneToMonitor } = useCraneEventManager(); const { getVehicleById } = vehicleStore(); const { getHumanById, setHumanScheduled } = humanStore(); - const { getCraneById, setCraneScheduled } = craneStore(); + const { getCraneById, setCraneScheduled, addCurrentAction } = craneStore(); const { getMachineById, setMachineActive } = machineStore(); const { getStorageUnitById } = storageUnitStore(); const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore(); @@ -427,20 +427,21 @@ export function useTriggerHandler() { action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) { const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); - if (model?.type === 'transfer') { + if (model?.type === 'human') { const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid); if (crane) { setIsPaused(materialId, true); setCraneScheduled(crane.modelUuid, true); - const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (conveyor) { - addConveyorToMonitor(conveyor.modelUuid, () => { + const human = getHumanById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (human) { + addHumanToMonitor(human.modelUuid, () => { + addCurrentAction(crane.modelUuid, action.actionUuid, null, null); addCraneToMonitor(crane.modelUuid, () => { setIsPaused(materialId, true); handleAction(action, materialId); }, action.actionUuid) - }) + }, action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid) } } } @@ -620,6 +621,66 @@ export function useTriggerHandler() { } } } + } else if (toEvent?.type === 'crane') { + // Vehicle to Human + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + + // Handle current action of the material + handleAction(action, materialId); + + if (material.next) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const crane = getCraneById(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 (crane) { + 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 (model?.type === 'human') { + const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (crane) { + setIsPaused(materialId, true); + setCraneScheduled(crane.modelUuid, true); + const human = getHumanById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (human) { + addHumanToMonitor(human.modelUuid, () => { + addCurrentAction(crane.modelUuid, action.actionUuid, null, null); + addCraneToMonitor(crane.modelUuid, () => { + setIsPaused(materialId, true); + + handleAction(action, materialId); + }, action.actionUuid) + }, action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid) + } + } + } + } + } + } + } + } + } } } else if (fromEvent?.type === 'machine') { if (toEvent?.type === 'transfer') { @@ -1408,6 +1469,134 @@ export function useTriggerHandler() { setIsPaused(material.materialId, false); } } + } else if (material && action.actionType === 'operator') { + + 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, + }); + + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + + 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 (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); + } + } } diff --git a/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx index 8b943d9..b4c9b90 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx @@ -13,30 +13,44 @@ const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => { useEffect(() => { const loadState = agvDetail.currentLoad > 0; setHasLoad(loadState); - + if (!loadState) { setIsAttached(false); - if (meshRef.current?.parent) { - meshRef.current.parent.remove(meshRef.current); + const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D; + if (agvModel) { + const material = agvModel.getObjectByName('Sample-Material'); + if (material) { + agvModel.remove(material); + } } } }, [agvDetail.currentLoad]); useFrame(() => { + // if (agvDetail.currentMaterials.length === 0 || agvDetail.currentLoad === 0) { + // const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D; + // if (agvModel) { + // const material = agvModel.getObjectByName('Sample-Material'); + // if (material) { + // agvModel.remove(material); + // } + // } + // } if (!hasLoad || !meshRef.current || isAttached) return; const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D; if (agvModel && !isAttached) { - if (meshRef.current.parent) { - meshRef.current.parent.remove(meshRef.current); + const material = agvModel.getObjectByName('Sample-Material'); + if (material) { + agvModel.remove(material); } - + agvModel.add(meshRef.current); - + meshRef.current.position.copy(offset); meshRef.current.rotation.set(0, 0, 0); meshRef.current.scale.set(1, 1, 1); - + setIsAttached(true); } }); @@ -45,6 +59,7 @@ const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => { <> {hasLoad && agvDetail.currentMaterials.length > 0 && ( {selectedPath === "auto" && - + {currentPath.map((pos, i) => { if (i < currentPath.length - 1) { return ( diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 5de64cd..0466ba2 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -12,24 +12,26 @@ import MaterialAnimator from '../animator/materialAnimator'; import VehicleAnimator from '../animator/vehicleAnimator'; import { useHumanEventManager } from '../../../human/eventManager/useHumanEventManager'; +import { useCraneEventManager } from '../../../crane/eventManager/useCraneEventManager'; function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); - const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, productStore, assetStore } = useSceneContext(); + const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, productStore, assetStore } = useSceneContext(); const { removeMaterial, setEndTime, setIsVisible } = materialStore(); const { getStorageUnitById } = storageUnitStore(); const { getHumanById, addCurrentAction, addCurrentMaterial, incrementHumanLoad } = humanStore(); + const { getCraneById, addCurrentAction: addCraneAction, addCurrentMaterial: addCraneMaterial, incrementCraneLoad } = craneStore(); const { getArmBotById } = armBotStore(); const { getConveyorById } = conveyorStore(); const { triggerPointActions } = useTriggerHandler(); const { setCurrentAnimation, getAssetById } = assetStore(); const { addHumanToMonitor } = useHumanEventManager(); + const { addCraneToMonitor } = useCraneEventManager(); const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { vehicles, setVehicleActive, setVehicleState, setVehiclePicking, clearCurrentMaterials, setVehicleLoad, decrementVehicleLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = vehicleStore(); - const [currentPhase, setCurrentPhase] = useState('stationed'); + const { vehicles, setCurrentPhase, setVehicleActive, setVehicleState, setVehiclePicking, clearCurrentMaterials, setVehicleLoad, decrementVehicleLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = vehicleStore(); const [path, setPath] = useState<[number, number, number][]>([]); const pauseTimeRef = useRef(null); const idleTimeRef = useRef(0); @@ -80,7 +82,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) // Function to reset everything function reset() { - setCurrentPhase('stationed'); + setCurrentPhase(agvDetail.modelUuid, 'stationed'); setVehicleActive(agvDetail.modelUuid, false); setVehiclePicking(agvDetail.modelUuid, false); setVehicleState(agvDetail.modelUuid, 'idle'); @@ -103,20 +105,20 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) if (isPlaying && selectedPath === "auto") { if (!agvDetail.point.action.unLoadPoint || !agvDetail.point.action.pickUpPoint) return; - if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'stationed') { + if (!agvDetail.isActive && agvDetail.state === 'idle' && agvDetail.currentPhase === 'stationed') { const toPickupPath = computePath( new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]), agvDetail?.point?.action?.pickUpPoint?.position ); setPath(toPickupPath); - setCurrentPhase('stationed-pickup'); + setCurrentPhase(agvDetail.modelUuid, 'stationed-pickup'); setVehicleState(agvDetail.modelUuid, 'running'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, true); vehicleStatus(agvDetail.modelUuid, 'Started from station, heading to pickup'); return; - } else if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'picking') { + } else if (!agvDetail.isActive && agvDetail.state === 'idle' && agvDetail.currentPhase === 'picking') { if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity && agvDetail.currentMaterials.length > 0) { if (agvDetail.point.action.pickUpPoint && agvDetail.point.action.unLoadPoint) { const toDrop = computePath( @@ -124,21 +126,21 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) agvDetail.point.action.unLoadPoint.position ); setPath(toDrop); - setCurrentPhase('pickup-drop'); + setCurrentPhase(agvDetail.modelUuid, 'pickup-drop'); setVehicleState(agvDetail.modelUuid, 'running'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, true); vehicleStatus(agvDetail.modelUuid, 'Started from pickup point, heading to drop point'); } } - } else if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'dropping' && agvDetail.currentLoad === 0) { + } else if (!agvDetail.isActive && agvDetail.state === 'idle' && agvDetail.currentPhase === 'dropping' && agvDetail.currentLoad === 0) { if (agvDetail.point.action.pickUpPoint && agvDetail.point.action.unLoadPoint) { const dropToPickup = computePath( agvDetail.point.action.unLoadPoint.position, agvDetail.point.action.pickUpPoint.position ); setPath(dropToPickup); - setCurrentPhase('drop-pickup'); + setCurrentPhase(agvDetail.modelUuid, 'drop-pickup'); setVehicleState(agvDetail.modelUuid, 'running'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, true); @@ -149,7 +151,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) else { reset() } - }, [vehicles, currentPhase, path, isPlaying, selectedPath]); + }, [vehicles, agvDetail.currentPhase, path, isPlaying, selectedPath]); function animate(currentTime: number) { if (previousTimeRef.current === null) { @@ -198,22 +200,22 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) }, [agvDetail, isPlaying]); function handleCallBack() { - if (currentPhase === 'stationed-pickup') { - setCurrentPhase('picking'); + if (agvDetail.currentPhase === 'stationed-pickup') { + setCurrentPhase(agvDetail.modelUuid, 'picking'); setVehicleState(agvDetail.modelUuid, 'idle'); setVehiclePicking(agvDetail.modelUuid, true); setVehicleActive(agvDetail.modelUuid, false); vehicleStatus(agvDetail.modelUuid, 'Reached pickup point, waiting for material'); setPath([]); - } else if (currentPhase === 'pickup-drop') { - setCurrentPhase('dropping'); + } else if (agvDetail.currentPhase === 'pickup-drop') { + setCurrentPhase(agvDetail.modelUuid, 'dropping'); setVehicleState(agvDetail.modelUuid, 'idle'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, false); vehicleStatus(agvDetail.modelUuid, 'Reached drop point'); setPath([]); - } else if (currentPhase === 'drop-pickup') { - setCurrentPhase('picking'); + } else if (agvDetail.currentPhase === 'drop-pickup') { + setCurrentPhase(agvDetail.modelUuid, 'picking'); setVehicleState(agvDetail.modelUuid, 'idle'); setVehiclePicking(agvDetail.modelUuid, true); setVehicleActive(agvDetail.modelUuid, false); @@ -252,6 +254,12 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) if (action && (triggeredAction?.actionType === 'assembly' || triggeredAction?.actionType === 'worker')) { handleMaterialDropToHuman(model, triggeredAction); } + } else if (model.type === 'crane') { + const action = getActionByUuid(selectedProduct.productUuid, agvDetail.point.action.actionUuid); + if (action && (triggeredAction?.actionType === 'pickAndDrop')) { + handleMaterialDropToCrane(model, triggeredAction); + addCraneAction(model.modelUuid, triggeredAction.actionUuid, null, null); + } } } else { const droppedMaterial = agvDetail.currentLoad; @@ -265,6 +273,64 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) } } + function handleMaterialDropToCrane(model: CraneEventSchema, action: CraneAction) { + + if (model) { + if (action.actionType === 'pickAndDrop') { + addCraneToMonitor(model.modelUuid, () => { + loopMaterialDropToCrane( + agvDetail, + model.modelUuid, + action.actionUuid + ); + }, action.actionUuid || '') + } + } + } + + function loopMaterialDropToCrane( + vehicle: VehicleStatus, + craneId: string, + craneActionId: string + ) { + let currentVehicleLoad = vehicle.currentLoad; + + const unloadLoop = () => { + const crane = getCraneById(craneId); + const craneaction = crane?.point.actions.find((action) => action.actionUuid === craneActionId); + + if (!crane || crane.currentAction?.actionUuid !== craneaction?.actionUuid) return; + if (crane.isCarrying) { + decrementVehicleLoad(vehicle.modelUuid, 1); + currentVehicleLoad -= 1; + const material = removeLastMaterial(vehicle.modelUuid); + if (material) { + setIsVisible(material.materialId, false); + } + return; + } else if (!crane.isCarrying && !crane.isActive && crane.currentLoad < (craneaction?.maxPickUpCount || 0) && craneaction?.actionType === 'pickAndDrop') { + const material = getLastMaterial(vehicle.modelUuid); + if (material) { + incrementCraneLoad(craneId, 1); + addCraneAction(craneId, craneActionId, material.materialType, material.materialId); + addCraneMaterial(craneId, material.materialType, material.materialId); + } + } + setTimeout(() => { + requestAnimationFrame(unloadLoop); + }, 500) + }; + + const crane = getCraneById(craneId); + const craneaction = crane?.point.actions.find((action) => action.actionUuid === craneActionId); + if (crane && crane.currentLoad < (craneaction?.maxPickUpCount || 0)) { + setTimeout(() => { + unloadLoop(); + }, 500) + } + } + + function handleMaterialDropToHuman(model: HumanEventSchema, action: HumanAction) { if (model) { @@ -576,7 +642,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) void; - addCurrentAction: (modelUuid: string, actionUuid: string) => void; + addCurrentAction: (modelUuid: string, actionUuid: string, materialType: string | null, materialId: string | null) => void; removeCurrentAction: (modelUuid: string) => void; setCraneActive: (modelUuid: string, isActive: boolean) => void; setCraneScheduled: (modelUuid: string, isScheduled: boolean) => void; + setIsCaryying: (modelUuid: string, isCarrying: boolean) => void; setCraneLoad: (modelUuid: string, load: number) => void; setCraneState: (modelUuid: string, newState: CraneStatus["state"]) => void; incrementCraneLoad: (modelUuid: string, incrementBy: number) => void; @@ -54,6 +55,7 @@ export const createCraneStore = () => { currentPhase: 'init', isActive: false, isScheduled: false, + isCarrying: false, idleTime: 0, activeTime: 0, currentLoad: 0, @@ -93,7 +95,7 @@ export const createCraneStore = () => { }); }, - addCurrentAction: (modelUuid, actionUuid) => { + addCurrentAction: (modelUuid, actionUuid, materialType, materialId) => { set((state) => { const crane = state.cranes.find(c => c.modelUuid === modelUuid); if (crane) { @@ -101,7 +103,9 @@ export const createCraneStore = () => { if (action) { crane.currentAction = { actionUuid: action.actionUuid, - actionName: action.actionName + actionName: action.actionName, + materialType: materialType, + materialId: materialId }; } } @@ -135,6 +139,15 @@ export const createCraneStore = () => { }); }, + setIsCaryying: (modelUuid, isCarrying) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.isCarrying = isCarrying; + } + }); + }, + setCraneLoad: (modelUuid, load) => { set((state) => { const crane = state.cranes.find(c => c.modelUuid === modelUuid); diff --git a/app/src/store/simulation/useVehicleStore.ts b/app/src/store/simulation/useVehicleStore.ts index 1e558f0..e01bbce 100644 --- a/app/src/store/simulation/useVehicleStore.ts +++ b/app/src/store/simulation/useVehicleStore.ts @@ -15,6 +15,7 @@ interface VehiclesStore { deletePathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], pointId: string) => VehicleAction["paths"]; clearVehicles: () => void; + setCurrentPhase: (modelUuid: string, phase: string) => void; setVehicleActive: (modelUuid: string, isActive: boolean) => void; setVehiclePicking: (modelUuid: string, isPicking: boolean) => void; updateSteeringAngle: (modelUuid: string, steeringAngle: number) => void; @@ -52,6 +53,7 @@ export const createVehicleStore = () => { state.vehicles.push({ ...event, productUuid, + currentPhase: 'stationed', isActive: false, isPicking: false, idleTime: 0, @@ -130,6 +132,15 @@ export const createVehicleStore = () => { }); }, + setCurrentPhase: (modelUuid, phase) => { + set((state) => { + const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); + if (vehicle) { + vehicle.currentPhase = phase; + } + }); + }, + setVehicleActive: (modelUuid, isActive) => { set((state) => { const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid); diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 1e90fe8..ae6c55a 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -98,7 +98,7 @@ interface StorageAction { interface HumanAction { actionUuid: string; actionName: string; - actionType: "worker" | "assembly"; + actionType: "worker" | "assembly" | "operator"; processTime: number; swapMaterial?: string; assemblyPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } @@ -264,6 +264,7 @@ interface ArmBotStatus extends RoboticArmEventSchema { interface VehicleStatus extends VehicleEventSchema { productUuid: string; + currentPhase: string; isActive: boolean; isPicking: boolean; idleTime: number; @@ -303,6 +304,7 @@ interface CraneStatus extends CraneEventSchema { currentPhase: string; isActive: boolean; isScheduled: boolean; + isCarrying: boolean; idleTime: number; activeTime: number; currentLoad: number; @@ -310,6 +312,8 @@ interface CraneStatus extends CraneEventSchema { currentAction?: { actionUuid: string; actionName: string; + materialType: string | null; + materialId: string | null; }; } @@ -319,7 +323,7 @@ interface CraneStatus extends CraneEventSchema { type HumanEventState = { humanId: string; actionQueue: { - actionType: 'worker' | 'assembly'; + actionType: 'worker' | 'assembly' | 'operator'; actionUuid: string; actionName: string; maxLoadCount: number;