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

This commit is contained in:
2025-08-05 13:12:11 +05:30
15 changed files with 569 additions and 245 deletions

View File

@@ -3,8 +3,15 @@ import { useMemo } from "react";
import { Cylinder } from "@react-three/drei"; import { Cylinder } from "@react-three/drei";
export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => { export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => {
const { edgeCylinders, center, size } = useMemo(() => { const { edgeCylinders, cornerSpheres, center, size } = useMemo(() => {
if (!boundingBox) return { edgeCylinders: [], center: new Vector3(), size: new Vector3() }; if (!boundingBox) {
return {
edgeCylinders: [],
cornerSpheres: [],
center: new Vector3(),
size: new Vector3(),
};
}
const min = boundingBox.min; const min = boundingBox.min;
const max = boundingBox.max; const max = boundingBox.max;
@@ -50,7 +57,13 @@ export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { nam
}; };
}); });
return { edgeCylinders, center, size }; const cornerSpheres = corners.map((corner, i) => ({
key: `corner-sphere-${i}`,
position: corner.clone(),
radius: radius * 1.5,
}));
return { edgeCylinders, cornerSpheres, center, size };
}, [boundingBox, lineWidth]); }, [boundingBox, lineWidth]);
if (!boundingBox) return null; if (!boundingBox) return null;
@@ -58,11 +71,18 @@ export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { nam
return ( return (
<group name={name}> <group name={name}>
{edgeCylinders.map(({ key, position, rotation, length, radius }) => ( {edgeCylinders.map(({ key, position, rotation, length, radius }) => (
<Cylinder key={key} args={[radius, radius, length, 6]} position={position} quaternion={rotation} > <Cylinder key={key} args={[radius, radius, length, 6]} position={position} quaternion={rotation}>
<meshBasicMaterial color={color} depthWrite={false} /> <meshBasicMaterial color={color} depthWrite={false} />
</Cylinder> </Cylinder>
))} ))}
{cornerSpheres.map(({ key, position, radius }) => (
<mesh key={key} position={position}>
<sphereGeometry args={[radius, 12, 12]} />
<meshBasicMaterial color={color} depthWrite={false} />
</mesh>
))}
<mesh visible={false} position={center}> <mesh visible={false} position={center}>
<boxGeometry args={[size.x, size.y, size.z]} /> <boxGeometry args={[size.x, size.y, size.z]} />
</mesh> </mesh>

View File

@@ -449,7 +449,11 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
</> </>
) : ( ) : (
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={2.5} /> <>
{!isSelected &&
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={2.5} />
}
</>
)} )}
{isSelected && {isSelected &&
<AssetBoundingBox name='Asset BBox' boundingBox={boundingBox} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} /> <AssetBoundingBox name='Asset BBox' boundingBox={boundingBox} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} />

View File

