added crane interialtion with other assets
This commit is contained in:
@@ -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() {
|
||||
<LabledDropdown
|
||||
label="Action Type"
|
||||
defaultOption={activeOption}
|
||||
options={["worker", "assembly"]}
|
||||
options={["worker", "assembly", "operator"]}
|
||||
onSelect={handleSelectActionType}
|
||||
disabled={false}
|
||||
/>
|
||||
</div>
|
||||
{currentAction.actionType === 'worker' &&
|
||||
{(currentAction.actionType === 'worker' || currentAction.actionType === "operator") &&
|
||||
<WorkerAction
|
||||
loadCapacity={{
|
||||
value: loadCapacity,
|
||||
|
||||
@@ -24,7 +24,7 @@ export function usePickAndDropHandler() {
|
||||
if (!modelUuid) return;
|
||||
|
||||
incrementCraneLoad(modelUuid, 1);
|
||||
addCurrentAction(modelUuid, action.actionUuid);
|
||||
addCurrentAction(modelUuid, action.actionUuid, material.materialType, materialId);
|
||||
addCurrentMaterial(modelUuid, material.materialType, material.materialId);
|
||||
|
||||
pickAndDropLogStatus(material.materialName, `performing pickAndDrop action`);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useProductContext } from "../../../products/productContext";
|
||||
import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager";
|
||||
|
||||
export function useRetrieveHandler() {
|
||||
const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, conveyorStore, productStore, humanStore, assetStore } = useSceneContext();
|
||||
const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, conveyorStore, craneStore, productStore, humanStore, assetStore } = useSceneContext();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { addMaterial } = materialStore();
|
||||
const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore();
|
||||
@@ -14,6 +14,7 @@ export function useRetrieveHandler() {
|
||||
const { getVehicleById, incrementVehicleLoad, addCurrentMaterial } = vehicleStore();
|
||||
const { getConveyorById } = conveyorStore();
|
||||
const { getHumanById, incrementHumanLoad, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore();
|
||||
const { getCraneById, incrementCraneLoad, addCurrentMaterial: addCurrentMaterialToCrane, addCurrentAction: addCurrentActionToCrane } = craneStore();
|
||||
const { getAssetById, setCurrentAnimation } = assetStore();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { getArmBotById, addCurrentAction } = armBotStore();
|
||||
@@ -28,6 +29,7 @@ export function useRetrieveHandler() {
|
||||
const retrievalTimeRef = useRef<Map<string, number>>(new Map());
|
||||
const retrievalCountRef = useRef<Map<string, number>>(new Map());
|
||||
const monitoredHumansRef = useRef<Set<string>>(new Set());
|
||||
const cranePickupLockRef = useRef<Map<string, boolean>>(new Map());
|
||||
|
||||
const [initialDelayComplete, setInitialDelayComplete] = useState(false);
|
||||
const delayTimerRef = useRef<NodeJS.Timeout | null>(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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<MaterialAnimatorProps>) {
|
||||
const materialRef = useRef<any>(null);
|
||||
const { scene } = useThree();
|
||||
const [isRendered, setIsRendered] = useState<boolean>(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 && (
|
||||
<MaterialModel
|
||||
materialId={crane.currentAction?.materialId ?? ''}
|
||||
matRef={materialRef}
|
||||
materialType={crane.currentAction?.materialType ?? 'Default material'}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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<THREE.Vector3 | null>(null);
|
||||
const trolleyWorldRef = useRef<THREE.Vector3 | null>(null);
|
||||
const hookWorldRef = useRef<THREE.Vector3 | null>(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 (
|
||||
<>
|
||||
<mesh
|
||||
geometry={geometry}
|
||||
position={position}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={0x888888}
|
||||
metalness={0.5}
|
||||
roughness={0.4}
|
||||
side={THREE.DoubleSide}
|
||||
transparent={true}
|
||||
opacity={0.3}
|
||||
/>
|
||||
</mesh>
|
||||
{isHelperNeeded &&
|
||||
<>
|
||||
<mesh
|
||||
geometry={geometry}
|
||||
position={position}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={0x888888}
|
||||
metalness={0.5}
|
||||
roughness={0.4}
|
||||
side={THREE.DoubleSide}
|
||||
transparent={true}
|
||||
opacity={0.3}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{points && points.map((point, i) => (
|
||||
<Box
|
||||
key={`original-${i}`}
|
||||
position={point}
|
||||
args={[0.15, 0.15, 0.15]}
|
||||
>
|
||||
<meshStandardMaterial color="yellow" />
|
||||
</Box>
|
||||
))}
|
||||
{points && points.map((point, i) => (
|
||||
<Box
|
||||
key={`original-${i}`}
|
||||
position={point}
|
||||
args={[0.15, 0.15, 0.15]}
|
||||
>
|
||||
<meshStandardMaterial color="yellow" />
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{clampedPoints && clampedPoints.map((point, i) => (
|
||||
<Sphere
|
||||
key={`clamped-${i}`}
|
||||
position={point}
|
||||
args={[0.1, 16, 16]}
|
||||
>
|
||||
<meshStandardMaterial color={isInside[i] ? 'lightgreen' : 'orange'} />
|
||||
</Sphere>
|
||||
))}
|
||||
{clampedPoints && clampedPoints.map((point, i) => (
|
||||
<Sphere
|
||||
key={`clamped-${i}`}
|
||||
position={point}
|
||||
args={[0.1, 16, 16]}
|
||||
>
|
||||
<meshStandardMaterial color={isInside[i] ? 'lightgreen' : 'orange'} />
|
||||
</Sphere>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<string>('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}
|
||||
/>
|
||||
|
||||
<MaterialAnimator crane={crane} />
|
||||
|
||||
<PillarJibHelper
|
||||
crane={crane}
|
||||
points={points}
|
||||
isHelperNeeded={false}
|
||||
/>
|
||||
|
||||
</>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<WorkerAnimatorProps>) {
|
||||
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<number>(0);
|
||||
const movingForward = useRef<boolean>(true);
|
||||
const completedRef = useRef<boolean>(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<boolean>(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 && (
|
||||
<group visible={false}>
|
||||
<Line points={currentPath} color="blue" lineWidth={3} />
|
||||
{currentPath.map((point, index) => (
|
||||
<mesh key={index} position={point}>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="red" />
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default OperatorAnimator;
|
||||
@@ -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<number | null>(null);
|
||||
const idleTimeRef = useRef<number>(0);
|
||||
const activeTimeRef = useRef<number>(0);
|
||||
const isPausedRef = useRef<boolean>(false);
|
||||
const isSpeedRef = useRef<number>(0);
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
const previousTimeRef = useRef<number | null>(null);
|
||||
const animationFrameIdRef = useRef<number | null>(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 (
|
||||
<>
|
||||
<OperatorAnimator
|
||||
path={path}
|
||||
handleCallBack={handleCallBack}
|
||||
human={human}
|
||||
reset={reset}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default OperatorInstance;
|
||||
@@ -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' &&
|
||||
<AssemblerInstance human={human} />
|
||||
}
|
||||
{action && action.actionType === 'operator' &&
|
||||
<OperatorInstance human={human} />
|
||||
}
|
||||
|
||||
<MaterialAnimator human={human} />
|
||||
</>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 && (
|
||||
<MaterialModel
|
||||
name={'Sample-Material'}
|
||||
matRef={meshRef}
|
||||
materialId={agvDetail.currentMaterials[0].materialId || ''}
|
||||
materialType={agvDetail.currentMaterials[0].materialType || 'Default material'}
|
||||
|
||||
@@ -183,7 +183,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
||||
return (
|
||||
<>
|
||||
{selectedPath === "auto" &&
|
||||
<group>
|
||||
<group visible={false}>
|
||||
{currentPath.map((pos, i) => {
|
||||
if (i < currentPath.length - 1) {
|
||||
return (
|
||||
|
||||
@@ -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<string>('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<number | null>(null);
|
||||
const idleTimeRef = useRef<number>(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 }>)
|
||||
<VehicleAnimator
|
||||
path={path}
|
||||
handleCallBack={handleCallBack}
|
||||
currentPhase={currentPhase}
|
||||
currentPhase={agvDetail.currentPhase}
|
||||
agvUuid={agvDetail?.modelUuid}
|
||||
agvDetail={agvDetail}
|
||||
reset={reset}
|
||||
|
||||
@@ -14,11 +14,12 @@ interface CraneStore {
|
||||
|
||||
setCurrentPhase: (modelUuid: string, phase: string) => 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
8
app/src/types/simulationTypes.d.ts
vendored
8
app/src/types/simulationTypes.d.ts
vendored
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user