Merge remote-tracking branch 'origin/dev-simulation/human' into main-demo

This commit is contained in:
2025-07-08 10:32:57 +05:30
27 changed files with 794 additions and 885 deletions

View File

@@ -0,0 +1,36 @@
import { useCallback } from "react";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
export function useAssemblyHandler() {
const { materialStore, humanStore, productStore } = useSceneContext();
const { getMaterialById } = materialStore();
const { getModelUuidByActionUuid } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { incrementHumanLoad, addCurrentMaterial } = humanStore();
const assemblyLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
}
const handleAssembly = useCallback((action: HumanAction, materialId?: string) => {
if (!action || action.actionType !== 'assembly' || !materialId) return;
const material = getMaterialById(materialId);
if (!material) return;
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
if (!modelUuid) return;
incrementHumanLoad(modelUuid, 1);
addCurrentMaterial(modelUuid, material.materialType, material.materialId);
assemblyLogStatus(material.materialName, `performing assembly action`);
}, [getMaterialById]);
return {
handleAssembly,
};
}

View File

@@ -1,13 +1,19 @@
import { useEffect, useCallback } from 'react';
import { useWorkerHandler } from './actionHandler/useWorkerHandler';
import { useAssemblyHandler } from './actionHandler/useAssemblyHandler';
export function useHumanActions() {
const { handleWorker } = useWorkerHandler();
const { handleAssembly } = useAssemblyHandler();
const handleWorkerAction = useCallback((action: HumanAction, materialId: string) => {
handleWorker(action, materialId);
}, [handleWorker]);
const handleAssemblyAction = useCallback((action: HumanAction, materialId: string) => {
handleAssembly(action, materialId);
}, [handleAssembly]);
const handleHumanAction = useCallback((action: HumanAction, materialId: string) => {
if (!action) return;
@@ -15,10 +21,13 @@ export function useHumanActions() {
case 'worker':
handleWorkerAction(action, materialId);
break;
case 'assembly':
handleAssemblyAction(action, materialId);
break;
default:
console.warn(`Unknown Human action type: ${action.actionType}`);
}
}, [handleWorkerAction]);
}, [handleWorkerAction, handleAssemblyAction]);
const cleanup = useCallback(() => {
}, []);

View File