@@ -6,12 +6,13 @@ import { useProductContext } from "../../../products/productContext";
import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager"; import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager";
export function useRetrieveHandler() { export function useRetrieveHandler() {
const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext(); const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, conveyorStore, productStore, humanStore, assetStore } = useSceneContext();
const { selectedProductStore } = useProductContext(); const { selectedProductStore } = useProductContext();
const { addMaterial } = materialStore(); const { addMaterial } = materialStore();
const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore(); const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore();
const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore(); const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore();
const { getVehicleById, incrementVehicleLoad, addCurrentMaterial } = vehicleStore(); const { getVehicleById, incrementVehicleLoad, addCurrentMaterial } = vehicleStore();
const { getConveyorById } = conveyorStore();
const { getHumanById, incrementHumanLoad, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore(); const { getHumanById, incrementHumanLoad, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore();
const { getAssetById, setCurrentAnimation } = assetStore(); const { getAssetById, setCurrentAnimation } = assetStore();
const { selectedProduct } = selectedProductStore(); const { selectedProduct } = selectedProductStore();
@@ -60,9 +61,9 @@ export function useRetrieveHandler() {
actionUuid: action.actionUuid actionUuid: action.actionUuid
}, },
current: { current: {
modelUuid: action.triggers[0]?.triggeredAsset.triggeredModel.modelUuid, modelUuid: modelUuid,
pointUuid: action.triggers[0]?.triggeredAsset.triggeredPoint.pointUuid, pointUuid: pointUuid,
actionUuid: action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid actionUuid: action.actionUuid
}, },
}; };
@@ -371,6 +372,32 @@ export function useRetrieveHandler() {
} }
return; return;
} }
} else if (triggeredModel?.type === 'transfer') {
const model = getConveyorById(triggeredModel.modelUuid);
if (model && !model.isPaused) {
if (humanAsset?.animationState?.current === 'idle') {
setCurrentAnimation(human.modelUuid, 'pickup', true, false, false);
} else if (humanAsset?.animationState?.current === 'pickup' && humanAsset.animationState.isCompleted) {
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);
incrementHumanLoad(human.modelUuid, 1);
addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId);
retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`);
retrievalCountRef.current.set(actionUuid, currentCount + 1);
}
}
}
return;
}
} else if (triggeredModel?.type === 'machine') { } else if (triggeredModel?.type === 'machine') {
const machine = getMachineById(triggeredModel.modelUuid); const machine = getMachineById(triggeredModel.modelUuid);
if (machine && !machine.isActive && machine.state === 'idle' && !machine.currentAction) { if (machine && !machine.isActive && machine.state === 'idle' && !machine.currentAction) {

View File

@@ -182,31 +182,23 @@ function TriggerConnector() {
const canvasElement = gl.domElement; const canvasElement = gl.domElement;
let drag = false; let drag = false;
let isRightMouseDown = false; let isLeftMouseDown = false;
const onMouseDown = (evt: MouseEvent) => { const onMouseDown = (evt: MouseEvent) => {
if (selectedAsset) { if (selectedAsset) {
clearSelectedAsset(); clearSelectedAsset();
} }
if (evt.button === 2) { if (evt.button === 0) {
isRightMouseDown = true; isLeftMouseDown = true;
drag = false; drag = false;
} }
}; };
const onMouseUp = (evt: MouseEvent) => { const onMouseUp = (evt: MouseEvent) => {
if (evt.button === 2) { if (evt.button === 0) {
isRightMouseDown = false; isLeftMouseDown = false;
} }
}
const onMouseMove = () => {
if (isRightMouseDown) {
drag = true;
}
};
const handleRightClick = (evt: MouseEvent) => {
if (drag) return; if (drag) return;
evt.preventDefault(); evt.preventDefault();
@@ -368,13 +360,16 @@ function TriggerConnector() {
} else if (firstSelectedPoint) { } else if (firstSelectedPoint) {
setFirstSelectedPoint(null); setFirstSelectedPoint(null);
} }
}
const onMouseMove = () => {
drag = true;
}; };
if (subModule === 'mechanics' && toolMode === 'cursor' && selectedAction.actionId && selectedAction.actionName) { if (subModule === 'mechanics' && toolMode === 'cursor' && selectedAction.actionId && selectedAction.actionName) {
canvasElement.addEventListener("mousedown", onMouseDown); canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp); canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove); canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener('contextmenu', handleRightClick);
} else { } else {
setFirstSelectedPoint(null); setFirstSelectedPoint(null);
} }
@@ -383,7 +378,6 @@ function TriggerConnector() {
canvasElement.removeEventListener("mousedown", onMouseDown); canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp); canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove); canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener('contextmenu', handleRightClick);
}; };
}, [gl, subModule, selectedProduct, firstSelectedPoint, toolMode, selectedAction]); }, [gl, subModule, selectedProduct, firstSelectedPoint, toolMode, selectedAction]);

View File

@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useGLTF } from '@react-three/drei'; import * as THREE from 'three'
import { Tube, useGLTF } from '@react-three/drei';
import { useFrame, useThree } from '@react-three/fiber'; import { useFrame, useThree } from '@react-three/fiber';
import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore'; import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore';
import { useProductContext } from '../../../products/productContext'; import { useProductContext } from '../../../products/productContext';
@@ -27,7 +28,7 @@ function HumanUi() {
const { humanStore, productStore } = useSceneContext(); const { humanStore, productStore } = useSceneContext();
const { selectedProduct } = selectedProductStore(); const { selectedProduct } = selectedProductStore();
const { humans, getHumanById } = humanStore(); const { humans, getHumanById } = humanStore();
const { updateEvent, updateAction, getActionByUuid } = productStore(); const { updateEvent, getActionByUuid } = productStore();
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]); const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]);
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]); const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]);
const [assemblyPosition, setAssemblyPosition] = useState<[number, number, number]>([0, 1, 0]); const [assemblyPosition, setAssemblyPosition] = useState<[number, number, number]>([0, 1, 0]);
@@ -313,64 +314,49 @@ function HumanUi() {
rotation={[0, Math.PI, 0]} rotation={[0, Math.PI, 0]}
> >
{isAssembly ? ( {isAssembly ? (
<primitive <MarkerPrimitive
ref={assemblyMarker} name="assemblyMarker"
refProp={assemblyMarker}
object={assemblyScene} object={assemblyScene}
position={assemblyPosition} position={assemblyPosition}
rotation={assemblyRotation} rotation={assemblyRotation}
onPointerDown={(e: any) => { outerGroupRef={outerGroup}
if (e.object.parent.name === "handle") { type="assembly"
handlePointerDown(e, "assembly", "assembly"); subtype="assembly"
} else { color="#0f87f7"
handlePointerDown(e, "assembly", "assembly"); setIsDragging={setIsDragging}
} setIsRotating={setIsRotating}
}} handlePointerDown={handlePointerDown}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/> />
) : ( ) : (
<> <>
<primitive <MarkerPrimitive
name="startMarker" name="startMarker"
refProp={startMarker}
object={startScene} object={startScene}
ref={startMarker}
position={startPosition} position={startPosition}
rotation={startRotation} rotation={startRotation}
onPointerDown={(e: any) => { outerGroupRef={outerGroup}
if (e.object.parent.name === "handle") { type="start"
handlePointerDown(e, "start", "start"); subtype="start"
} else { color="lightgreen"
handlePointerDown(e, "start", "start"); setIsDragging={setIsDragging}
} setIsRotating={setIsRotating}
}} handlePointerDown={handlePointerDown}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/> />
<MarkerPrimitive
<primitive
name="endMarker" name="endMarker"
refProp={endMarker}
object={endScene} object={endScene}
ref={endMarker}
position={endPosition} position={endPosition}
rotation={endRotation} rotation={endRotation}
onPointerDown={(e: any) => { outerGroupRef={outerGroup}
if (e.object.parent.name === "handle") { type="end"
handlePointerDown(e, "end", "end"); subtype="end"
} else { color="darkorange"
handlePointerDown(e, "end", "end"); setIsDragging={setIsDragging}
} setIsRotating={setIsRotating}
}} handlePointerDown={handlePointerDown}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/> />
</> </>
)} )}
@@ -380,4 +366,101 @@ function HumanUi() {
); );
} }
export default HumanUi; export default HumanUi;
const MarkerPrimitive = ({
name,
refProp,
object,
position,
rotation,
outerGroupRef,
type,
subtype,
color,
setIsDragging,
setIsRotating,
handlePointerDown,
}: {
name: string;
refProp: any;
object: THREE.Object3D;
position: [number, number, number];
rotation: [number, number, number];
outerGroupRef: React.RefObject<THREE.Group>;
type: string;
subtype: string;
color: string;
setIsDragging: (val: any) => void;
setIsRotating: (val: any) => void;
handlePointerDown: any;
}) => {
const { controls, scene, camera } = useThree();
const [hitPoint, setHitPoint] = useState<THREE.Vector3 | null>(null);
const [curve, setCurve] = useState<THREE.CatmullRomCurve3 | null>(null);
useFrame(() => {
if (!refProp.current || !outerGroupRef.current || !scene) return;
const worldPos = new THREE.Vector3();
refProp.current.getWorldPosition(worldPos);
const localMarkerPos = outerGroupRef.current.worldToLocal(worldPos.clone());
const rayOrigin = worldPos.clone();
const direction = new THREE.Vector3(0, -1, 0);
const raycaster = new THREE.Raycaster(rayOrigin, direction, 0.1, 1000);
raycaster.camera = camera;
const intersects = raycaster.intersectObjects(scene.children, true);
const hit = intersects.find(i => i.object.name !== name);
if (hit) {
const localHit = outerGroupRef.current.worldToLocal(hit.point.clone());
setHitPoint(localHit);
const newCurve = new THREE.CatmullRomCurve3([
localMarkerPos.clone(),
localHit.clone(),
]);
setCurve(newCurve);
} else {
setHitPoint(null);
setCurve(null);
}
});
return (
<>
<primitive
name={name}
ref={refProp}
object={object}
position={position}
rotation={rotation}
onPointerDown={(e: any) => {
handlePointerDown(e, type, subtype);
}}
onPointerMissed={() => {
setIsDragging(null);
setIsRotating(null);
if (controls) (controls as any).enabled = true;
}}
/>
{hitPoint && (
<>
<mesh name={name} position={hitPoint} rotation={[-Math.PI / 2, 0, 0]}>
<torusGeometry args={[0.15, 0.02, 3, 32]} />
<meshBasicMaterial color={color} depthWrite={false} />
</mesh>
{curve && (
<Tube args={[curve, 20, 0.01, 8, true]} >
<meshBasicMaterial color={color} depthWrite={false} />
</Tube>
)}
</>
)}
</>
);
};

View File

@@ -61,30 +61,12 @@ function MaterialInstance({ material }: { readonly material: MaterialSchema }) {
function getCurrentSpeed(productUuid: string, modelUuid: string) { function getCurrentSpeed(productUuid: string, modelUuid: string) {
const event = getEventByModelUuid(productUuid, modelUuid) const event = getEventByModelUuid(productUuid, modelUuid)
if (event) { if (event) {
if (event.type === 'transfer') { if (event.type === 'transfer' || event.type === 'machine' || event.type === 'storageUnit') {
return event.speed;
}
if (event.type === 'vehicle') {
return event.speed;
}
if (event.type === 'machine') {
return 1; return 1;
} }
if (event.type === 'vehicle' || event.type === 'roboticArm' || event.type === 'human') {
if (event.type === 'roboticArm') {
return event.speed; return event.speed;
} }
if (event.type === 'storageUnit') {
return 1;
}
if (event.type === 'human') {
return event.speed;
}
} else { } else {
return 1; return 1;
} }

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react' import { useEffect } from 'react'
import MaterialInstance from './instance/materialInstance' import MaterialInstance from './instance/materialInstance'
import { useSceneContext } from '../../../scene/sceneContext'; import { useSceneContext } from '../../../scene/sceneContext';

View File

@@ -33,8 +33,8 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone
const [customCurvePoints, setCustomCurvePoints] = useState<THREE.Vector3[] | null>(null); const [customCurvePoints, setCustomCurvePoints] = useState<THREE.Vector3[] | null>(null);
const { armBotStore, productStore, materialStore } = useSceneContext(); const { armBotStore, productStore, materialStore } = useSceneContext();
const { getArmBotById } = armBotStore(); const { getArmBotById } = armBotStore();
const { getMaterialById } = materialStore(); const { getMaterialById, getMaterialPosition } = materialStore();
const { getEventByModelUuid } = productStore(); const { getEventByModelUuid, getActionByUuid, getPointByUuid } = productStore();
const { selectedProductStore } = useProductContext(); const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore(); const { selectedProduct } = selectedProductStore();
const { scene } = useThree(); const { scene } = useThree();
@@ -167,13 +167,18 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone
let start = currentPath[0]; let start = currentPath[0];
let end = currentPath[currentPath.length - 1]; let end = currentPath[currentPath.length - 1];
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
const armbotStatus = getArmBotById(armBot.modelUuid); const armbotStatus = getArmBotById(armBot.modelUuid);
const currentMaterial = armbotStatus?.currentAction?.materialId; const currentMaterial = armbotStatus?.currentAction?.materialId;
if (armbotStatus && currentMaterial && (currentPhase === 'rest-to-start' || currentPhase === 'start-to-end')) { const currentAction = getActionByUuid(selectedProduct.productUuid, armbotStatus?.currentAction?.actionUuid || '');
if (armbotStatus && currentMaterial && currentAction && (currentPhase === 'rest-to-start' || currentPhase === 'start-to-end' || currentPhase === 'end-to-rest')) {
const materialData = getMaterialById(currentMaterial); const materialData = getMaterialById(currentMaterial);
if (materialData) { if (materialData) {
const prevModel = getEventByModelUuid(selectedProduct.productUuid, materialData.current.modelUuid); const prevModel = getEventByModelUuid(selectedProduct.productUuid, materialData.current.modelUuid);
const nextModel = getEventByModelUuid(selectedProduct.productUuid, currentAction?.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid || '');
const nextPoint = getPointByUuid(selectedProduct.productUuid, currentAction?.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid || '', currentAction?.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '');
if (prevModel && prevModel.type === 'transfer') { if (prevModel && prevModel.type === 'transfer') {
const material = scene.getObjectByProperty("uuid", currentMaterial); const material = scene.getObjectByProperty("uuid", currentMaterial);
@@ -194,7 +199,69 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone
start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z]; start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
} }
} }
} else if (prevModel && prevModel.type === 'storageUnit') {
const position = getMaterialPosition(prevModel.modelUuid, currentMaterial);
const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid);
if (armbotModel) {
const armbotWorldPos = new THREE.Vector3();
let materialWorldPos = new THREE.Vector3();
if (position) {
materialWorldPos.copy(position);
} else {
materialWorldPos.copy(bone.getWorldPosition(armbotWorldPos));
}
const materialLocalPos = materialWorldPos.clone();
armbotModel.worldToLocal(materialLocalPos);
if (currentPhase === 'rest-to-start') {
end = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
} else if (currentPhase === 'start-to-end') {
start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
} else if (currentPhase === 'end-to-rest') {
start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
}
}
} }
if (nextModel && nextPoint && nextModel.type === 'transfer') {
const conveyorModel = scene.getObjectByProperty("uuid", nextModel.modelUuid);
const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid);
if (conveyorModel && armbotModel) {
const localPoint = new THREE.Vector3(
nextPoint.position[0],
nextPoint.position[1],
nextPoint.position[2]
);
const worldPoint = conveyorModel.localToWorld(localPoint);
armbotModel.worldToLocal(worldPoint);
if (currentPhase === 'start-to-end') {
end = [worldPoint.x, worldPoint.y + 0.35, worldPoint.z];
}
}
}
}
}
if (currentPhase === 'end-to-rest') {
console.log('currentPhase: ', currentPhase);
const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid);
const armbotWorldPos = new THREE.Vector3();
if (armbotModel) {
let materialWorldPos = new THREE.Vector3();
materialWorldPos.copy(bone.getWorldPosition(armbotWorldPos));
const materialLocalPos = materialWorldPos.clone();
armbotModel.worldToLocal(materialLocalPos);
start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
} }
} }

View File

@@ -173,13 +173,16 @@ const ArmBotUI = () => {
const targetMesh = scene?.getObjectByProperty("uuid", selectedArmBotData?.modelUuid || ''); const targetMesh = scene?.getObjectByProperty("uuid", selectedArmBotData?.modelUuid || '');
const iks = targetMesh?.userData?.iks;
const firstIK = Array.isArray(iks) && iks.length > 0 ? iks[0] : {};
const { handlePointerDown } = useDraggableGLTF( const { handlePointerDown } = useDraggableGLTF(
updatePointToState, updatePointToState,
{ {
minDistance: targetMesh?.userData?.iks[0]?.minDistance || 1.2, minDistance: firstIK.minDistance ?? 1.2,
maxDistance: targetMesh?.userData?.iks[0]?.maxDistance || 2, maxDistance: firstIK.maxDistance ?? 2,
maxheight: targetMesh?.userData?.iks[0]?.maxheight || 0.6, maxheight: firstIK.maxheight ?? 0.6,
minheight: targetMesh?.userData?.iks[0]?.minheight || 1.9, minheight: firstIK.minheight ?? 1.9,
} }
); );

View File

@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import * as Types from "../../../../types/world/worldTypes"; import * as Types from "../../../../types/world/worldTypes";
import { useGLTF } from "@react-three/drei"; import { Tube, useGLTF } from "@react-three/drei";
import * as THREE from "three";
import { useFrame, useThree } from "@react-three/fiber"; import { useFrame, useThree } from "@react-three/fiber";
import { useSelectedEventSphere, useIsDragging, useIsRotating, } from "../../../../store/simulation/useSimulationStore"; import { useSelectedEventSphere, useIsDragging, useIsRotating, } from "../../../../store/simulation/useSimulationStore";
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi"; import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
@@ -16,8 +17,6 @@ import { useVersionContext } from "../../../builder/version/versionContext";
const VehicleUI = () => { const VehicleUI = () => {
const { scene: startScene } = useGLTF(startPoint) as any; const { scene: startScene } = useGLTF(startPoint) as any;
const { scene: endScene } = useGLTF(startEnd) as any; const { scene: endScene } = useGLTF(startEnd) as any;
const startMarker = useRef<Group>(null);
const endMarker = useRef<Group>(null);
const prevMousePos = useRef({ x: 0, y: 0 }); const prevMousePos = useRef({ x: 0, y: 0 });
const { selectedEventSphere } = useSelectedEventSphere(); const { selectedEventSphere } = useSelectedEventSphere();
const { selectedProductStore } = useProductContext(); const { selectedProductStore } = useProductContext();
@@ -162,7 +161,7 @@ const VehicleUI = () => {
let globalStartPosition = null; let globalStartPosition = null;
let globalEndPosition = null; let globalEndPosition = null;
if (outerGroup.current && startMarker.current && endMarker.current) { if (outerGroup.current) {
const worldPosStart = new Vector3(...startPosition); const worldPosStart = new Vector3(...startPosition);
globalStartPosition = outerGroup.current.localToWorld( globalStartPosition = outerGroup.current.localToWorld(
worldPosStart.clone() worldPosStart.clone()
@@ -277,24 +276,19 @@ const VehicleUI = () => {
const currentPointerX = state.pointer.x; const currentPointerX = state.pointer.x;
const deltaX = currentPointerX - prevMousePos.current.x; const deltaX = currentPointerX - prevMousePos.current.x;
prevMousePos.current.x = currentPointerX; prevMousePos.current.x = currentPointerX;
const marker = const rotationSpeed = 10;
isRotating === "start" ? startMarker.current : endMarker.current; if (isRotating === "start") {
if (marker) { setStartRotation([
const rotationSpeed = 10; startRotation[0],
marker.rotation.y += deltaX * rotationSpeed; startRotation[1] + deltaX * rotationSpeed,
if (isRotating === "start") { startRotation[2],
setStartRotation([ ]);
marker.rotation.x, } else {
marker.rotation.y, setEndRotation([
marker.rotation.z, endRotation[0],
]); endRotation[1] + deltaX * rotationSpeed,
} else { endRotation[2],
setEndRotation([ ]);
marker.rotation.x,
marker.rotation.y,
marker.rotation.z,
]);
}
} }
}); });
@@ -342,41 +336,130 @@ const VehicleUI = () => {
</group> </group>
{/* Start Marker */} {/* Start Marker */}
<primitive <VehicleMarkerPrimitive
name="startMarker" name="startMarker"
object={startScene} object={startScene}
ref={startMarker}
position={startPosition} position={startPosition}
rotation={startRotation} rotation={startRotation}
onPointerDown={(e: any) => { outerGroupRef={outerGroup}
e.stopPropagation(); color="green"
handlePointerDown(e, "start", "start"); handlePointerDown={handlePointerDown}
}} setIsDragging={setIsDragging}
setIsRotating={setIsRotating}
/>
{/* End Marker */}
<VehicleMarkerPrimitive
name="endMarker"
object={endScene}
position={endPosition}
rotation={endRotation}
outerGroupRef={outerGroup}
color="orange"
handlePointerDown={handlePointerDown}
setIsDragging={setIsDragging}
setIsRotating={setIsRotating}
/>
</group>
) : null;
};
export default VehicleUI;
export const VehicleMarkerPrimitive = ({
name,
object,
position,
rotation,
outerGroupRef,
color,
handlePointerDown,
setIsDragging,
setIsRotating,
}: {
name: string;
object: THREE.Object3D;
position: [number, number, number];
rotation: [number, number, number];
outerGroupRef: React.RefObject<THREE.Group>;
color: string;
handlePointerDown: (e: any, type: "start" | "end", rotation: "start" | "end") => void;
setIsDragging: (val: any) => void;
setIsRotating: (val: any) => void;
}) => {
const { scene, camera } = useThree();
const markerRef = useRef<THREE.Group>(null);
const [hitPoint, setHitPoint] = useState<THREE.Vector3 | null>(null);
const [curve, setCurve] = useState<THREE.CatmullRomCurve3 | null>(null);
useFrame(() => {
if (!markerRef.current || !outerGroupRef.current) return;
const worldPos = new THREE.Vector3();
markerRef.current.getWorldPosition(worldPos);
const localMarkerPos = outerGroupRef.current.worldToLocal(worldPos.clone());
const rayOrigin = worldPos.clone();
const direction = new THREE.Vector3(0, -1, 0);
const raycaster = new THREE.Raycaster(rayOrigin, direction, 0.1, 1000);
raycaster.camera = camera;
const intersects = raycaster.intersectObjects(scene.children, true);
const hit = intersects.find(i => i.object.name !== name);
if (hit) {
const localHit = outerGroupRef.current.worldToLocal(hit.point.clone());
setHitPoint(localHit);
const newCurve = new THREE.CatmullRomCurve3([
localMarkerPos.clone(),
localHit.clone(),
]);
setCurve(newCurve);
} else {
setHitPoint(null);
setCurve(null);
}
});
return (
<>
<primitive
name={name}
ref={markerRef}
object={object}
position={position}
rotation={rotation}
onPointerDown={(e: any) =>
handlePointerDown(
e,
name === "startMarker" ? "start" : "end",
name === "startMarker" ? "start" : "end"
)
}
onPointerMissed={() => { onPointerMissed={() => {
controls.enabled = true;
setIsDragging(null); setIsDragging(null);
setIsRotating(null); setIsRotating(null);
}} }}
/> />
{/* End Marker */} {hitPoint && (
<primitive <>
name="endMarker" <mesh
object={endScene} name={`${name}-torus`}
ref={endMarker} position={hitPoint}
position={endPosition} rotation={[-Math.PI / 2, 0, 0]}
rotation={endRotation} >
onPointerDown={(e: any) => { <torusGeometry args={[0.15, 0.02, 3, 32]} />
e.stopPropagation(); <meshBasicMaterial color={color} depthWrite={false} />
handlePointerDown(e, "end", "end"); </mesh>
}}
onPointerMissed={() => { {curve && (
controls.enabled = true; <Tube args={[curve, 20, 0.01, 8, true]}>
setIsDragging(null); <meshBasicMaterial color={color} depthWrite={false} />
setIsRotating(null); </Tube>
}} )}
/> </>
</group> )}
) : null; </>
}; );
export default VehicleUI; };

View File

@@ -1,72 +1,84 @@
import { useRef, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { MaterialModel } from "../../../materials/instances/material/materialModel";
import { Object3D, Box3, Vector3 } from "three"; import { Object3D, Box3, Vector3 } from "three";
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import { useLoadingProgress } from "../../../../../store/builder/store"; import { useLoadingProgress } from "../../../../../store/builder/store";
import { MaterialModel } from "../../../materials/instances/material/materialModel";
import { useSceneContext } from "../../../../scene/sceneContext";
const MaterialAnimator = ({ const MaterialAnimator = ({
storage, storage,
}: Readonly<{ storage: StorageUnitStatus }>) => { }: Readonly<{ storage: StorageUnitStatus }>) => {
const meshRef = useRef<any>(null!); const { scene } = useThree();
const { scene } = useThree(); const padding = 0.1;
const padding = 0.1; const { loadingProgress } = useLoadingProgress();
const { loadingProgress } = useLoadingProgress(); const { materialStore } = useSceneContext();
const { materialPositions, setMaterialPositions } = materialStore();
const storageModel = useMemo(() => { const storageModel = useMemo(() => {
return scene.getObjectByProperty("uuid", storage.modelUuid) as Object3D; return scene.getObjectByProperty("uuid", storage.modelUuid) as Object3D;
}, [scene, storage.modelUuid, loadingProgress]); }, [scene, storage.modelUuid, loadingProgress]);
const materialPositions = useMemo(() => { useEffect(() => {
if (!storageModel || storage.currentMaterials.length === 0) return []; if (!storageModel || storage.currentMaterials.length === 0) {
setMaterialPositions(storage.modelUuid, []);
return;
}
const box = new Box3().setFromObject(storageModel); const box = new Box3().setFromObject(storageModel);
const size = new Vector3(); const size = new Vector3();
box.getSize(size); box.getSize(size);
const matCount = storage.currentMaterials.length; const materialWidth = 0.45;
const materialDepth = 0.45;
const materialHeight = 0.3;
const cols = Math.floor(size.x / materialWidth);
const rows = Math.floor(size.z / materialDepth);
const itemsPerLayer = cols * rows;
// Assumed size each material needs in world units const origin = new Vector3(
const materialWidth = 0.45; box.min.x + materialWidth / 2,
const materialDepth = 0.45; box.max.y + padding,
const materialHeight = 0.3; box.min.z + materialDepth / 2
);
const cols = Math.floor(size.x / materialWidth); const newMaterials = storage.currentMaterials.map((mat, i) => {
const rows = Math.floor(size.z / materialDepth); const layer = Math.floor(i / itemsPerLayer);
const itemsPerLayer = cols * rows; const layerIndex = i % itemsPerLayer;
const row = Math.floor(layerIndex / cols);
const col = layerIndex % cols;
const origin = new Vector3( const position = new Vector3(
box.min.x + materialWidth / 2, origin.x + col * materialWidth,
box.max.y + padding, // slightly above the surface origin.y + layer * (materialHeight + padding),
box.min.z + materialDepth / 2 origin.z + row * materialDepth
);
return {
materialId: mat.materialId,
position,
};
});
setMaterialPositions(storage.modelUuid, newMaterials);
}, [storageModel, storage.currentMaterials]);
return (
<group position={[0, -padding, 0]}>
{(materialPositions[storage.modelUuid] || []).map(({ materialId, position }) => {
const mat = storage.currentMaterials.find((m) => m.materialId === materialId);
return (
<MaterialModel
key={materialId}
materialId={materialId}
matRef={null}
materialType={mat?.materialType ?? "Default material"}
position={position}
/>
);
})}
</group>
); );
return Array.from({ length: matCount }, (_, i) => {
const layer = Math.floor(i / itemsPerLayer);
const layerIndex = i % itemsPerLayer;
const row = Math.floor(layerIndex / cols);
const col = layerIndex % cols;
return new Vector3(
origin.x + col * materialWidth,
origin.y + layer * (materialHeight + padding),
origin.z + row * materialDepth
);
});
}, [storageModel, storage.currentMaterials]);
return (
<group {...{ position: [0, -padding, 0] }}>
{storage.currentMaterials.map((mat, index) => (
<MaterialModel
key={`${index}-${mat.materialId}`}
materialId={mat.materialId}
matRef={meshRef}
materialType={mat.materialType ?? "Default material"}
position={materialPositions[index]}
/>
))}
</group>
);
}; };
export default MaterialAnimator; export default MaterialAnimator;

View File

@@ -7,7 +7,6 @@ import { useViewSceneStore } from "../../../../store/builder/store";
function StorageUnitInstances() { function StorageUnitInstances() {
const { storageUnitStore } = useSceneContext(); const { storageUnitStore } = useSceneContext();
const { storageUnits } = storageUnitStore(); const { storageUnits } = storageUnitStore();
// console.log('storageUnits: ', storageUnits);
const { viewSceneLabels } = useViewSceneStore(); const { viewSceneLabels } = useViewSceneStore();
return ( return (

View File

@@ -1,4 +1,3 @@
import React from 'react'
import StorageUnitInstances from './instances/storageUnitInstances' import StorageUnitInstances from './instances/storageUnitInstances'
function StorageUnit() { function StorageUnit() {

View File

@@ -370,40 +370,18 @@ export function useTriggerHandler() {
} }
} }
} else { } else {
if (human.isActive === false && human.state === 'idle') {
// Handle current action from arm bot
setIsPaused(materialId, true);
handleAction(action, materialId);
} else {
// Handle current action using Event Manager
setHumanScheduled(human.modelUuid, true);
setIsPaused(materialId, true);
addHumanToMonitor(human.modelUuid, () => {
handleAction(action, materialId)
}, action.actionUuid);
}
}
} else {
if (human.isActive === false && human.state === 'idle') {
// Handle current action from arm bot
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true); setHumanScheduled(human.modelUuid, true);
handleAction(action, materialId);
} else {
// Handle current action using Event Manager
setIsPaused(materialId, true); setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
addHumanToMonitor(human.modelUuid, () => { addHumanToMonitor(human.modelUuid, () => {
handleAction(action, materialId) handleAction(action, materialId)
}, action.actionUuid); }, action.actionUuid);
} }
} else {
setHumanScheduled(human.modelUuid, true);
setIsPaused(materialId, true);
addHumanToMonitor(human.modelUuid, () => {
handleAction(action, materialId)
}, action.actionUuid);
} }
} }
} }
@@ -1260,12 +1238,6 @@ export function useTriggerHandler() {
actionUuid: material.current.actionUuid, actionUuid: material.current.actionUuid,
}) })
setCurrentLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
});
setIsPaused(material.materialId, true); setIsPaused(material.materialId, true);
setIsVisible(material.materialId, true); setIsVisible(material.materialId, true);
@@ -1279,6 +1251,13 @@ export function useTriggerHandler() {
if (model?.type === 'roboticArm') { if (model?.type === 'roboticArm') {
addArmBotToMonitor(model.modelUuid, () => { addArmBotToMonitor(model.modelUuid, () => {
setCurrentLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
});
setNextLocation(material.materialId, { setNextLocation(material.materialId, {
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
@@ -1288,6 +1267,13 @@ export function useTriggerHandler() {
}) })
} else if (model?.type === 'vehicle') { } else if (model?.type === 'vehicle') {
addVehicleToMonitor(model.modelUuid, () => { addVehicleToMonitor(model.modelUuid, () => {
setCurrentLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
});
setNextLocation(material.materialId, { setNextLocation(material.materialId, {
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
@@ -1296,6 +1282,13 @@ export function useTriggerHandler() {
setIsPaused(material.materialId, false); setIsPaused(material.materialId, false);
}) })
} else if (model?.type === 'transfer') { } else if (model?.type === 'transfer') {
setCurrentLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
});
setNextLocation(material.materialId, { setNextLocation(material.materialId, {
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
@@ -1303,15 +1296,48 @@ export function useTriggerHandler() {
setIsPaused(material.materialId, false); setIsPaused(material.materialId, false);
} else if (model?.type === 'human') { } else if (model?.type === 'human') {
addHumanToMonitor(model.modelUuid, () => { if (fromEvent.modelUuid === model.modelUuid) {
setNextLocation(material.materialId, {
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
})
setIsPaused(material.materialId, false); setIsPaused(material.materialId, false);
}, action.actionUuid)
setCurrentLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
});
setTimeout(() => {
setIsPaused(material.materialId, false);
setNextLocation(material.materialId, {
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
})
}, 0)
} else {
addHumanToMonitor(model.modelUuid, () => {
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);
}, action.actionUuid)
}
} else { } else {
setCurrentLocation(material.materialId, {
modelUuid: material.current.modelUuid,
pointUuid: material.current.pointUuid,
actionUuid: material.current.actionUuid,
});
setNextLocation(material.materialId, { setNextLocation(material.materialId, {
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',

View File

@@ -1,15 +1,29 @@
import * as THREE from 'three';
import { create } from 'zustand'; import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
interface MaterialPosition {
materialId: string;
position: THREE.Vector3;
}
type MaterialPositionsMap = Record<string, MaterialPosition[]>;
type MaterialsStore = { type MaterialsStore = {
materials: MaterialsSchema; materials: MaterialsSchema;
materialHistory: MaterialHistorySchema; materialHistory: MaterialHistorySchema;
materialPositions: MaterialPositionsMap;
addMaterial: (material: MaterialSchema) => MaterialSchema | undefined; addMaterial: (material: MaterialSchema) => MaterialSchema | undefined;
removeMaterial: (materialId: string) => MaterialSchema | undefined; removeMaterial: (materialId: string) => MaterialSchema | undefined;
clearMaterials: () => void; clearMaterials: () => void;
updateMaterial: (materialId: string, updates: Partial<MaterialSchema>) => MaterialSchema | undefined; updateMaterial: (materialId: string, updates: Partial<MaterialSchema>) => MaterialSchema | undefined;
setMaterialPositions: (modelUuid: string, materialPositions: MaterialPosition[]) => void;
getMaterialPosition: (modelUuid: string, materialId: string) => THREE.Vector3 | undefined;
setPreviousLocation: ( setPreviousLocation: (
materialId: string, materialId: string,
location: { location: {
@@ -61,6 +75,7 @@ export const createMaterialStore = () => {
immer((set, get) => ({ immer((set, get) => ({
materials: [], materials: [],
materialHistory: [], materialHistory: [],
materialPositions: {},
addMaterial: (material) => { addMaterial: (material) => {
let updatedMaterial: MaterialSchema | undefined; let updatedMaterial: MaterialSchema | undefined;
@@ -262,6 +277,16 @@ export const createMaterialStore = () => {
return updatedMaterial; return updatedMaterial;
}, },
setMaterialPositions: (modelUuid, positions) => {
set((state) => {
state.materialPositions[modelUuid] = positions;
});
},
getMaterialPosition: (modelUuid, materialId) => {
return get().materialPositions[modelUuid]?.find(p => p.materialId === materialId)?.position;
},
getMaterialById: (materialId) => { getMaterialById: (materialId) => {
return get().materials.find(m => m.materialId === materialId); return get().materials.find(m => m.materialId === materialId);
}, },