@@ -39,7 +39,7 @@ export function useActionHandler() {
case 'store': case 'retrieve':
handleStorageAction(action as StorageAction, materialId as string);
break;
case 'worker':
case 'worker': case 'assembly':
handleHumanAction(action as HumanAction, materialId as string);
break;
default:

View File

@@ -6,6 +6,7 @@ import { MaterialModel } from '../../../materials/instances/material/materialMod
const MaterialAnimator = ({ human }: { human: HumanStatus }) => {
const meshRef = useRef<any>(null!);
const [hasLoad, setHasLoad] = useState(false);
const [isAttached, setIsAttached] = useState(false);
const { scene } = useThree();
useEffect(() => {
@@ -13,32 +14,45 @@ const MaterialAnimator = ({ human }: { human: HumanStatus }) => {
}, [human.currentLoad]);
useEffect(() => {
if (!hasLoad || !meshRef.current) return;
if (!hasLoad || !meshRef.current) {
setIsAttached(false);
return;
}
const humanModel = scene.getObjectByProperty("uuid", human.modelUuid) as THREE.Object3D;
if (!humanModel) return;
meshRef.current.visible = false;
const bone = humanModel.getObjectByName('PlaceObjectRefBone') as THREE.Bone;
if (bone) {
if (meshRef.current.parent) {
meshRef.current.parent.remove(meshRef.current);
}
bone.add(meshRef.current);
meshRef.current.position.set(0, 0, 0);
meshRef.current.rotation.set(0, 0, 0);
meshRef.current.scale.set(1, 1, 1);
meshRef.current.visible = true;
setIsAttached(true);
}
}, [hasLoad, human.modelUuid]);
}, [hasLoad, human.modelUuid, scene]);
return (
<>
{hasLoad && human.currentMaterials.length > 0 && (
{hasLoad && human.point.action.actionType === 'worker' && human.currentMaterials.length > 0 && (
<MaterialModel
matRef={meshRef}
materialId={human.currentMaterials[0].materialId || ''}
materialType={human.currentMaterials[0].materialType || 'Default material'}
visible={isAttached}
/>
)}
</>
);
};
export default MaterialAnimator;
export default MaterialAnimator;

View File

@@ -16,7 +16,7 @@ function HumanInstance({ human }: { human: HumanStatus }) {
const { isPlaying } = usePlayButtonStore();
const { scene } = useThree();
const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext();
const { removeMaterial, setEndTime } = materialStore();
const { removeMaterial, setEndTime, setMaterial } = materialStore();
const { getStorageUnitById } = storageUnitStore();
const { getArmBotById } = armBotStore();
const { getConveyorById } = conveyorStore();
@@ -41,6 +41,13 @@ function HumanInstance({ human }: { human: HumanStatus }) {
const previousTimeRef = useRef<number | null>(null);
const animationFrameIdRef = useRef<number | null>(null);
const humanAsset = getAssetById(human.modelUuid);
const processStartTimeRef = useRef<number | null>(null);
const processTimeRef = useRef<number>(0);
const processAnimationIdRef = useRef<number | null>(null);
const accumulatedPausedTimeRef = useRef<number>(0);
const lastPauseTimeRef = useRef<number | null>(null);
const hasLoggedHalfway = useRef(false);
const hasLoggedCompleted = useRef(false);
useEffect(() => {
isPausedRef.current = isPaused;
@@ -94,6 +101,16 @@ function HumanInstance({ human }: { human: HumanStatus }) {
cancelAnimationFrame(animationFrameIdRef.current)
animationFrameIdRef.current = null
}
if (processAnimationIdRef.current) {
cancelAnimationFrame(processAnimationIdRef.current);
processAnimationIdRef.current = null;
}
processStartTimeRef.current = null;
processTimeRef.current = 0;
accumulatedPausedTimeRef.current = 0;
lastPauseTimeRef.current = null;
hasLoggedHalfway.current = false;
hasLoggedCompleted.current = false;
const object = scene.getObjectByProperty('uuid', human.modelUuid);
if (object && human) {
object.position.set(human.position[0], human.position[1], human.position[2]);
@@ -103,7 +120,103 @@ function HumanInstance({ human }: { human: HumanStatus }) {
useEffect(() => {
if (isPlaying) {
if (!human.point.action.pickUpPoint || !human.point.action.dropPoint) return;
if (!human.point.action.assemblyPoint || human.point.action.actionType === 'worker') return;
if (!human.isActive && human.state === 'idle' && currentPhase === 'init') {
setHumanState(human.modelUuid, 'idle');
setCurrentPhase('waiting');
setHumanPicking(human.modelUuid, false);
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Human is waiting for material in assembly');
} else if (!human.isActive && human.state === 'idle' && currentPhase === 'waiting') {
if (human.currentMaterials.length > 0 && humanAsset && humanAsset.animationState?.current !== 'working_standing') {
setCurrentAnimation(human.modelUuid, 'working_standing', true, true, false);
setHumanState(human.modelUuid, 'running');
setCurrentPhase('assembling');
setHumanPicking(human.modelUuid, true);
setHumanActive(human.modelUuid, true);
processStartTimeRef.current = performance.now();
processTimeRef.current = human.point.action.processTime || 0;
accumulatedPausedTimeRef.current = 0;
lastPauseTimeRef.current = null;
hasLoggedHalfway.current = false;
hasLoggedCompleted.current = false;
if (!processAnimationIdRef.current) {
processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess);
}
}
} else if (human.isActive && human.state === 'running' && human.currentMaterials.length > 0 && humanAsset && humanAsset.animationState?.current === 'working_standing' && humanAsset.animationState?.isCompleted) {
if (human.point.action.assemblyPoint && currentPhase === 'assembling') {
setHumanState(human.modelUuid, 'idle');
setCurrentPhase('waiting');
setHumanPicking(human.modelUuid, false);
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Human is waiting for material in assembly');
decrementHumanLoad(human.modelUuid, 1);
const material = removeLastMaterial(human.modelUuid);
if (material) {
triggerPointActions(human.point.action, material.materialId);
}
}
}
} else {
reset()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [human, currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]);
const trackAssemblyProcess = useCallback(() => {
const now = performance.now();
if (!processStartTimeRef.current || !human.point.action.processTime) {
return;
}
if (isPausedRef.current) {
if (!lastPauseTimeRef.current) {
lastPauseTimeRef.current = now;
}
processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess);
return;
} else if (lastPauseTimeRef.current) {
accumulatedPausedTimeRef.current += now - lastPauseTimeRef.current;
lastPauseTimeRef.current = null;
}
const elapsed = (now - processStartTimeRef.current - accumulatedPausedTimeRef.current) * isSpeedRef.current;
const totalProcessTimeMs = human.point.action.processTime * 1000;
if (elapsed >= totalProcessTimeMs / 2 && !hasLoggedHalfway.current) {
hasLoggedHalfway.current = true;
if (human.currentMaterials.length > 0) {
setMaterial(human.currentMaterials[0].materialId, human.point.action.swapMaterial || 'Default Material');
}
humanStatus(human.modelUuid, `🟡 Human ${human.modelUuid} reached halfway in assembly.`);
}
if (elapsed >= totalProcessTimeMs && !hasLoggedCompleted.current) {
hasLoggedCompleted.current = true;
setCurrentAnimation(human.modelUuid, 'working_standing', true, true, true);
if (processAnimationIdRef.current) {
cancelAnimationFrame(processAnimationIdRef.current);
processAnimationIdRef.current = null;
}
humanStatus(human.modelUuid, `✅ Human ${human.modelUuid} completed assembly process.`);
return;
}
processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess);
}, [human.modelUuid, human.point.action.processTime, human.currentMaterials]);
useEffect(() => {
if (isPlaying) {
if (!human.point.action.pickUpPoint || !human.point.action.dropPoint || human.point.action.actionType === 'assembly') return;
if (!human.isActive && human.state === 'idle' && currentPhase === 'init') {
const toPickupPath = computePath(
@@ -145,7 +258,7 @@ function HumanInstance({ human }: { human: HumanStatus }) {
setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true);
humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point');
}
} else if (human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0) {
} else if (human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0 && humanAsset?.animationState?.current !== 'pickup') {
setCurrentAnimation(human.modelUuid, 'pickup', true, false, false);
}
} else if (!human.isActive && human.state === 'idle' && currentPhase === 'dropping' && human.currentLoad === 0) {
@@ -253,6 +366,9 @@ function HumanInstance({ human }: { human: HumanStatus }) {
function startUnloadingProcess() {
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) {
if (human.point.action.triggers.length > 0) {
const trigger = getTriggerByUuid(selectedProduct.productUuid, human.point.action.triggers[0]?.triggerUuid);

View File

@@ -4,21 +4,24 @@ import { useFrame, useThree } from '@react-three/fiber';
import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore';
import { useProductContext } from '../../../products/productContext';
import { useSceneContext } from '../../../../scene/sceneContext';
import { Group, Plane, Vector3 } from 'three';
import { Group, Plane, Vector2, Vector3 } from 'three';
import { useVersionContext } from '../../../../builder/version/versionContext';
import { useParams } from 'react-router-dom';
import startPoint from "../../../../../assets/gltf-glb/ui/arrow_green.glb";
import startEnd from "../../../../../assets/gltf-glb/ui/arrow_red.glb";
import startPoint from "../../../../../assets/gltf-glb/ui/human-ui-green.glb";
import startEnd from "../../../../../assets/gltf-glb/ui/human-ui-orange.glb";
import assembly from "../../../../../assets/gltf-glb/ui/human-ui-assembly.glb";
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
function HumanUi() {
const { scene: startScene } = useGLTF(startPoint) as any;
const { scene: endScene } = useGLTF(startEnd) as any;
const { scene: assemblyScene } = useGLTF(assembly) as any;
const startMarker = useRef<Group>(null);
const endMarker = useRef<Group>(null);
const assemblyMarker = useRef<Group>(null);
const outerGroup = useRef<Group>(null);
const prevMousePos = useRef({ x: 0, y: 0 });
const { controls, raycaster } = useThree();
const { controls, raycaster, camera } = useThree();
const { selectedEventSphere } = useSelectedEventSphere();
const { selectedProductStore } = useProductContext();
const { humanStore, productStore } = useSceneContext();
@@ -27,11 +30,13 @@ function HumanUi() {
const { updateEvent } = productStore();
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]);
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]);
const [startRotation, setStartRotation] = useState<[number, number, number]>([0, 0, 0]);
const [startRotation, setStartRotation] = useState<[number, number, number]>([0, Math.PI, 0]);
const [assemblyRotation, setAssemblyRotation] = useState<[number, number, number]>([0, 0, 0]);
const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]);
const { isDragging, setIsDragging } = useIsDragging();
const { isRotating, setIsRotating } = useIsRotating();
const plane = useRef(new Plane(new Vector3(0, 1, 0), 0));
const dragOffset = useRef(new Vector3());
const [selectedHumanData, setSelectedHumanData] = useState<{
position: [number, number, number];
@@ -43,6 +48,10 @@ function HumanUi() {
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const selectedHuman = selectedEventSphere ? getHumanById(selectedEventSphere.userData.modelUuid) : null;
const actionType = selectedHuman?.point?.action?.actionType || null;
const isAssembly = actionType === 'assembly';
const updateBackend = (
productName: string,
productUuid: string,
@@ -69,21 +78,42 @@ function HumanUi() {
rotation: selectedHuman.rotation,
});
if (outerGroup.current) {
outerGroup.current.position.set(
selectedHuman.position[0],
selectedHuman.position[1],
selectedHuman.position[2]
);
}
const action = selectedHuman.point.action;
if (action.pickUpPoint?.position && outerGroup.current) {
const worldPos = new Vector3(...action.pickUpPoint.position);
const localPosition = outerGroup.current.worldToLocal(worldPos.clone());
setStartPosition([localPosition.x, 0.5, localPosition.z]);
setStartPosition([localPosition.x, 1, localPosition.z]);
setStartRotation(action.pickUpPoint.rotation || [0, 0, 0]);
} else {
setStartPosition([0, 1, 0]);
setStartRotation([0, Math.PI, 0]);
}
if (action.dropPoint?.position && outerGroup.current) {
const worldPos = new Vector3(...action.dropPoint.position);
const localPosition = outerGroup.current.worldToLocal(worldPos.clone());
setEndPosition([localPosition.x, 0.5, localPosition.z]);
setEndRotation(action.dropPoint.rotation || [0, 0, 0]);
setEndPosition([localPosition.x, 1, localPosition.z]);
setEndRotation(action.dropPoint.rotation || [0, Math.PI, 0]);
} else {
setEndPosition([0, 1, 0]);
setEndRotation([0, 0, 0]);
}
if (action.assemblyPoint?.rotation) {
setAssemblyRotation(action.assemblyPoint.rotation);
} else {
setAssemblyRotation([0, 0, 0]);
}
}, [selectedEventSphere, outerGroup.current, selectedAction, humans]);
const handlePointerDown = (
@@ -91,84 +121,117 @@ function HumanUi() {
state: "start" | "end",
rotation: "start" | "end"
) => {
if (e.object.name === "handle") {
if (isAssembly) return;
e.stopPropagation();
const intersection = new Vector3();
const pointer = new Vector2((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1);
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.ray.intersectPlane(plane.current, intersection);
if (e.object.parent.name === "handle") {
const normalizedX = (e.clientX / window.innerWidth) * 2 - 1;
const normalizedY = -(e.clientY / window.innerHeight) * 2 + 1;
prevMousePos.current = { x: normalizedX, y: normalizedY };
setIsRotating(rotation);
if (controls) (controls as any).enabled = false;
setIsDragging(null);
} else {
setIsDragging(state);
setIsRotating(null);
if (controls) (controls as any).enabled = false;
}
if (intersects) {
let localPoint: Vector3 | null = null;
if (outerGroup.current) {
localPoint = outerGroup.current.worldToLocal(intersection.clone());
}
const marker = state === "start" ? startMarker.current : endMarker.current;
if (marker && localPoint) {
const markerPos = new Vector3().copy(marker.position);
dragOffset.current.copy(markerPos.sub(localPoint));
}
}
if (controls) (controls as any).enabled = false;
};
const handlePointerUp = () => {
(controls as any).enabled = true;
setIsDragging(null);
setIsRotating(null);
if (selectedEventSphere?.userData.modelUuid && selectedAction.actionId) {
const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid);
if (!selectedEventSphere?.userData.modelUuid || !selectedAction?.actionId) return;
if (selectedHuman && outerGroup.current && startMarker.current && endMarker.current) {
const worldPosStart = new Vector3(...startPosition);
const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone());
const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid);
if (!selectedHuman || !outerGroup.current) return;
const worldPosEnd = new Vector3(...endPosition);
const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone());
const isAssembly = selectedHuman.point?.action?.actionType === 'assembly';
const updatedAction = {
...selectedHuman.point.action,
pickUpPoint: {
position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number],
rotation: startRotation
},
dropPoint: {
position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number],
rotation: endRotation
}
};
let updatedAction;
const event = updateEvent(
selectedProduct.productUuid,
selectedEventSphere.userData.modelUuid,
{
...selectedHuman,
point: {
...selectedHuman.point,
action: updatedAction
}
}
);
if (isAssembly) {
updatedAction = {
...selectedHuman.point.action,
assemblyPoint: {
rotation: assemblyRotation
},
};
} else {
if (!startMarker.current || !endMarker.current) return;
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
}
const worldPosStart = new Vector3(...startPosition);
const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone());
const worldPosEnd = new Vector3(...endPosition);
const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone());
updatedAction = {
...selectedHuman.point.action,
pickUpPoint: {
position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number],
rotation: startRotation,
},
dropPoint: {
position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number],
rotation: endRotation,
},
};
}
const event = updateEvent(
selectedProduct.productUuid,
selectedEventSphere.userData.modelUuid,
{
...selectedHuman,
point: {
...selectedHuman.point,
action: updatedAction,
},
}
);
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
}
};
useFrame(() => {
if (!isDragging || !plane.current || !raycaster || !outerGroup.current) return;
if (isAssembly || !isDragging || !plane.current || !raycaster || !outerGroup.current) return;
const intersectPoint = new Vector3();
const intersects = raycaster.ray.intersectPlane(
plane.current,
intersectPoint
);
const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint);
if (!intersects) return;
const localPoint = outerGroup?.current.worldToLocal(intersectPoint.clone());
const localPoint = outerGroup.current.worldToLocal(intersectPoint.clone()).add(dragOffset.current);
if (isDragging === "start") {
setStartPosition([localPoint.x, 0.5, localPoint.z]);
setStartPosition([localPoint.x, 1, localPoint.z]);
} else if (isDragging === "end") {
setEndPosition([localPoint.x, 0.5, localPoint.z]);
setEndPosition([localPoint.x, 1, localPoint.z]);
}
});
@@ -177,11 +240,17 @@ function HumanUi() {
const currentPointerX = state.pointer.x;
const deltaX = currentPointerX - prevMousePos.current.x;
prevMousePos.current.x = currentPointerX;
const marker =isRotating === "start" ? startMarker.current : endMarker.current;
const marker = isRotating === "start" ? isAssembly ? assemblyMarker.current : startMarker.current : isAssembly ? assemblyMarker.current : endMarker.current;
if (marker) {
const rotationSpeed = 10;
marker.rotation.y += deltaX * rotationSpeed;
if (isRotating === "start") {
if (isAssembly && isRotating === "start") {
setAssemblyRotation([
marker.rotation.x,
marker.rotation.y,
marker.rotation.z,
]);
} else if (isRotating === "start") {
setStartRotation([
marker.rotation.x,
marker.rotation.y,
@@ -212,7 +281,7 @@ function HumanUi() {
return () => {
window.removeEventListener("pointerup", handleGlobalPointerUp);
};
}, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation]);
}, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation, assemblyRotation]);
return (
<>
@@ -220,44 +289,71 @@ function HumanUi() {
<group
position={selectedHumanData.position}
ref={outerGroup}
rotation={[0, Math.PI, 0]}
>
<primitive
name="startMarker"
object={startScene}
ref={startMarker}
position={startPosition}
rotation={startRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "start", "start");
}}
onPointerMissed={() => {
(controls as any).enabled = true;
setIsDragging(null);
setIsRotating(null);
}}
/>
{isAssembly ? (
<primitive
ref={assemblyMarker}
object={assemblyScene}
position={[0, 1, 0]}
rotation={assemblyRotation}
onPointerDown={(e: any) => {
if (e.object.parent.name === "handle") {
e.stopPropagation();
const normalizedX = (e.clientX / window.innerWidth) * 2 - 1;
prevMousePos.current.x = normalizedX;
setIsRotating("start");
setIsDragging(null);
if (controls) (controls as any).enabled = false;
}
}}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/>
) : (
<>
<primitive
name="startMarker"
object={startScene}
ref={startMarker}
position={startPosition}
rotation={startRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "start", "start");
}}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/>
<primitive
name="endMarker"
object={endScene}
ref={endMarker}
position={endPosition}
rotation={endRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "end", "end");
}}
onPointerMissed={() => {
(controls as any).enabled = true;
setIsDragging(null);
setIsRotating(null);
}}
/>
<primitive
name="endMarker"
object={endScene}
ref={endMarker}
position={endPosition}
rotation={endRotation}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "end", "end");
}}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/>
</>
)}
</group>
)}
</>
)
);
}
export default HumanUi

View File

@@ -301,7 +301,6 @@ const VehicleUI = () => {
return selectedVehicleData ? (
<group
position={selectedVehicleData.position}
rotation={selectedVehicleData.rotation}
ref={outerGroup}
>
<group

View File

@@ -299,6 +299,10 @@ export function useTriggerHandler() {
const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid);
const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid);
if (human && human.modelUuid === "cc62adae-7000-447b-b845-6d4910de503a") {
console.log(human);
}
setPreviousLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
@@ -330,7 +334,9 @@ export function useTriggerHandler() {
if (vehicle) {
if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -341,7 +347,9 @@ export function useTriggerHandler() {
addVehicleToMonitor(vehicle.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -354,7 +362,9 @@ export function useTriggerHandler() {
if (vehicle) {
if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -365,7 +375,9 @@ export function useTriggerHandler() {
addVehicleToMonitor(vehicle.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -382,7 +394,9 @@ export function useTriggerHandler() {
if (conveyor) {
if (!conveyor.isPaused) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -393,7 +407,9 @@ export function useTriggerHandler() {
addConveyorToMonitor(conveyor.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -406,7 +422,9 @@ export function useTriggerHandler() {
if (conveyor) {
if (!conveyor.isPaused) {
// Handle current action from vehicle
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
setIsPaused(materialId, true);
handleAction(action, materialId);
@@ -417,7 +435,9 @@ export function useTriggerHandler() {
addConveyorToMonitor(conveyor.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -434,7 +454,9 @@ export function useTriggerHandler() {
if (machine) {
if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) {
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -443,7 +465,9 @@ export function useTriggerHandler() {
addMachineToMonitor(machine.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -456,7 +480,9 @@ export function useTriggerHandler() {
if (machine) {
if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) {
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -465,7 +491,9 @@ export function useTriggerHandler() {
addMachineToMonitor(machine.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
}
)
@@ -480,7 +508,9 @@ export function useTriggerHandler() {
// Handle current action from arm bot
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -489,7 +519,9 @@ export function useTriggerHandler() {
setIsPaused(materialId, true);
addHumanToMonitor(human.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId)
}
);
@@ -501,7 +533,9 @@ export function useTriggerHandler() {
// Handle current action from arm bot
setIsPaused(materialId, true);
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId);
} else {
@@ -510,7 +544,9 @@ export function useTriggerHandler() {
setIsPaused(materialId, true);
addHumanToMonitor(human.modelUuid,
() => {
setIsVisible(materialId, false);
if (action.actionType === 'worker') {
setIsVisible(materialId, false);
}
handleAction(action, materialId)
}
);
@@ -1280,7 +1316,7 @@ export function useTriggerHandler() {
if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
const material = getMaterialById(materialId);
if (material) {
if (material && action.actionType === 'worker') {
setPreviousLocation(material.materialId, {
modelUuid: material.current.modelUuid,
@@ -1410,6 +1446,28 @@ export function useTriggerHandler() {
handleAction(action, material.materialId);
}
} else if (material && action.actionType === 'assembly') {
setPreviousLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
})
setCurrentLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
});
setNextLocation(material.materialId, {
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid,
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid,
})
setIsPaused(material.materialId, false);
setIsVisible(material.materialId, true);
}
}

View File

@@ -6,40 +6,53 @@ import { MaterialModel } from '../../../materials/instances/material/materialMod
const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => {
const meshRef = useRef<any>(null!);
const [hasLoad, setHasLoad] = useState(false);
const [isAttached, setIsAttached] = useState(false);
const { scene } = useThree();
const offset = new THREE.Vector3(0, 0.85, 0);
useEffect(() => {
setHasLoad(agvDetail.currentLoad > 0);
const loadState = agvDetail.currentLoad > 0;
setHasLoad(loadState);
if (!loadState) {
setIsAttached(false);
if (meshRef.current?.parent) {
meshRef.current.parent.remove(meshRef.current);
}
}
}, [agvDetail.currentLoad]);
useFrame(() => {
if (!hasLoad || !meshRef.current) return;
if (!hasLoad || !meshRef.current || isAttached) return;
const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D;
if (agvModel) {
const worldPosition = offset.clone().applyMatrix4(agvModel.matrixWorld);
meshRef.current.position.copy(worldPosition);
meshRef.current.rotation.copy(agvModel.rotation);
if (agvModel && !isAttached) {
if (meshRef.current.parent) {
meshRef.current.parent.remove(meshRef.current);
}
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);
}
});
return (
<>
{hasLoad && (
<>
{agvDetail.currentMaterials.length > 0 &&
<MaterialModel
matRef={meshRef}
materialId={agvDetail.currentMaterials[0].materialId || ''}
materialType={agvDetail.currentMaterials[0].materialType || 'Default material'}
/>
}
</>
{hasLoad && agvDetail.currentMaterials.length > 0 && (
<MaterialModel
matRef={meshRef}
materialId={agvDetail.currentMaterials[0].materialId || ''}
materialType={agvDetail.currentMaterials[0].materialType || 'Default material'}
visible={isAttached}
/>
)}
</>
);
};
export default MaterialAnimator;
export default MaterialAnimator;

View File

@@ -87,7 +87,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
const distances = [];
let accumulatedDistance = 0;
let index = 0;
const rotationSpeed = 1;
const rotationSpeed = 0.75;
for (let i = 0; i < currentPath.length - 1; i++) {
const start = new THREE.Vector3(...currentPath[i]);
@@ -107,17 +107,25 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
const end = new THREE.Vector3(...currentPath[index + 1]);
const segmentDistance = distances[index];
const currentDirection = new THREE.Vector3().subVectors(end, start).normalize();
const targetAngle = Math.atan2(currentDirection.x, currentDirection.z);
const currentAngle = object.rotation.y;
const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0)));
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
targetQuaternion.multiply(y180);
let angleDifference = targetAngle - currentAngle;
if (angleDifference > Math.PI) angleDifference -= 2 * Math.PI;
if (angleDifference < -Math.PI) angleDifference += 2 * Math.PI;
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < 0.01) {
object.quaternion.copy(targetQuaternion);
} else {
const step = rotationSpeed * delta * speed * agvDetail.speed;
const angle = object.quaternion.angleTo(targetQuaternion);
const maxRotationStep = (rotationSpeed * speed * agvDetail.speed) * delta;
object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep);
const isAligned = Math.abs(angleDifference) < 0.01;
if (angle < step) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
}
const isAligned = angle < 0.01;
if (isAligned) {
progressRef.current += delta * (speed * agvDetail.speed);
@@ -129,17 +137,25 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
if (progressRef.current >= totalDistance) {
if (restRotation && objectRotation) {
const targetEuler = new THREE.Euler(
objectRotation.x,
objectRotation.y - agvDetail.point.action.steeringAngle,
objectRotation.z
);
const targetQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
object.quaternion.slerp(targetQuaternion, delta * (rotationSpeed * speed * agvDetail.speed));
if (object.quaternion.angleTo(targetQuaternion) < 0.01) {
const targetEuler = new THREE.Euler(0, objectRotation.y - agvDetail.point.action.steeringAngle, 0);
const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
const targetQuaternion = baseQuaternion.multiply(y180);
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < 0.01) {
object.quaternion.copy(targetQuaternion);
object.rotation.copy(targetEuler);
setRestingRotation(false);
} else {
const step = rotationSpeed * delta * speed * agvDetail.speed;
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < step) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
}
return;
}