Merge branch 'main-dev' into dev-physics

This commit is contained in:
2025-07-30 12:52:03 +05:30
58 changed files with 4294 additions and 2132 deletions

View File

@@ -16,12 +16,7 @@ import { getUserData } from "../../../functions/getUserData";
import { useSceneContext } from "../../scene/sceneContext";
import { useVersionContext } from "../version/versionContext";
const gltfLoaderWorker = new Worker(
new URL(
"../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
import.meta.url
)
);
const gltfLoaderWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", import.meta.url));
function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
const { activeModule } = useModuleStore();
@@ -44,9 +39,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(
"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"
);
dracoLoader.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/");
loader.setDRACOLoader(dracoLoader);
useEffect(() => {
@@ -267,6 +260,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
actionName: "Action 1",
actionType: "worker",
loadCount: 1,
assemblyCount: 1,
loadCapacity: 1,
processTime: 10,
triggers: []

View File

@@ -384,6 +384,7 @@ async function handleModelLoad(
actionName: "Action 1",
actionType: "worker",
loadCount: 1,
assemblyCount: 1,
loadCapacity: 1,
processTime: 10,
triggers: []

View File

@@ -1,26 +1,71 @@
import { Box3, BoxGeometry, EdgesGeometry, Vector3 } from "three";
import { RigidBody } from "@react-three/rapier";
import { Box3, Vector3, Quaternion } from "three";
import { useMemo } from "react";
import { Cylinder } from "@react-three/drei";
export const AssetBoundingBox = ({ boundingBox }: { boundingBox: Box3 }) => {
export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => {
const { edgeCylinders, center, size } = useMemo(() => {
if (!boundingBox) return { edgeCylinders: [], center: new Vector3(), size: new Vector3() };
const size = boundingBox.getSize(new Vector3());
const center = boundingBox.getCenter(new Vector3());
const min = boundingBox.min;
const max = boundingBox.max;
const center = boundingBox.getCenter(new Vector3());
const size = boundingBox.getSize(new Vector3());
const boxGeometry = useMemo(() => new BoxGeometry(size.x, size.y, size.z), [size]);
const edges = useMemo(() => new EdgesGeometry(boxGeometry), [boxGeometry]);
const corners = [
new Vector3(min.x, min.y, min.z),
new Vector3(max.x, min.y, min.z),
new Vector3(max.x, max.y, min.z),
new Vector3(min.x, max.y, min.z),
new Vector3(min.x, min.y, max.z),
new Vector3(max.x, min.y, max.z),
new Vector3(max.x, max.y, max.z),
new Vector3(min.x, max.y, max.z),
];
const edgeIndices: [number, number][] = [
[0, 1], [1, 2], [2, 3], [3, 0],
[4, 5], [5, 6], [6, 7], [7, 4],
[0, 4], [1, 5], [2, 6], [3, 7],
];
const radius = 0.005 * lineWidth;
const edgeCylinders = edgeIndices.map(([startIdx, endIdx], i) => {
const start = corners[startIdx];
const end = corners[endIdx];
const direction = new Vector3().subVectors(end, start);
const length = direction.length();
const midPoint = new Vector3().addVectors(start, end).multiplyScalar(0.5);
const quaternion = new Quaternion().setFromUnitVectors(
new Vector3(0, 1, 0),
direction.clone().normalize()
);
return {
key: `edge-cylinder-${i}`,
position: midPoint,
rotation: quaternion,
length,
radius,
};
});
return { edgeCylinders, center, size };
}, [boundingBox, lineWidth]);
if (!boundingBox) return null;
return (
<RigidBody includeInvisible type="fixed" colliders="cuboid" position={center.toArray()} rotation={[0, 0, 0]}>
<lineSegments>
<bufferGeometry attach="geometry" {...edges} />
<lineBasicMaterial depthWrite={false} attach="material" color="gray" linewidth={1} />
</lineSegments>
<group name={name}>
{edgeCylinders.map(({ key, position, rotation, length, radius }) => (
<Cylinder key={key} args={[radius, radius, length, 6]} position={position} quaternion={rotation} >
<meshBasicMaterial color={color} depthWrite={false} />
</Cylinder>
))}
<mesh visible={false}>
<mesh visible={false} position={center}>
<boxGeometry args={[size.x, size.y, size.z]} />
<meshStandardMaterial transparent opacity={0} />
</mesh>
</RigidBody>
</group>
);
};

View File

@@ -0,0 +1,94 @@
import { useEffect, useRef, useCallback, useState } from 'react';
import * as THREE from 'three';
import { useFrame } from '@react-three/fiber';
import { useSceneContext } from '../../../../../scene/sceneContext';
import useModuleStore from '../../../../../../store/useModuleStore';
import { usePauseButtonStore, useAnimationPlaySpeed } from '../../../../../../store/usePlayButtonStore';
interface ModelAnimatorProps {
asset: Asset;
gltfScene: THREE.Object3D;
}
export function ModelAnimator({
asset,
gltfScene,
}: ModelAnimatorProps) {
const mixerRef = useRef<THREE.AnimationMixer>();
const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
const [previousAnimation, setPreviousAnimation] = useState<string | null>(null);
const blendFactor = useRef(0);
const blendDuration = 0.5;
const { speed } = useAnimationPlaySpeed();
const { isPaused } = usePauseButtonStore();
const { assetStore } = useSceneContext();
const { activeModule } = useModuleStore();
const { setAnimations, setAnimationComplete } = assetStore();
const handleAnimationComplete = useCallback(() => {
if (asset.animationState) {
setAnimationComplete(asset.modelUuid, true);
}
}, [asset.animationState]);
useEffect(() => {
if (!gltfScene || !gltfScene.animations || gltfScene.animations.length === 0) return;
mixerRef.current = new THREE.AnimationMixer(gltfScene);
gltfScene.animations.forEach((clip: THREE.AnimationClip) => {
const action = mixerRef.current!.clipAction(clip);
actions.current[clip.name] = action;
});
const animationNames = gltfScene.animations.map(clip => clip.name);
setAnimations(asset.modelUuid, animationNames);
return () => {
mixerRef.current?.stopAllAction();
mixerRef.current = undefined;
};
}, [gltfScene]);
useEffect(() => {
if (!asset.animationState || !mixerRef.current) return;
const { current, loopAnimation, isPlaying } = asset.animationState;
const currentAction = actions.current[current];
const previousAction = previousAnimation ? actions.current[previousAnimation] : null;
if (isPlaying && currentAction && !isPaused) {
blendFactor.current = 0;
currentAction.reset();
currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1);
currentAction.clampWhenFinished = true;
if (previousAction && previousAction !== currentAction) {
previousAction.crossFadeTo(currentAction, blendDuration, false);
}
currentAction.play();
mixerRef.current.addEventListener('finished', handleAnimationComplete);
setPreviousAnimation(current);
} else {
Object.values(actions.current).forEach((action) => action.stop());
}
return () => {
if (mixerRef.current) {
mixerRef.current.removeEventListener('finished', handleAnimationComplete);
}
};
}, [asset.animationState?.current, asset.animationState?.isCompleted, asset.animationState?.isPlaying, isPaused, activeModule]);
useFrame((_, delta) => {
if (mixerRef.current) {
mixerRef.current.update(delta * (activeModule === 'simulation' ? speed : 1));
}
});
return null;
}

View File

@@ -0,0 +1,238 @@
import * as THREE from 'three';
import { CameraControls } from '@react-three/drei';
import { ThreeEvent } from '@react-three/fiber';
import { useCallback } from 'react';
import { ProductStoreType } from '../../../../../../store/simulation/useProductStore';
import { EventStoreType } from '../../../../../../store/simulation/useEventsStore';
import { Socket } from 'socket.io-client';
import { useActiveTool, useToolMode } from '../../../../../../store/builder/store';
import useModuleStore, { useSubModuleStore } from '../../../../../../store/useModuleStore';
import { useSocketStore } from '../../../../../../store/builder/store';
import { useSceneContext } from '../../../../../scene/sceneContext';
import { useProductContext } from '../../../../../simulation/products/productContext';
import { useVersionContext } from '../../../../version/versionContext';
import { useParams } from 'react-router-dom';
import { getUserData } from '../../../../../../functions/getUserData';
// import { deleteFloorItem } from '../../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi';
export function useModelEventHandlers({
controls,
boundingBox,
groupRef,
toggleView,
deletableFloorItem,
setDeletableFloorItem,
setSelectedFloorItem,
gl,
setTop,
setLeft,
getIsEventInProduct,
getEventByModelUuid,
setSelectedAsset,
clearSelectedAsset,
removeAsset,
updateBackend,
leftDrag,
rightDrag
}: {
controls: CameraControls | any,
boundingBox: THREE.Box3 | null,
groupRef: React.RefObject<THREE.Group>,
toggleView: boolean,
deletableFloorItem: THREE.Object3D | null,
setDeletableFloorItem: (item: THREE.Object3D | null) => void,
setSelectedFloorItem: (item: THREE.Object3D | null) => void,
gl: THREE.WebGLRenderer,
setTop: (top: number) => void,
setLeft: (left: number) => void,
getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean,
getEventByModelUuid: (modelUuid: string) => EventsSchema | undefined,
setSelectedAsset: (EventData: EventsSchema) => void,
clearSelectedAsset: () => void,
removeAsset: (modelUuid: string) => void,
updateBackend: (productName: string, productUuid: string, projectId: string, event: EventsSchema) => void,
leftDrag: React.MutableRefObject<boolean>,
rightDrag: React.MutableRefObject<boolean>
}) {
const { activeTool } = useActiveTool();
const { activeModule } = useModuleStore();
const { subModule } = useSubModuleStore();
const { socket } = useSocketStore();
const { eventStore, productStore } = useSceneContext();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const { userId, organization } = getUserData();
const handleDblClick = (asset: Asset) => {
if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
const size = boundingBox.getSize(new THREE.Vector3());
const center = boundingBox.getCenter(new THREE.Vector3());
const front = new THREE.Vector3(0, 0, 1);
groupRef.current.localToWorld(front);
front.sub(groupRef.current.position).normalize();
const distance = Math.max(size.x, size.y, size.z) * 2;
const newPosition = center.clone().addScaledVector(front, distance);
(controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true);
(controls as CameraControls).setTarget(center.x, center.y, center.z, true);
(controls as CameraControls).fitToBox(groupRef.current, true, {
cover: true,
paddingTop: 5,
paddingLeft: 5,
paddingBottom: 5,
paddingRight: 5,
});
setSelectedFloorItem(groupRef.current);
}
};
const handleClick = async (evt: ThreeEvent<MouseEvent>, asset: Asset) => {
if (leftDrag.current || toggleView) return;
if (activeTool === 'delete' && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
//REST
// const response = await deleteFloorItem(organization, asset.modelUuid, asset.modelName);
//SOCKET
const data = {
organization,
modelUuid: asset.modelUuid,
modelName: asset.modelName,
socketId: socket.id,
userId,
versionId: selectedVersion?.versionId || '',
projectId
}
const response = socket.emit('v1:model-asset:delete', data)
eventStore.getState().removeEvent(asset.modelUuid);
const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid);
updatedEvents.forEach((event) => {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
})
if (response) {
removeAsset(asset.modelUuid);
echo.success("Model Removed!");
}
} else if (activeModule === 'simulation' && subModule === "simulations" && activeTool === 'pen') {
if (asset.eventData && asset.eventData.type === 'Conveyor') {
const intersectedPoint = evt.point;
const localPosition = groupRef.current?.worldToLocal(intersectedPoint.clone());
if (localPosition) {
const conveyorPoint: ConveyorPointSchema = {
uuid: THREE.MathUtils.generateUUID(),
position: [localPosition?.x, localPosition?.y, localPosition?.z],
rotation: [0, 0, 0],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
actionName: `Action 1`,
actionType: 'default',
material: 'Default Material',
delay: 0,
spawnInterval: 5,
spawnCount: 1,
triggers: []
}
}
const event = productStore.getState().addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint);
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
}
}
}
}
};
const handlePointerOver = useCallback((asset: Asset) => {
if (activeTool === "delete" && activeModule === 'builder') {
if (deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
return;
} else {
setDeletableFloorItem(groupRef.current);
}
}
}, [activeTool, activeModule, deletableFloorItem]);
const handlePointerOut = useCallback((evt: ThreeEvent<MouseEvent>, asset: Asset) => {
if (evt.intersections.length === 0 && activeTool === "delete" && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
setDeletableFloorItem(null);
}
}, [activeTool, deletableFloorItem]);
const handleContextMenu = (asset: Asset, evt: ThreeEvent<MouseEvent>) => {
if (rightDrag.current || toggleView) return;
if (activeTool === "cursor" && subModule === 'simulations') {
if (asset.modelUuid) {
const canvasElement = gl.domElement;
const isInProduct = getIsEventInProduct(selectedProduct.productUuid, asset.modelUuid);
if (isInProduct) {
const event = getEventByModelUuid(asset.modelUuid);
if (event) {
setSelectedAsset(event);
const canvasRect = canvasElement.getBoundingClientRect();
const relativeX = evt.clientX - canvasRect.left;
const relativeY = evt.clientY - canvasRect.top;
setTop(relativeY);
setLeft(relativeX);
} else {
clearSelectedAsset();
}
} else {
const event = getEventByModelUuid(asset.modelUuid);
if (event) {
setSelectedAsset(event)
const canvasRect = canvasElement.getBoundingClientRect();
const relativeX = evt.clientX - canvasRect.left;
const relativeY = evt.clientY - canvasRect.top;
setTop(relativeY);
setLeft(relativeX);
} else {
clearSelectedAsset()
}
}
} else {
clearSelectedAsset()
}
} else {
clearSelectedAsset()
}
}
return {
handleDblClick,
handleClick,
handlePointerOver,
handlePointerOut,
handleContextMenu
};
}

View File

@@ -5,8 +5,8 @@ import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
import { RapierRigidBody, RigidBody } from '@react-three/rapier';
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
import { ThreeEvent, useThree } from '@react-three/fiber';
import { useActiveTool, useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
import { AssetBoundingBox } from '../../functions/assetBoundingBox';
import { CameraControls } from '@react-three/drei';
import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore';
@@ -17,23 +17,23 @@ import { useParams } from 'react-router-dom';
import { getUserData } from '../../../../../functions/getUserData';
import { useSceneContext } from '../../../../scene/sceneContext';
import { useVersionContext } from '../../../version/versionContext';
import { useAnimationPlaySpeed, usePauseButtonStore } from '../../../../../store/usePlayButtonStore';
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
import ConveyorCollider from './conveyorCollider';
function Model({ asset }: { readonly asset: Asset }) {
import { ModelAnimator } from './animator/modelAnimator';
function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boolean }) {
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
const { camera, controls, gl, scene } = useThree();
const { controls, gl, scene } = useThree();
const savedTheme: string = localStorage.getItem("theme") || "light";
const { activeTool } = useActiveTool();
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { subModule } = useSubModuleStore();
const { activeModule } = useModuleStore();
const { speed } = useAnimationPlaySpeed();
const { isPaused } = usePauseButtonStore();
const { assetStore, eventStore, productStore } = useSceneContext();
const { removeAsset, setAnimations, resetAnimation, setAnimationComplete } = assetStore();
const { removeAsset, resetAnimation } = assetStore();
const { setTop } = useTopData();
const { setLeft } = useLeftData();
const { getIsEventInProduct, addPoint } = productStore();
@@ -44,28 +44,22 @@ function Model({ asset }: { readonly asset: Asset }) {
const { socket } = useSocketStore();
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { limitDistance } = useLimitDistance();
const { renderDistance } = useRenderDistance();
const leftDrag = useRef(false);
const isLeftMouseDown = useRef(false);
const rightDrag = useRef(false);
const isRightMouseDown = useRef(false);
const [isRendered, setIsRendered] = useState(false);
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
const [conveyorPlaneSize, setConveyorPlaneSize] = useState<[number, number] | null>(null);
const groupRef = useRef<THREE.Group>(null);
const rigidBodyRef = useRef<RapierRigidBody>(null);
const mixerRef = useRef<THREE.AnimationMixer>();
const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
const [previousAnimation, setPreviousAnimation] = useState<string | null>(null);
const [isSelected, setIsSelected] = useState(false);
const [ikData, setIkData] = useState<any>();
const blendFactor = useRef(0);
const blendDuration = 0.5;
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
const { selectedAssets } = useSelectedAssets();
const updateBackend = (
productName: string,
@@ -93,6 +87,17 @@ function Model({ asset }: { readonly asset: Asset }) {
}
}, [asset.modelUuid, ikData])
useEffect(() => {
if (gltfScene) {
gltfScene.traverse((child: any) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
})
}
}, [gltfScene]);
useEffect(() => {
setDeletableFloorItem(null);
if (selectedFloorItem === null || selectedFloorItem.userData.modelUuid !== asset.modelUuid) {
@@ -123,16 +128,6 @@ function Model({ asset }: { readonly asset: Asset }) {
clone.animations = cachedModel.animations || [];
setGltfScene(clone);
calculateBoundingBox(clone);
if (cachedModel.animations && clone.animations.length > 0) {
const animationName = clone.animations.map((clip: any) => clip.name);
setAnimations(asset.modelUuid, animationName)
mixerRef.current = new THREE.AnimationMixer(clone);
clone.animations.forEach((animation: any) => {
const action = mixerRef.current!.clipAction(animation);
actions.current[animation.name] = action;
});
}
return;
}
@@ -197,21 +192,6 @@ function Model({ asset }: { readonly asset: Asset }) {
}, []);
useFrame(() => {
const assetPosition = new THREE.Vector3(...asset.position);
if (limitDistance) {
if (!isRendered && assetPosition.distanceTo(camera.position) <= renderDistance) {
setIsRendered(true);
} else if (isRendered && assetPosition.distanceTo(camera.position) > renderDistance) {
setIsRendered(false);
}
} else {
if (!isRendered) {
setIsRendered(true);
}
}
})
const handleDblClick = (asset: Asset) => {
if (asset) {
if (activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
@@ -377,50 +357,6 @@ function Model({ asset }: { readonly asset: Asset }) {
}
}
const handleAnimationComplete = useCallback(() => {
if (asset.animationState) {
setAnimationComplete(asset.modelUuid, true);
}
}, [asset.animationState]);
useFrame((_, delta) => {
if (mixerRef.current) {
mixerRef.current.update(delta * (activeModule === 'simulation' ? speed : 1));
}
});
useEffect(() => {
if (!asset.animationState || !mixerRef.current) return;
const { current, loopAnimation, isPlaying } = asset.animationState;
const currentAction = actions.current[current];
const previousAction = previousAnimation ? actions.current[previousAnimation] : null;
if (isPlaying && currentAction && activeModule === 'simulation' && !isPaused) {
blendFactor.current = 0;
currentAction.reset();
currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1);
currentAction.clampWhenFinished = true;
if (previousAction && previousAction !== currentAction) {
previousAction.crossFadeTo(currentAction, blendDuration, false);
}
currentAction.play();
mixerRef.current.addEventListener('finished', handleAnimationComplete);
setPreviousAnimation(current);
} else {
Object.values(actions.current).forEach((action) => action.stop());
}
return () => {
if (mixerRef.current) {
mixerRef.current.removeEventListener('finished', handleAnimationComplete);
}
};
}, [asset.animationState?.current, asset.animationState?.isCompleted, asset.animationState?.isPlaying, isPaused, activeModule]);
useEffect(() => {
const canvasElement = gl.domElement;
@@ -465,6 +401,18 @@ function Model({ asset }: { readonly asset: Asset }) {
}, [gl])
useEffect(() => {
if (selectedAssets.length > 0) {
if (selectedAssets.some((selectedAsset: THREE.Object3D) => selectedAsset.userData.modelUuid === asset.modelUuid)) {
setIsSelected(true);
} else {
setIsSelected(false);
}
} else {
setIsSelected(false);
}
}, [selectedAssets])
return (
<group
key={asset.modelUuid}
@@ -475,6 +423,8 @@ function Model({ asset }: { readonly asset: Asset }) {
rotation={asset.rotation}
visible={asset.isVisible}
userData={{ ...asset, iks: ikData, rigidBodyRef: rigidBodyRef.current, boundingBox: boundingBox }}
castShadow
receiveShadow
onDoubleClick={(e) => {
e.stopPropagation();
if (!toggleView) {
@@ -508,6 +458,7 @@ function Model({ asset }: { readonly asset: Asset }) {
<>
{isRendered ? (
<>
<RigidBody
type="fixed"
colliders='cuboid'
@@ -516,20 +467,23 @@ function Model({ asset }: { readonly asset: Asset }) {
<primitive object={gltfScene} />
</ RigidBody>
<ModelAnimator asset={asset} gltfScene={gltfScene} />
</>
) : (
<>
{boundingBox &&
<AssetBoundingBox boundingBox={boundingBox} />
}
</>
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={2.5} />
)}
<ConveyorCollider boundingBox={boundingBox}
asset={asset}
modelName={asset.modelName}
scene={scene}
conveyorPlaneSize={conveyorPlaneSize}
/>
{isSelected &&
<AssetBoundingBox name='Asset BBox' boundingBox={boundingBox} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} />
}
</>
)}
</group>

View File

@@ -1,17 +1,45 @@
import Model from './model/model';
import { useThree } from '@react-three/fiber';
import { useEffect, useRef, useState } from "react";
import { useThree, useFrame } from "@react-three/fiber";
import { Vector3 } from "three";
import { CameraControls } from '@react-three/drei';
import { Vector3 } from 'three';
import { useSelectedFloorItem } from '../../../../store/builder/store';
import { useLimitDistance, useRenderDistance, useSelectedFloorItem } from '../../../../store/builder/store';
import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore';
import { useSceneContext } from '../../../scene/sceneContext';
import Model from './model/model';
const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url));
function Models() {
const { controls } = useThree();
const { controls, camera } = useThree();
const { assetStore } = useSceneContext();
const { assets } = assetStore();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
const { limitDistance } = useLimitDistance();
const { renderDistance } = useRenderDistance();
const [renderMap, setRenderMap] = useState<Record<string, boolean>>({});
const cameraPos = useRef(new Vector3());
useEffect(() => {
distanceWorker.onmessage = (e) => {
const { shouldRender, modelUuid } = e.data;
setRenderMap((prev) => {
if (prev[modelUuid] === shouldRender) return prev;
return { ...prev, [modelUuid]: shouldRender };
});
};
}, []);
useFrame(() => {
camera.getWorldPosition(cameraPos.current);
for (const asset of assets) {
const isRendered = renderMap[asset.modelUuid] ?? false;
distanceWorker.postMessage({ modelUuid: asset.modelUuid, assetPosition: { x: asset.position[0], y: asset.position[1], z: asset.position[2], }, cameraPosition: cameraPos.current, limitDistance, renderDistance, isRendered, });
}
});
return (
<group
@@ -28,11 +56,11 @@ function Models() {
}
}}
>
{assets.map((asset) =>
<Model key={asset.modelUuid} asset={asset} />
)}
{assets.map((asset) => (
<Model key={asset.modelUuid} asset={asset} isRendered={renderMap[asset.modelUuid] ?? false} />
))}
</group>
)
);
}
export default Models
export default Models;

View File

@@ -19,8 +19,9 @@ function FloorCreator() {
const { toolMode } = useToolMode();
const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore();
const { floorStore } = useSceneContext();
const { addFloor, getFloorPointById, getFloorByPoints } = floorStore();
const { floorStore, undoRedo2DStore } = useSceneContext();
const { addFloor, getFloorPointById } = floorStore();
const { push2D } = undoRedo2DStore();
const drag = useRef(false);
const isLeftMouseDown = useRef(false);
const { selectedVersionStore } = useVersionContext();
@@ -103,6 +104,21 @@ function FloorCreator() {
};
addFloor(floor);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Floor',
lineData: floor,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
@@ -142,6 +158,21 @@ function FloorCreator() {
};
addFloor(floor);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Floor',
lineData: floor,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
@@ -191,6 +222,21 @@ function FloorCreator() {
};
addFloor(floor);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Floor',
lineData: floor,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
@@ -243,7 +289,7 @@ function FloorCreator() {
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext);
};
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addFloor, getFloorPointById, getFloorByPoints, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]);
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addFloor, getFloorPointById, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]);
return (
<>

View File

@@ -30,10 +30,11 @@ function Line({ points }: Readonly<LineProps>) {
const [isDeletable, setIsDeletable] = useState(false);
const { socket } = useSocketStore();
const { toolMode } = useToolMode();
const { wallStore, floorStore, zoneStore } = useSceneContext();
const { wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext();
const { push2D } = undoRedo2DStore();
const { removeWallByPoints, setPosition: setWallPosition, getWallsByPointId } = wallStore();
const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId } = floorStore();
const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId } = zoneStore();
const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId, getFloorsByPoints } = floorStore();
const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId, getZonesByPoints } = zoneStore();
const { userId, organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
@@ -117,10 +118,26 @@ function Line({ points }: Readonly<LineProps>) {
}
socket.emit('v1:model-Wall:delete', data);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Delete',
point: {
type: 'Wall',
lineData: removedWall,
timeStamp: new Date().toISOString(),
}
}
]
});
}
setHoveredLine(null);
}
if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') {
const Floors = getFloorsByPoints(points);
const { removedFloors, updatedFloors } = removeFloorByPoints(points);
if (removedFloors.length > 0) {
removedFloors.forEach(floor => {
@@ -143,6 +160,22 @@ function Line({ points }: Readonly<LineProps>) {
socket.emit('v1:model-Floor:delete', data);
}
});
const removedFloorsData = removedFloors.map((floor) => ({
type: "Floor" as const,
lineData: floor,
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Delete',
points: removedFloorsData
}
]
});
}
if (updatedFloors.length > 0) {
updatedFloors.forEach(floor => {
@@ -165,11 +198,29 @@ function Line({ points }: Readonly<LineProps>) {
socket.emit('v1:model-Floor:add', data);
}
});
const updatedFloorsData = updatedFloors.map((floor) => ({
type: "Floor" as const,
lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor,
newData: floor,
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Update',
points: updatedFloorsData
}
]
});
}
setHoveredLine(null);
}
if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') {
const Zones = getZonesByPoints(points);
const { removedZones, updatedZones } = removeZoneByPoints(points);
if (removedZones.length > 0) {
removedZones.forEach(zone => {
@@ -192,6 +243,22 @@ function Line({ points }: Readonly<LineProps>) {
socket.emit('v1:zone:delete', data);
}
});
const removedZonesData = removedZones.map((zone) => ({
type: "Zone" as const,
lineData: zone,
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Delete',
points: removedZonesData
}
]
});
}
if (updatedZones.length > 0) {
updatedZones.forEach(zone => {
@@ -214,7 +281,26 @@ function Line({ points }: Readonly<LineProps>) {
socket.emit('v1:zone:add', data);
}
});
const updatedZonesData = updatedZones.map((zone) => ({
type: "Zone" as const,
lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone,
newData: zone,
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Update',
points: updatedZonesData
}
]
});
}
setHoveredLine(null);
}
handleCanvasCursors('default');
}

View File

@@ -32,13 +32,14 @@ function Point({ point }: { readonly point: Point }) {
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
const { socket } = useSocketStore();
const { toolMode } = useToolMode();
const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext();
const { push2D } = undoRedo2DStore();
const { setPosition: setAislePosition, removePoint: removeAislePoint, getAislesByPointId } = aisleStore();
const { setPosition: setWallPosition, removePoint: removeWallPoint, getWallsByPointId } = wallStore();
const { setPosition: setFloorPosition, removePoint: removeFloorPoint, getFloorsByPointId } = floorStore();
const { setPosition: setZonePosition, removePoint: removeZonePoint, getZonesByPointId } = zoneStore();
const { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle, snapFloorPoint, snapFloorAngle, snapZonePoint, snapZoneAngle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position });
const { hoveredPoint,hoveredLine, setHoveredPoint } = useBuilderStore();
const { hoveredPoint, hoveredLine, setHoveredPoint } = useBuilderStore();
const { selectedPoints } = useSelectedPoints();
const { userId, organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
@@ -47,6 +48,13 @@ function Point({ point }: { readonly point: Point }) {
const boxScale: [number, number, number] = Constants.pointConfig.boxScale;
const colors = getColor(point);
const [initialPositions, setInitialPositions] = useState<{
aisles?: Aisle[],
walls?: Wall[],
floors?: Floor[],
zones?: Zone[]
}>({});
useEffect(() => {
handleCanvasCursors('default');
}, [toolMode])
@@ -152,6 +160,20 @@ function Point({ point }: { readonly point: Point }) {
const currentPosition = new THREE.Vector3(...point.position);
const offset = new THREE.Vector3().subVectors(currentPosition, hit);
setDragOffset(offset);
if (point.pointType === 'Aisle') {
const aisles = getAislesByPointId(point.pointUuid);
setInitialPositions({ aisles });
} else if (point.pointType === 'Wall') {
const walls = getWallsByPointId(point.pointUuid);
setInitialPositions({ walls });
} else if (point.pointType === 'Floor') {
const floors = getFloorsByPointId(point.pointUuid);
setInitialPositions({ floors });
} else if (point.pointType === 'Zone') {
const zones = getZonesByPointId(point.pointUuid);
setInitialPositions({ zones });
}
}
};
@@ -159,6 +181,7 @@ function Point({ point }: { readonly point: Point }) {
handleCanvasCursors('default');
setDragOffset(null);
if (toolMode !== 'move') return;
if (point.pointType === 'Aisle') {
const updatedAisles = getAislesByPointId(point.pointUuid);
if (updatedAisles.length > 0 && projectId) {
@@ -180,6 +203,23 @@ function Point({ point }: { readonly point: Point }) {
type: updatedAisle.type
})
})
if (initialPositions.aisles && initialPositions.aisles.length > 0) {
const updatedPoints = initialPositions.aisles.map((aisle) => ({
type: "Aisle" as const,
lineData: aisle,
newData: updatedAisles.find(a => a.aisleUuid === aisle.aisleUuid),
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [{
actionType: 'Lines-Update',
points: updatedPoints,
}]
});
}
}
} else if (point.pointType === 'Wall') {
const updatedWalls = getWallsByPointId(point.pointUuid);
@@ -203,6 +243,23 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:model-Wall:add', data);
});
}
if (initialPositions.walls && initialPositions.walls.length > 0) {
const updatedPoints = initialPositions.walls.map((wall) => ({
type: "Wall" as const,
lineData: wall,
newData: updatedWalls.find(w => w.wallUuid === wall.wallUuid),
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [{
actionType: 'Lines-Update',
points: updatedPoints,
}]
});
}
} else if (point.pointType === 'Floor') {
const updatedFloors = getFloorsByPointId(point.pointUuid);
if (updatedFloors && updatedFloors.length > 0 && projectId) {
@@ -225,6 +282,23 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:model-Floor:add', data);
});
}
if (initialPositions.floors && initialPositions.floors.length > 0) {
const updatedPoints = initialPositions.floors.map((floor) => ({
type: "Floor" as const,
lineData: floor,
newData: updatedFloors.find(f => f.floorUuid === floor.floorUuid),
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [{
actionType: 'Lines-Update',
points: updatedPoints,
}]
});
}
} else if (point.pointType === 'Zone') {
const updatedZones = getZonesByPointId(point.pointUuid);
if (updatedZones && updatedZones.length > 0 && projectId) {
@@ -247,13 +321,33 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:zone:add', data);
});
}
if (initialPositions.zones && initialPositions.zones.length > 0) {
const updatedPoints = initialPositions.zones.map((zone) => ({
type: "Zone" as const,
lineData: zone,
newData: updatedZones.find(z => z.zoneUuid === zone.zoneUuid),
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [{
actionType: 'Lines-Update',
points: updatedPoints,
}]
});
}
}
setInitialPositions({});
}
const handlePointClick = (point: Point) => {
if (toolMode === '2D-Delete') {
if (point.pointType === 'Aisle') {
const removedAisles = removeAislePoint(point.pointUuid);
setHoveredPoint(null);
if (removedAisles.length > 0) {
removedAisles.forEach(aisle => {
if (projectId) {
@@ -273,9 +367,25 @@ function Point({ point }: { readonly point: Point }) {
}
socket.emit('v1:model-aisle:delete', data);
}
});
setHoveredPoint(null);
const removedAislesData = removedAisles.map((aisle) => ({
type: "Aisle" as const,
lineData: aisle,
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Delete',
points: removedAislesData
}
]
});
}
}
if (point.pointType === 'Wall') {
@@ -302,9 +412,26 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:model-Wall:delete', data);
}
});
const removedWallsData = removedWalls.map((wall) => ({
type: "Wall" as const,
lineData: wall,
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Delete',
points: removedWallsData
}
]
});
}
}
if (point.pointType === 'Floor') {
const Floors = getFloorsByPointId(point.pointUuid);
const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid);
setHoveredPoint(null);
if (removedFloors.length > 0) {
@@ -328,6 +455,22 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:model-Floor:delete', data);
}
});
const removedFloorsData = removedFloors.map((floor) => ({
type: "Floor" as const,
lineData: floor,
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Delete',
points: removedFloorsData
}
]
});
}
if (updatedFloors.length > 0) {
updatedFloors.forEach(floor => {
@@ -350,9 +493,27 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:model-Floor:add', data);
}
});
const updatedFloorsData = updatedFloors.map((floor) => ({
type: "Floor" as const,
lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor,
newData: floor,
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Update',
points: updatedFloorsData
}
]
});
}
}
if (point.pointType === 'Zone') {
const Zones = getZonesByPointId(point.pointUuid);
const { removedZones, updatedZones } = removeZonePoint(point.pointUuid);
setHoveredPoint(null);
if (removedZones.length > 0) {
@@ -376,6 +537,22 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:zone:delete', data);
}
});
const removedZonesData = removedZones.map((zone) => ({
type: "Zone" as const,
lineData: zone,
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Delete',
points: removedZonesData
}
]
});
}
if (updatedZones.length > 0) {
updatedZones.forEach(zone => {
@@ -398,6 +575,23 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:zone:add', data);
}
});
const updatedZonesData = updatedZones.map((zone) => ({
type: "Zone" as const,
lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone,
newData: zone,
timeStamp: new Date().toISOString(),
}));
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Update',
points: updatedZonesData
}
]
});
}
}
handleCanvasCursors('default');
@@ -422,7 +616,6 @@ function Point({ point }: { readonly point: Point }) {
return null;
}
return (
<>
{!isSelected ?
@@ -453,7 +646,7 @@ function Point({ point }: { readonly point: Point }) {
onPointerOut={() => {
if (hoveredPoint) {
setHoveredPoint(null);
if(!hoveredLine){
if (!hoveredLine) {
handleCanvasCursors('default');
}
}

View File

@@ -10,7 +10,7 @@ import { useToggleView, useWallVisibility } from '../../../../../store/builder/s
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
import * as Constants from '../../../../../types/world/worldConstants';
import DecalInstance from '../../../Decal/decalInstance';
// import DecalInstance from '../../../Decal/decalInstance';
import defaultMaterial from '../../../../../assets/textures/floor/wall-tex.png';
import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg';
@@ -63,10 +63,10 @@ function Wall({ wall }: { readonly wall: Wall }) {
const materials = useMemo(() => {
return [
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Left
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Right
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Top
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Bottom
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Left
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Right
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Top
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Bottom
new THREE.MeshStandardMaterial({
color: Constants.wallConfig.defaultColor,
side: THREE.DoubleSide,
@@ -99,11 +99,15 @@ function Wall({ wall }: { readonly wall: Wall }) {
return (
<mesh
castShadow
receiveShadow
name={`Wall-${wall.wallUuid}`}
key={wall.wallUuid}
userData={wall}
>
<Base
castShadow
receiveShadow
ref={meshRef}
geometry={geometry}
position={[centerX, centerY, centerZ]}

View File

@@ -132,6 +132,7 @@ function Floor({ room }: { room: Point[] }) {
<mesh name="Wall-Floor" rotation={[Math.PI / 2, 0, 0]}>
<Extrude
receiveShadow
castShadow
name="Wall-Floor"
args={[shape, { depth: Constants.floorConfig.height, bevelEnabled: false }]}
position={[0, 0, 0]}

View File

@@ -22,8 +22,9 @@ function WallCreator() {
const { toolMode } = useToolMode();
const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore();
const { wallStore } = useSceneContext();
const { wallStore, undoRedo2DStore } = useSceneContext();
const { addWall, getWallPointById, removeWall, getWallByPoints } = wallStore();
const { push2D } = undoRedo2DStore();
const drag = useRef(false);
const isLeftMouseDown = useRef(false);
const { selectedVersionStore } = useVersionContext();
@@ -91,6 +92,7 @@ function WallCreator() {
const closestPoint = new THREE.Vector3().lerpVectors(point1Vec, point2Vec, t);
removeWall(wall.wallUuid);
if (projectId) {
// API
@@ -142,6 +144,7 @@ function WallCreator() {
wallHeight: wallHeight,
decals: []
}
addWall(wall2);
// API
@@ -171,8 +174,36 @@ function WallCreator() {
wallHeight: wallHeight,
decals: []
}
addWall(wall3);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Create',
points: [
{
type: 'Wall',
lineData: wall3,
timeStamp: new Date().toISOString(),
}, {
type: 'Wall',
lineData: wall2,
timeStamp: new Date().toISOString(),
}
]
}, {
actionType: 'Line-Delete',
point: {
type: 'Wall',
lineData: wall,
timeStamp: new Date().toISOString(),
}
}
],
})
// API
// if (projectId) {
@@ -202,7 +233,8 @@ function WallCreator() {
wallThickness: wallThickness,
wallHeight: wallHeight,
decals: []
};
}
addWall(wall1);
// API
@@ -232,6 +264,7 @@ function WallCreator() {
wallHeight: wallHeight,
decals: []
}
addWall(wall2);
// API
@@ -261,8 +294,40 @@ function WallCreator() {
wallHeight: wallHeight,
decals: []
}
addWall(wall3);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Lines-Create',
points: [
{
type: 'Wall',
lineData: wall3,
timeStamp: new Date().toISOString(),
}, {
type: 'Wall',
lineData: wall1,
timeStamp: new Date().toISOString(),
}, {
type: 'Wall',
lineData: wall2,
timeStamp: new Date().toISOString(),
}
]
}, {
actionType: 'Line-Delete',
point: {
type: 'Wall',
lineData: wall,
timeStamp: new Date().toISOString(),
}
}
],
})
// API
// if (projectId) {
@@ -328,9 +393,24 @@ function WallCreator() {
wallThickness: wallThickness,
wallHeight: wallHeight,
decals: []
};
}
addWall(wall);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Wall',
lineData: wall,
timeStamp: new Date().toISOString(),
}
}
],
})
// API
// if (projectId) {

View File

@@ -242,7 +242,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
visible={wallAsset.isVisible}
userData={wallAsset}
>
<Subtraction position={[center.x, center.y, center.z]} scale={[size.x, size.y, wall.wallThickness + 0.05]}>
<Subtraction position={[center.x, center.y, 0]} scale={[size.x, size.y, wall.wallThickness + 0.05]}>
<Geometry>
<Base geometry={new THREE.BoxGeometry()} />
</Geometry>
@@ -265,7 +265,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
e.stopPropagation();
let currentObject = e.object as THREE.Object3D;
while (currentObject) {
if (currentObject.name === "Scene") {
if (currentObject.userData.wallUuid) {
break;
}
currentObject = currentObject.parent as THREE.Object3D;
@@ -286,7 +286,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
e.stopPropagation();
let currentObject = e.object as THREE.Object3D;
while (currentObject) {
if (currentObject.name === "Scene") {
if (currentObject.userData.wallUuid) {
break;
}
currentObject = currentObject.parent as THREE.Object3D;

View File

@@ -19,8 +19,9 @@ function ZoneCreator() {
const { toolMode } = useToolMode();
const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore();
const { zoneStore } = useSceneContext();
const { zones, addZone, getZonePointById, getZoneByPoints } = zoneStore();
const { zoneStore, undoRedo2DStore } = useSceneContext();
const { addZone, getZonePointById } = zoneStore();
const { push2D } = undoRedo2DStore();
const drag = useRef(false);
const isLeftMouseDown = useRef(false);
const { selectedVersionStore } = useVersionContext();
@@ -102,6 +103,21 @@ function ZoneCreator() {
};
addZone(zone);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Zone',
lineData: zone,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
@@ -139,6 +155,21 @@ function ZoneCreator() {
};
addZone(zone);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Zone',
lineData: zone,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
@@ -186,6 +217,21 @@ function ZoneCreator() {
};
addZone(zone);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Zone',
lineData: zone,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
@@ -238,7 +284,7 @@ function ZoneCreator() {
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext);
};
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, getZoneByPoints, zoneColor, zoneHeight, snappedPosition, snappedPoint]);
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, zoneColor, zoneHeight, snappedPosition, snappedPoint]);
return (
<>

View File

@@ -14,6 +14,7 @@ import TransformControl from "./transformControls/transformControls";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../functions/getUserData";
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
export default function Controls() {
const controlsRef = useRef<CameraControls>(null);
@@ -142,6 +143,8 @@ export default function Controls() {
<SelectionControls2D />
<UndoRedo2DControls />
<TransformControl />
</>

View File

@@ -83,7 +83,7 @@ function MoveControls2D({
if (keyCombination === "G") {
if (selectedPoints.length > 0) {
moveAssets();
movePoints();
}
}
@@ -164,7 +164,7 @@ function MoveControls2D({
return new THREE.Vector3().subVectors(pointPosition, hitPoint);
}, []);
const moveAssets = useCallback(() => {
const movePoints = (() => {
if (selectedPoints.length === 0) return;
const states: Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }> = {};
@@ -192,9 +192,9 @@ function MoveControls2D({
setMovedObjects(selectedPoints);
setIsMoving(true);
}, [selectedPoints, camera, pointer, plane, raycaster, calculateDragOffset]);
});
const resetToInitialPositions = useCallback(() => {
const resetToInitialPositions = () => {
setTimeout(() => {
movedObjects.forEach((movedPoint: THREE.Object3D) => {
if (movedPoint.userData.pointUuid && initialStates[movedPoint.uuid]) {
@@ -218,7 +218,7 @@ function MoveControls2D({
}
});
}, 0)
}, [movedObjects, initialStates, setAislePosition, setWallPosition, setFloorPosition, setZonePosition]);
};
const placeMovedAssets = () => {
if (movedObjects.length === 0) return;

View File

@@ -1,5 +1,5 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { SkeletonUtils } from "three-stdlib";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
@@ -10,20 +10,18 @@ import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
const CopyPasteControls3D = ({
copiedObjects,
setCopiedObjects,
pastedObjects,
setpastedObjects,
selectionGroup,
setDuplicatedObjects,
movedObjects,
setMovedObjects,
rotatedObjects,
setRotatedObjects,
boundingBoxRef
}: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
@@ -33,31 +31,67 @@ const CopyPasteControls3D = ({
const { assetStore, eventStore } = useSceneContext();
const { addEvent } = eventStore();
const { projectId } = useParams();
const { assets, addAsset } = assetStore();
const { assets, addAsset, setPosition, updateAsset, removeAsset, getAssetById } = assetStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
const [isPasting, setIsPasting] = useState(false);
const [relativePositions, setRelativePositions] = useState<THREE.Vector3[]>([]);
const [centerOffset, setCenterOffset] = useState<THREE.Vector3 | null>(null);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
left: false,
right: false,
});
const calculateRelativePositions = useCallback((objects: THREE.Object3D[]) => {
if (objects.length === 0) return { center: new THREE.Vector3(), relatives: [] };
const box = new THREE.Box3();
objects.forEach(obj => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
const relatives = objects.map(obj => {
const relativePos = new THREE.Vector3().subVectors(obj.position, center);
return relativePos;
});
return { center, relatives };
}, []);
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
let isPointerMoving = false;
const onPointerDown = () => {
isMoving = false;
const onPointerDown = (event: PointerEvent) => {
isPointerMoving = false;
if (event.button === 0) mouseButtonsDown.current.left = true;
if (event.button === 2) mouseButtonsDown.current.right = true;
};
const onPointerMove = () => {
isMoving = true;
isPointerMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && pastedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
if (event.button === 0) mouseButtonsDown.current.left = false;
if (event.button === 2) mouseButtonsDown.current.right = false;
if (!isPointerMoving && pastedObjects.length > 0 && event.button === 0) {
event.preventDefault();
addPastedObjects();
}
if (!isPointerMoving && pastedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
pastedObjects.forEach((obj: THREE.Object3D) => {
removeAsset(obj.userData.modelUuid);
});
}
};
const onKeyDown = (event: KeyboardEvent) => {
@@ -69,6 +103,13 @@ const CopyPasteControls3D = ({
if (keyCombination === "Ctrl+V" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
pasteCopiedObjects();
}
if (keyCombination === "ESCAPE" && pastedObjects.length > 0) {
event.preventDefault();
clearSelection();
pastedObjects.forEach((obj: THREE.Object3D) => {
removeAsset(obj.userData.modelUuid);
});
}
};
if (!toggleView) {
@@ -84,27 +125,23 @@ const CopyPasteControls3D = ({
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [assets, camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, movedObjects, socket, rotatedObjects]);
useFrame(() => {
if (pastedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
pastedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
}
}
if (!isPasting || pastedObjects.length === 0) return;
if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) return;
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit && centerOffset) {
pastedObjects.forEach((pastedObject: THREE.Object3D, index: number) => {
const model = scene.getObjectByProperty("uuid", pastedObject.userData.modelUuid);
if (!model) return;
const newPos = new THREE.Vector3().addVectors(hit, relativePositions[index]);
model.position.set(newPos.x, 0, newPos.z);
});
}
});
@@ -127,31 +164,41 @@ const CopyPasteControls3D = ({
const pasteCopiedObjects = () => {
if (copiedObjects.length > 0 && pastedObjects.length === 0) {
const newClones = copiedObjects.map((obj: THREE.Object3D) => {
const clone = obj.clone();
clone.position.copy(obj.position);
const { center, relatives } = calculateRelativePositions(copiedObjects);
setRelativePositions(relatives);
const newPastedObjects = copiedObjects.map((obj: any) => {
const clone = SkeletonUtils.clone(obj);
clone.userData.modelUuid = THREE.MathUtils.generateUUID();
return clone;
});
selectionGroup.current.add(...newClones);
setpastedObjects([...newClones]);
setSelectedAssets([...newClones]);
const intersectionPoint = new THREE.Vector3();
setpastedObjects(newPastedObjects);
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
const intersectionPoint = new THREE.Vector3();
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
newClones.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
}
if (hit) {
const offset = new THREE.Vector3().subVectors(center, hit);
setCenterOffset(offset);
setIsPasting(true);
newPastedObjects.forEach((obj: THREE.Object3D, index: number) => {
const initialPos = new THREE.Vector3().addVectors(hit, relatives[index]);
const asset: Asset = {
modelUuid: obj.userData.modelUuid,
modelName: obj.userData.modelName,
assetId: obj.userData.assetId,
position: initialPos.toArray(),
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
isLocked: false,
isVisible: true,
isCollidable: false,
opacity: 0.5,
};
addAsset(asset);
});
}
}
};
@@ -159,33 +206,35 @@ const CopyPasteControls3D = ({
const addPastedObjects = () => {
if (pastedObjects.length === 0) return;
pastedObjects.forEach(async (obj: THREE.Object3D) => {
if (obj) {
const worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
obj.position.copy(worldPosition);
pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => {
if (pastedAsset) {
const assetUuid = pastedAsset.userData.modelUuid;
const asset = getAssetById(assetUuid);
const model = scene.getObjectByProperty("uuid", pastedAsset.userData.modelUuid);
if (!asset || !model) return;
const position = new THREE.Vector3().copy(model.position);
const newFloorItem: Types.FloorItemType = {
modelUuid: THREE.MathUtils.generateUUID(),
modelName: obj.userData.modelName,
assetId: obj.userData.assetId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
modelUuid: pastedAsset.userData.modelUuid,
modelName: pastedAsset.userData.modelName,
assetId: pastedAsset.userData.assetId,
position: [position.x, position.y, position.z],
rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] },
isLocked: false,
isVisible: true,
};
let updatedEventData = null;
if (obj.userData.eventData) {
updatedEventData = JSON.parse(JSON.stringify(obj.userData.eventData));
if (pastedAsset.userData.eventData) {
updatedEventData = JSON.parse(JSON.stringify(pastedAsset.userData.eventData));
updatedEventData.modelUuid = newFloorItem.modelUuid;
const eventData: any = {
type: obj.userData.eventData.type,
type: pastedAsset.userData.eventData.type,
};
if (obj.userData.eventData.type === "Conveyor") {
if (pastedAsset.userData.eventData.type === "Conveyor") {
const ConveyorEvent: ConveyorEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -217,7 +266,7 @@ const CopyPasteControls3D = ({
rotation: point.rotation
}));
} else if (obj.userData.eventData.type === "Vehicle") {
} else if (pastedAsset.userData.eventData.type === "Vehicle") {
const vehicleEvent: VehicleEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -255,7 +304,7 @@ const CopyPasteControls3D = ({
rotation: vehicleEvent.point.rotation
};
} else if (obj.userData.eventData.type === "ArmBot") {
} else if (pastedAsset.userData.eventData.type === "ArmBot") {
const roboticArmEvent: RoboticArmEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -289,7 +338,7 @@ const CopyPasteControls3D = ({
rotation: roboticArmEvent.point.rotation
};
} else if (obj.userData.eventData.type === "StaticMachine") {
} else if (pastedAsset.userData.eventData.type === "StaticMachine") {
const machineEvent: MachineEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -317,7 +366,7 @@ const CopyPasteControls3D = ({
position: machineEvent.point.position,
rotation: machineEvent.point.rotation
};
} else if (obj.userData.eventData.type === "Storage") {
} else if (pastedAsset.userData.eventData.type === "Storage") {
const storageEvent: StorageEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -344,7 +393,7 @@ const CopyPasteControls3D = ({
position: storageEvent.point.position,
rotation: storageEvent.point.rotation
};
} else if (obj.userData.eventData.type === "Human") {
} else if (pastedAsset.userData.eventData.type === "Human") {
const humanEvent: HumanEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -363,6 +412,7 @@ const CopyPasteControls3D = ({
actionName: "Action 1",
actionType: "worker",
loadCapacity: 1,
assemblyCount: 1,
loadCount: 1,
processTime: 10,
triggers: []
@@ -379,28 +429,14 @@ const CopyPasteControls3D = ({
}
newFloorItem.eventData = eventData;
//REST
// await setAssetsApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
position: [position.x, 0, position.z],
rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
@@ -410,54 +446,36 @@ const CopyPasteControls3D = ({
projectId
};
// console.log('data: ', data);
socket.emit("v1:model-asset:add", data);
//REST
obj.userData = {
name: newFloorItem.modelName,
modelId: newFloorItem.assetId,
modelUuid: newFloorItem.modelUuid,
eventData: JSON.parse(JSON.stringify(eventData))
};
// await setAssetsApi(data);
//SOCKET
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
position: data.position,
position: [position.x, 0, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
eventData: data.eventData
}
addAsset(asset);
};
updateAsset(asset.modelUuid, asset);
} else {
//REST
// await setAssetsApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
position: [position.x, 0, position.z],
rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
@@ -466,22 +484,21 @@ const CopyPasteControls3D = ({
userId
};
// console.log('data: ', data);
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
position: data.position,
position: [position.x, 0, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
}
};
addAsset(asset);
updateAsset(asset.modelUuid, asset);
}
}
});
@@ -491,15 +508,14 @@ const CopyPasteControls3D = ({
};
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
setIsPasting(false);
setCenterOffset(null);
};
return null;
};

View File

@@ -1,5 +1,5 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { SkeletonUtils } from "three-stdlib";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
@@ -10,18 +10,16 @@ import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
const DuplicationControls3D = ({
duplicatedObjects,
setDuplicatedObjects,
setpastedObjects,
selectionGroup,
movedObjects,
setMovedObjects,
rotatedObjects,
setRotatedObjects,
boundingBoxRef
}: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
@@ -31,39 +29,71 @@ const DuplicationControls3D = ({
const { assetStore, eventStore } = useSceneContext();
const { addEvent } = eventStore();
const { projectId } = useParams();
const { assets, addAsset } = assetStore();
const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
const [isDuplicating, setIsDuplicating] = useState(false);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
left: false,
right: false,
});
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
const pointPosition = new THREE.Vector3().copy(point.position);
return new THREE.Vector3().subVectors(pointPosition, hitPoint);
}, []);
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
let isPointerMoving = false;
const onPointerDown = () => {
isMoving = false;
const onPointerDown = (event: PointerEvent) => {
isPointerMoving = false;
if (event.button === 0) mouseButtonsDown.current.left = true;
if (event.button === 2) mouseButtonsDown.current.right = true;
};
const onPointerMove = () => {
isMoving = true;
isPointerMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && duplicatedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
if (event.button === 0) mouseButtonsDown.current.left = false;
if (event.button === 2) mouseButtonsDown.current.right = false;
if (!isPointerMoving && duplicatedObjects.length > 0 && event.button === 0) {
event.preventDefault();
addDuplicatedAssets();
}
if (!isPointerMoving && duplicatedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
duplicatedObjects.forEach((obj: THREE.Object3D) => {
removeAsset(obj.userData.modelUuid);
});
}
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "Ctrl+D" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
if (keyCombination === "Ctrl+D" && movedObjects.length === 0 && rotatedObjects.length === 0) {
duplicateSelection();
}
if (keyCombination === "ESCAPE" && duplicatedObjects.length > 0) {
event.preventDefault();
clearSelection();
duplicatedObjects.forEach((obj: THREE.Object3D) => {
removeAsset(obj.userData.modelUuid);
});
}
};
if (!toggleView) {
@@ -79,87 +109,133 @@ const DuplicationControls3D = ({
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects]);
useFrame(() => {
if (duplicatedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
duplicatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
if (!isDuplicating || duplicatedObjects.length === 0) return;
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit) {
if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) {
const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid);
if (model) {
const newOffset = calculateDragOffset(model, intersectionPoint);
setDragOffset(newOffset);
}
return;
}
if (dragOffset) {
const adjustedHit = new THREE.Vector3().addVectors(intersectionPoint, dragOffset);
duplicatedObjects.forEach((duplicatedObject: THREE.Object3D) => {
if (duplicatedObject.userData.modelUuid) {
const initialPosition = initialPositions[duplicatedObject.userData.modelUuid];
if (initialPosition) {
const relativeOffset = new THREE.Vector3().subVectors(
initialPosition,
initialPositions[duplicatedObjects[0].userData.modelUuid]
);
const model = scene.getObjectByProperty("uuid", duplicatedObject.userData.modelUuid);
const newPosition = new THREE.Vector3().addVectors(adjustedHit, relativeOffset);
const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z];
if (model) {
model.position.set(...positionArray);
}
}
}
});
}
}
});
const duplicateSelection = () => {
const duplicateSelection = useCallback(() => {
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
const newClones = selectedAssets.map((asset: any) => {
if (asset.userData.rigidBodyRef) {
let userData = { ...asset.userData };
const positions: Record<string, THREE.Vector3> = {};
const newDuplicatedObjects = selectedAssets.map((obj: any) => {
if (obj.userData.rigidBodyRef) {
let userData = { ...obj.userData };
delete userData.rigidBodyRef;
asset.userData = userData;
obj.userData = userData;
}
const clone = SkeletonUtils.clone(asset);
clone.position.copy(asset.position);
const clone = SkeletonUtils.clone(obj);
clone.userData.modelUuid = THREE.MathUtils.generateUUID();
positions[clone.userData.modelUuid] = new THREE.Vector3().copy(obj.position);
return clone;
});
selectionGroup.current.add(...newClones);
setDuplicatedObjects(newClones);
setDuplicatedObjects(newDuplicatedObjects);
setInitialPositions(positions);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
const intersectionPoint = new THREE.Vector3();
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
if (hit) {
const offset = calculateDragOffset(selectedAssets[0], hit);
setDragOffset(offset);
}
setIsDuplicating(true);
newDuplicatedObjects.forEach((obj: THREE.Object3D) => {
const asset: Asset = {
modelUuid: obj.userData.modelUuid,
modelName: obj.userData.modelName,
assetId: obj.userData.assetId,
position: [obj.position.x, 0, obj.position.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
isLocked: false,
isVisible: true,
isCollidable: false,
opacity: 0.5,
};
addAsset(asset);
});
}
};
}, [selectedAssets, duplicatedObjects]);
const addDuplicatedAssets = () => {
if (duplicatedObjects.length === 0) return;
duplicatedObjects.forEach(async (obj: THREE.Object3D) => {
if (obj) {
const worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
obj.position.copy(worldPosition);
duplicatedObjects.forEach(async (duplicatedAsset: THREE.Object3D) => {
if (duplicatedAsset) {
const assetUuid = duplicatedAsset.userData.modelUuid;
const asset = getAssetById(assetUuid);
const model = scene.getObjectByProperty("uuid", duplicatedAsset.userData.modelUuid);
if (!asset || !model) return;
const position = new THREE.Vector3().copy(model.position);
const newFloorItem: Types.FloorItemType = {
modelUuid: THREE.MathUtils.generateUUID(),
modelName: obj.userData.modelName,
assetId: obj.userData.assetId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
modelUuid: duplicatedAsset.userData.modelUuid,
modelName: duplicatedAsset.userData.modelName,
assetId: duplicatedAsset.userData.assetId,
position: [position.x, position.y, position.z],
rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] },
isLocked: false,
isVisible: true,
};
let updatedEventData = null;
if (obj.userData.eventData) {
updatedEventData = JSON.parse(JSON.stringify(obj.userData.eventData));
if (duplicatedAsset.userData.eventData) {
updatedEventData = JSON.parse(JSON.stringify(duplicatedAsset.userData.eventData));
updatedEventData.modelUuid = newFloorItem.modelUuid;
const eventData: any = {
type: obj.userData.eventData.type,
type: duplicatedAsset.userData.eventData.type,
};
if (obj.userData.eventData.type === "Conveyor") {
if (duplicatedAsset.userData.eventData.type === "Conveyor") {
const ConveyorEvent: ConveyorEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -191,7 +267,7 @@ const DuplicationControls3D = ({
rotation: point.rotation
}));
} else if (obj.userData.eventData.type === "Vehicle") {
} else if (duplicatedAsset.userData.eventData.type === "Vehicle") {
const vehicleEvent: VehicleEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -229,7 +305,7 @@ const DuplicationControls3D = ({
rotation: vehicleEvent.point.rotation
};
} else if (obj.userData.eventData.type === "ArmBot") {
} else if (duplicatedAsset.userData.eventData.type === "ArmBot") {
const roboticArmEvent: RoboticArmEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -263,7 +339,7 @@ const DuplicationControls3D = ({
rotation: roboticArmEvent.point.rotation
};
} else if (obj.userData.eventData.type === "StaticMachine") {
} else if (duplicatedAsset.userData.eventData.type === "StaticMachine") {
const machineEvent: MachineEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -291,7 +367,7 @@ const DuplicationControls3D = ({
position: machineEvent.point.position,
rotation: machineEvent.point.rotation
};
} else if (obj.userData.eventData.type === "Storage") {
} else if (duplicatedAsset.userData.eventData.type === "Storage") {
const storageEvent: StorageEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -318,7 +394,7 @@ const DuplicationControls3D = ({
position: storageEvent.point.position,
rotation: storageEvent.point.rotation
};
} else if (obj.userData.eventData.type === "Human") {
} else if (duplicatedAsset.userData.eventData.type === "Human") {
const humanEvent: HumanEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -337,6 +413,7 @@ const DuplicationControls3D = ({
actionName: "Action 1",
actionType: "worker",
loadCapacity: 1,
assemblyCount: 1,
loadCount: 1,
processTime: 10,
triggers: []
@@ -354,121 +431,92 @@ const DuplicationControls3D = ({
newFloorItem.eventData = eventData;
//REST
// await setAssetsApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
position: [position.x, position.y, position.z],
rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
eventData: eventData,
versionId: selectedVersion?.versionId || '',
projectId,
userId
userId,
projectId
};
// console.log('data: ', data);
//REST
// await setAssetsApi(data);
//SOCKET
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
position: data.position,
position: [position.x, position.y, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
eventData: data.eventData
}
addAsset(asset);
};
updateAsset(asset.modelUuid, asset);
} else {
//REST
// await setAssetsApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
position: [position.x, position.y, position.z],
rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
versionId: selectedVersion?.versionId || '',
userId,
projectId
projectId,
userId
};
// console.log('data: ', data);/
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
position: data.position,
position: [position.x, position.y, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
}
};
addAsset(asset);
updateAsset(asset.modelUuid, asset);
}
}
});
echo.success("Object duplicated!");
clearSelection();
}
};
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
setIsDuplicating(false);
setDragOffset(null);
};
return null;
};

View File

@@ -1,5 +1,5 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
@@ -22,7 +22,6 @@ function MoveControls3D({
setpastedObjects,
duplicatedObjects,
setDuplicatedObjects,
selectionGroup,
rotatedObjects,
setRotatedObjects,
boundingBoxRef,
@@ -39,11 +38,16 @@ function MoveControls3D({
const { userId, organization } = getUserData();
const { projectId } = useParams();
const { assetStore, eventStore, productStore } = useSceneContext();
const { updateAsset } = assetStore();
const AssetGroup = useRef<THREE.Group | undefined>(undefined);
const { updateAsset, getAssetById } = assetStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
const [initialStates, setInitialStates] = useState<Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }>>({});
const [isMoving, setIsMoving] = useState(false);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
const updateBackend = (
productName: string,
productUuid: string,
@@ -65,47 +69,39 @@ function MoveControls3D({
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
const itemsGroup: any = scene.getObjectByName("Asset Group");
AssetGroup.current = itemsGroup;
if (!AssetGroup.current) {
console.error("Asset Group not found in the scene.");
return;
}
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
let isPointerMoving = false;
const onPointerMove = () => {
isMoving = true;
isPointerMoving = true;
};
const onKeyUp = (event: KeyboardEvent) => {
const isModifierKey = event.key === "Control" || event.key === "Shift";
const isModifierKey = (!event.shiftKey && !event.ctrlKey);
if (isModifierKey) {
if (isModifierKey && keyEvent !== "") {
setKeyEvent("");
}
};
const onPointerDown = (event: PointerEvent) => {
isPointerMoving = false;
if (event.button === 0) mouseButtonsDown.current.left = true;
if (event.button === 2) mouseButtonsDown.current.right = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && movedObjects.length > 0 && event.button === 0) {
if (event.button === 0) mouseButtonsDown.current.left = false;
if (event.button === 2) mouseButtonsDown.current.right = false;
if (!isPointerMoving && movedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeMovedAssets();
}
if (!isMoving && movedObjects.length > 0 && event.button === 2) {
if (!isPointerMoving && movedObjects.length > 0 && event.button === 2) {
event.preventDefault();
resetToInitialPositions();
clearSelection();
movedObjects.forEach((asset: any) => {
if (AssetGroup.current) {
AssetGroup.current.attach(asset);
}
});
setMovedObjects([]);
}
setKeyEvent("");
@@ -117,10 +113,12 @@ function MoveControls3D({
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0)
return;
if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
setKeyEvent(keyCombination);
} else {
setKeyEvent("");
if (keyCombination !== keyEvent) {
if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
setKeyEvent(keyCombination);
} else {
setKeyEvent("");
}
}
if (keyCombination === "G") {
@@ -131,14 +129,8 @@ function MoveControls3D({
if (keyCombination === "ESCAPE") {
event.preventDefault();
resetToInitialPositions();
clearSelection();
movedObjects.forEach((asset: any) => {
if (AssetGroup.current) {
AssetGroup.current.attach(asset);
}
});
setMovedObjects([]);
}
};
@@ -158,118 +150,166 @@ function MoveControls3D({
canvasElement.removeEventListener("keydown", onKeyDown);
canvasElement?.removeEventListener("keyup", onKeyUp);
};
}, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent,]);
}, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent]);
let moveSpeed = keyEvent === "Ctrl" || "Ctrl+Shift" ? 1 : 0.25;
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
const pointPosition = new THREE.Vector3().copy(point.position);
return new THREE.Vector3().subVectors(pointPosition, hitPoint);
}, []);
const resetToInitialPositions = useCallback(() => {
setTimeout(() => {
movedObjects.forEach((movedObject: THREE.Object3D) => {
if (movedObject.userData.modelUuid && initialStates[movedObject.uuid]) {
const initialState = initialStates[movedObject.uuid];
const positionArray: [number, number, number] = [
initialState.position.x,
initialState.position.y,
initialState.position.z
];
updateAsset(movedObject.userData.modelUuid, {
position: positionArray,
rotation: [
initialState.rotation?.x || 0,
initialState.rotation?.y || 0,
initialState.rotation?.z || 0
],
});
movedObject.position.copy(initialState.position);
if (initialState.rotation) {
movedObject.rotation.copy(initialState.rotation);
}
}
});
}, 0)
}, [movedObjects, initialStates, updateAsset]);
useFrame(() => {
if (movedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
let point = raycaster.ray.intersectPlane(plane, intersectionPoint);
const floorsGroup = scene.getObjectByName("Floors-Group") as Types.Group | null;
const floorChildren = floorsGroup?.children ?? [];
const floorIntersections = raycaster.intersectObjects([...floorChildren], true);
const intersectedFloor = floorIntersections.find((intersect) => intersect.object.name.includes("Floor"));
if (!isMoving || movedObjects.length === 0) return;
if (intersectedFloor && selectedAssets.length === 1) {
if (intersectedFloor.object.userData.floorUuid) {
point = new THREE.Vector3(intersectedFloor.point.x, intersectedFloor.object.userData.floorDepth, intersectedFloor.point.z);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit) {
if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) {
if (movedObjects[0]) {
const newOffset = calculateDragOffset(movedObjects[0], intersectionPoint);
setDragOffset(newOffset);
}
return;
}
if (point) {
let targetX = point.x;
let targetY = point.y;
let targetZ = point.z;
if (dragOffset) {
const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset);
if (keyEvent === "Ctrl") {
targetX = snapControls(targetX, "Ctrl");
targetZ = snapControls(targetZ, "Ctrl");
let moveDistance = keyEvent.includes("Shift") ? 0.05 : 1;
const initialBasePosition = initialPositions[movedObjects[0].uuid];
const positionDifference = new THREE.Vector3().subVectors(rawBasePosition, initialBasePosition);
let adjustedDifference = positionDifference.multiplyScalar(moveDistance);
const baseNewPosition = new THREE.Vector3().addVectors(initialBasePosition, adjustedDifference);
if (keyEvent.includes("Ctrl")) {
baseNewPosition.x = snapControls(baseNewPosition.x, keyEvent);
baseNewPosition.z = snapControls(baseNewPosition.z, keyEvent);
}
// else if (keyEvent === "Ctrl+Shift") {
// targetX = snapControls(targetX, "Ctrl+Shift");
// targetZ = snapControls(targetZ, "Ctrl+Shift");
// } else if (keyEvent === "Shift") {
// targetX = snapControls(targetX, "Shift");
// targetZ = snapControls(targetZ, "Shift");
// } else {
// }
movedObjects.forEach((movedAsset: THREE.Object3D) => {
if (movedAsset.userData.modelUuid) {
const initialPosition = initialPositions[movedAsset.userData.modelUuid];
if (initialPosition) {
const relativeOffset = new THREE.Vector3().subVectors(
initialPosition,
initialPositions[movedObjects[0].uuid]
);
const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid);
const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset);
const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z];
if (model) {
model.position.set(...positionArray);
}
}
}
});
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current.getWorldPosition(position);
selectionGroup.current.position.lerp(
new THREE.Vector3(
targetX - (position.x - selectionGroup.current.position.x),
targetY - (position.y - selectionGroup.current.position.y),
targetZ - (position.z - selectionGroup.current.position.z)
),
moveSpeed
);
} else {
const box = new THREE.Box3();
movedObjects.forEach((obj: THREE.Object3D) =>
box.expandByObject(obj)
);
movedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.lerp(
new THREE.Vector3(
targetX - (center.x - selectionGroup.current.position.x),
selectionGroup.current.position.y,
targetZ - (center.z - selectionGroup.current.position.z)
),
moveSpeed
);
}
}
}
});
const moveAssets = () => {
setMovedObjects(selectedAssets);
selectedAssets.forEach((asset: any) => {
selectionGroup.current.attach(asset);
const states: Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }> = {};
const positions: Record<string, THREE.Vector3> = {};
selectedAssets.forEach((asset: THREE.Object3D) => {
states[asset.uuid] = {
position: new THREE.Vector3().copy(asset.position),
rotation: asset.rotation ? new THREE.Euler().copy(asset.rotation) : undefined
};
positions[asset.uuid] = new THREE.Vector3().copy(asset.position);
});
setInitialStates(states);
setInitialPositions(positions);
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit && selectedAssets[0]) {
const offset = calculateDragOffset(selectedAssets[0], hit);
setDragOffset(offset);
}
setMovedObjects(selectedAssets);
setIsMoving(true);
};
const placeMovedAssets = () => {
if (movedObjects.length === 0) return;
movedObjects.forEach(async (obj: THREE.Object3D) => {
if (obj && AssetGroup.current) {
let worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
if (worldPosition.y < 0) {
worldPosition.y = 0;
}
selectionGroup.current.remove(obj);
obj.position.copy(worldPosition);
movedObjects.forEach(async (movedAsset: THREE.Object3D) => {
if (movedAsset) {
const assetUuid = movedAsset.userData.modelUuid;
const asset = getAssetById(assetUuid);
const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid);
if (!asset || !model) return;
const position = new THREE.Vector3().copy(model.position);
const newFloorItem: Types.FloorItemType = {
modelUuid: obj.userData.modelUuid,
modelName: obj.userData.modelName,
assetId: obj.userData.assetId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
modelUuid: movedAsset.userData.modelUuid,
modelName: movedAsset.userData.modelName,
assetId: movedAsset.userData.assetId,
position: [position.x, position.y, position.z],
rotation: { x: movedAsset.rotation.x, y: movedAsset.rotation.y, z: movedAsset.rotation.z },
isLocked: false,
isVisible: true,
};
if (obj.userData.eventData) {
const eventData = eventStore.getState().getEventByModelUuid(obj.userData.modelUuid);
const productData = productStore.getState().getEventByModelUuid(selectedProduct.productUuid, obj.userData.modelUuid);
if (movedAsset.userData.eventData) {
const eventData = eventStore.getState().getEventByModelUuid(movedAsset.userData.modelUuid);
const productData = productStore.getState().getEventByModelUuid(selectedProduct.productUuid, movedAsset.userData.modelUuid);
if (eventData) {
eventStore.getState().updateEvent(obj.userData.modelUuid, {
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
eventStore.getState().updateEvent(movedAsset.userData.modelUuid, {
position: [position.x, position.y, position.z],
rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z],
});
}
@@ -278,10 +318,10 @@ function MoveControls3D({
.getState()
.updateEvent(
selectedProduct.productUuid,
obj.userData.modelUuid,
movedAsset.userData.modelUuid,
{
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
position: [position.x, position.y, position.z],
rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z],
}
);
@@ -298,33 +338,18 @@ function MoveControls3D({
}
}
updateAsset(obj.userData.modelUuid, {
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
updateAsset(movedAsset.userData.modelUuid, {
position: asset.position,
rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z],
});
//REST
// await setAssetsApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
rotation: { x: movedAsset.rotation.x, y: movedAsset.rotation.y, z: movedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
@@ -333,22 +358,22 @@ function MoveControls3D({
userId
};
// console.log('data: ', data);
socket.emit("v1:model-asset:add", data);
//REST
AssetGroup.current.add(obj);
// await setAssetsApi(data);
//SOCKET
socket.emit("v1:model-asset:add", data);
}
});
echo.success("Object moved!");
setIsMoving(false);
clearSelection();
};
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
@@ -365,4 +390,4 @@ function MoveControls3D({
);
}
export default MoveControls3D;
export default MoveControls3D;

View File

@@ -1,5 +1,5 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
@@ -20,11 +20,9 @@ function RotateControls3D({
pastedObjects,
setpastedObjects,
duplicatedObjects,
setDuplicatedObjects,
selectionGroup
setDuplicatedObjects
}: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { camera, gl, scene, pointer, raycaster } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
@@ -36,26 +34,33 @@ function RotateControls3D({
const { projectId } = useParams();
const { assetStore, eventStore, productStore } = useSceneContext();
const { updateAsset } = assetStore();
const AssetGroup = useRef<THREE.Group | undefined>(undefined);
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const updateBackend = (
const [initialRotations, setInitialRotations] = useState<Record<string, THREE.Euler>>({});
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
const [isRotating, setIsRotating] = useState(false);
const prevPointerPosition = useRef<THREE.Vector2 | null>(null);
const rotationCenter = useRef<THREE.Vector3 | null>(null);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
left: false,
right: false,
});
const updateBackend = useCallback((
productName: string,
productUuid: string,
projectId: string,
eventData: EventsSchema
) => {
upsertProductOrEventApi({
productName: productName,
productUuid: productUuid,
projectId: projectId,
productName,
productUuid,
projectId,
eventDatas: eventData,
versionId: selectedVersion?.versionId || '',
})
}
const prevPointerPosition = useRef<THREE.Vector2 | null>(null);
});
}, [selectedVersion]);
useEffect(() => {
if (!camera || !scene || toggleView) return;
@@ -63,45 +68,37 @@ function RotateControls3D({
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
const itemsGroup: any = scene.getObjectByName("Asset Group");
AssetGroup.current = itemsGroup;
if (!AssetGroup.current) {
console.error("Asset Group not found in the scene.");
return;
}
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
let isPointerMoving = false;
const onPointerMove = () => {
isMoving = true;
isPointerMoving = true;
};
const onPointerDown = (event: PointerEvent) => {
isPointerMoving = false;
if (event.button === 0) mouseButtonsDown.current.left = true;
if (event.button === 2) mouseButtonsDown.current.right = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && rotatedObjects.length > 0 && event.button === 0) {
if (event.button === 0) mouseButtonsDown.current.left = false;
if (event.button === 2) mouseButtonsDown.current.right = false;
if (!isPointerMoving && rotatedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeRotatedAssets();
}
if (!isMoving && rotatedObjects.length > 0 && event.button === 2) {
if (!isPointerMoving && rotatedObjects.length > 0 && event.button === 2) {
event.preventDefault();
resetToInitialRotations();
clearSelection();
rotatedObjects.forEach((asset: any) => {
if (AssetGroup.current) {
AssetGroup.current.attach(asset);
}
});
setRotatedObjects([]);
}
};
const onKeyDown = (event: KeyboardEvent) => {
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || movedObjects.length > 0) return;
if (event.key.toLowerCase() === "r") {
if (selectedAssets.length > 0) {
rotateAssets();
@@ -109,15 +106,8 @@ function RotateControls3D({
}
if (event.key.toLowerCase() === "escape") {
event.preventDefault();
resetToInitialRotations();
clearSelection();
rotatedObjects.forEach((asset: any) => {
if (AssetGroup.current) {
AssetGroup.current.attach(asset);
}
});
setRotatedObjects([]);
}
};
@@ -135,41 +125,87 @@ function RotateControls3D({
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, rotatedObjects, movedObjects]);
}, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects]);
const resetToInitialRotations = useCallback(() => {
rotatedObjects.forEach((obj: THREE.Object3D) => {
const uuid = obj.uuid;
if (obj.userData.modelUuid) {
const initialRotation = initialRotations[uuid];
const initialPosition = initialPositions[uuid];
if (initialRotation && initialPosition) {
const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,];
const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,];
updateAsset(obj.userData.modelUuid, {
rotation: rotationArray,
position: positionArray,
});
obj.rotation.copy(initialRotation);
obj.position.copy(initialPosition);
}
}
});
}, [rotatedObjects, initialRotations, initialPositions, updateAsset]);
useFrame(() => {
if (rotatedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (!isRotating || rotatedObjects.length === 0) return;
if (point && prevPointerPosition.current) {
const box = new THREE.Box3();
rotatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
const delta = new THREE.Vector3().subVectors(point, center);
const prevPointerPosition3D = new THREE.Vector3(prevPointerPosition.current.x, 0, prevPointerPosition.current.y);
const angle = Math.atan2(delta.z, delta.x) - Math.atan2(prevPointerPosition3D.z - center.z, prevPointerPosition3D.x - center.x);
selectionGroup.current.rotation.y += -angle;
selectionGroup.current.position.sub(center);
selectionGroup.current.position.applyAxisAngle(new THREE.Vector3(0, 1, 0), -angle);
selectionGroup.current.position.add(center);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) {
if (point) {
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
return;
}
if (point && prevPointerPosition.current && rotationCenter.current) {
const center = rotationCenter.current;
const currentAngle = Math.atan2(point.z - center.z, point.x - center.x);
const prevAngle = Math.atan2(
prevPointerPosition.current.y - center.z,
prevPointerPosition.current.x - center.x
);
const angleDelta = prevAngle - currentAngle;
const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta);
rotatedObjects.forEach((obj: THREE.Object3D) => {
if (obj.userData.modelUuid) {
const relativePosition = new THREE.Vector3().subVectors(obj.position, center);
relativePosition.applyMatrix4(rotationMatrix);
obj.position.copy(center).add(relativePosition);
obj.rotateOnWorldAxis(new THREE.Vector3(0, 1, 0), angleDelta);
}
});
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
});
const rotateAssets = () => {
const rotateAssets = useCallback(() => {
const rotations: Record<string, THREE.Euler> = {};
const positions: Record<string, THREE.Vector3> = {};
const box = new THREE.Box3();
selectedAssets.forEach((asset: any) => box.expandByObject(asset));
selectedAssets.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
rotationCenter.current = center;
selectedAssets.forEach((obj: THREE.Object3D) => {
rotations[obj.uuid] = new THREE.Euler().copy(obj.rotation);
positions[obj.uuid] = new THREE.Vector3().copy(obj.position);
});
setInitialRotations(rotations);
setInitialPositions(positions);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
@@ -179,56 +215,54 @@ function RotateControls3D({
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
selectedAssets.forEach((asset: any) => {
selectionGroup.current.attach(asset);
});
setRotatedObjects(selectedAssets);
};
setIsRotating(true);
}, [selectedAssets, camera, pointer, raycaster, plane]);
const placeRotatedAssets = () => {
const placeRotatedAssets = useCallback(() => {
if (rotatedObjects.length === 0) return;
rotatedObjects.forEach(async (obj: THREE.Object3D) => {
if (obj && AssetGroup.current) {
const worldPosition = new THREE.Vector3();
const worldQuaternion = new THREE.Quaternion();
rotatedObjects.forEach((obj: THREE.Object3D) => {
if (obj && obj.userData.modelUuid) {
const rotationArray: [number, number, number] = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
obj.getWorldPosition(worldPosition);
obj.getWorldQuaternion(worldQuaternion);
selectionGroup.current.remove(obj);
obj.position.copy(worldPosition);
obj.quaternion.copy(worldQuaternion);
const positionArray: [number, number, number] = [obj.position.x, obj.position.y, obj.position.z];
const newFloorItem: Types.FloorItemType = {
modelUuid: obj.userData.modelUuid,
modelName: obj.userData.modelName,
assetId: obj.userData.assetId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
position: positionArray,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true
isVisible: true,
};
if (obj.userData.eventData) {
const eventData = eventStore.getState().getEventByModelUuid(obj.userData.modelUuid);
const productData = productStore.getState().getEventByModelUuid(selectedProductStore.getState().selectedProduct.productUuid, obj.userData.modelUuid);
const productData = productStore.getState().getEventByModelUuid(
selectedProduct.productUuid,
obj.userData.modelUuid
);
if (eventData) {
eventStore.getState().updateEvent(obj.userData.modelUuid, {
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
})
position: positionArray,
rotation: rotationArray,
});
}
if (productData) {
const event = productStore.getState().updateEvent(selectedProductStore.getState().selectedProduct.productUuid, obj.userData.modelUuid, {
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
})
if (event && organization) {
if (productData) {
const event = productStore.getState().updateEvent(
selectedProduct.productUuid,
obj.userData.modelUuid,
{
position: positionArray,
rotation: rotationArray,
}
);
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
@@ -242,25 +276,10 @@ function RotateControls3D({
}
updateAsset(obj.userData.modelUuid, {
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
position: positionArray,
rotation: rotationArray,
});
//REST
// await setAssetsApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
@@ -276,29 +295,29 @@ function RotateControls3D({
userId
};
// console.log('data: ', data);
socket.emit("v1:model-asset:add", data);
//REST
AssetGroup.current.add(obj);
// await setAssetsApi(data);
//SOCKET
socket.emit("v1:model-asset:add", data);
}
});
echo.success("Object rotated!");
setIsRotating(false);
clearSelection();
}
}, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]);
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
};
return null;
}
export default RotateControls3D
export default RotateControls3D;

View File

@@ -1,9 +1,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import * as THREE from "three";
import { useFrame, useThree } from "@react-three/fiber";
import { useThree } from "@react-three/fiber";
import { SelectionHelper } from "../selectionHelper";
import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox";
import * as Types from "../../../../../types/world/worldTypes";
import useModuleStore from "../../../../../store/useModuleStore";
import { useParams } from "react-router-dom";
@@ -13,7 +12,6 @@ import { useVersionContext } from "../../../../builder/version/versionContext";
import { useProductContext } from "../../../../simulation/products/productContext";
import { useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
import BoundingBox from "./boundingBoxHelper3D";
import DuplicationControls3D from "./duplicationControls3D";
import CopyPasteControls3D from "./copyPasteControls3D";
import MoveControls3D from "./moveControls3D";
@@ -23,7 +21,6 @@ import RotateControls3D from "./rotateControls3D";
const SelectionControls3D: React.FC = () => {
const { camera, controls, gl, scene, raycaster, pointer } = useThree();
const selectionGroup = useRef() as Types.RefGroup;
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const [movedObjects, setMovedObjects] = useState<THREE.Object3D[]>([]);
@@ -229,12 +226,6 @@ const SelectionControls3D: React.FC = () => {
}
}, [activeModule, toolMode, toggleView]);
useFrame(() => {
if (pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
selectionGroup.current.position.set(0, 0, 0);
}
});
const selectAssets = useCallback(() => {
selectionBox.endPoint.set(pointer.x, pointer.y, 0);
if (controls) (controls as any).enabled = true;
@@ -267,9 +258,6 @@ const SelectionControls3D: React.FC = () => {
}, [selectionBox, pointer, controls, selectedAssets, setSelectedAssets]);
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setSelectedAssets([]);
@@ -345,19 +333,13 @@ const SelectionControls3D: React.FC = () => {
return (
<>
<group name="SelectionGroup">
<group ref={selectionGroup} name="selectionAssetGroup">
<BoundingBox boundingBoxRef={boundingBoxRef} isPerAsset />
</group>
</group>
<MoveControls3D movedObjects={movedObjects} setMovedObjects={setMovedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
<MoveControls3D movedObjects={movedObjects} setMovedObjects={setMovedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} selectionGroup={selectionGroup} boundingBoxRef={boundingBoxRef} />
<RotateControls3D rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} />
<RotateControls3D rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} selectionGroup={selectionGroup} />
<DuplicationControls3D duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} setpastedObjects={setpastedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
<DuplicationControls3D duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} setpastedObjects={setpastedObjects} selectionGroup={selectionGroup} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
<CopyPasteControls3D copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} selectionGroup={selectionGroup} setDuplicatedObjects={setDuplicatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
<CopyPasteControls3D copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} setDuplicatedObjects={setDuplicatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
</>
);
};

View File

@@ -0,0 +1,355 @@
import { useParams } from "react-router-dom";
import { getUserData } from "../../../../../functions/getUserData";
import { useVersionContext } from "../../../../builder/version/versionContext";
import { useSceneContext } from "../../../sceneContext";
import { useSocketStore } from "../../../../../store/builder/store";
// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi";
// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi";
// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
// import { deleteZoneApi } from "../../../../../services/factoryBuilder/zone/deleteZoneApi";
// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
// import { deleteFloorApi } from "../../../../../services/factoryBuilder/floor/deleteFloorApi";
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
function useRedoHandler() {
const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
const { redo2D, peekRedo2D } = undoRedo2DStore();
const { addWall, removeWall, updateWall } = wallStore();
const { addFloor, removeFloor, updateFloor } = floorStore();
const { addZone, removeZone, updateZone } = zoneStore();
const { addAisle, removeAisle, updateAisle } = aisleStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
const { socket } = useSocketStore();
const handleRedo = () => {
const redoData = peekRedo2D();
if (!redoData) return;
if (redoData.type === 'Draw') {
const { actions } = redoData;
actions.forEach(action => {
const { actionType } = action;
if ('point' in action) {
const point = action.point;
if (actionType === 'Line-Create') {
handleCreate(point);
} else if (actionType === 'Line-Update') {
handleUpdate(point);
} else if (actionType === 'Line-Delete') {
handleRemove(point);
}
} else if ('points' in action) {
const points = action.points;
if (actionType === 'Lines-Create') {
points.forEach(handleCreate);
} else if (actionType === 'Lines-Update') {
points.forEach(handleUpdate);
} else if (actionType === 'Lines-Delete') {
points.forEach(handleRemove);
}
}
});
} else if (redoData.type === 'UI') {
// Handle UI actions if needed
}
redo2D();
};
const handleCreate = (point: UndoRedo2DDataTypeSchema) => {
switch (point.type) {
case 'Wall': createWallFromBackend(point.lineData); break;
case 'Floor': createFloorFromBackend(point.lineData); break;
case 'Zone': createZoneFromBackend(point.lineData); break;
case 'Aisle': createAisleFromBackend(point.lineData); break;
}
};
const handleRemove = (point: UndoRedo2DDataTypeSchema) => {
switch (point.type) {
case 'Wall': removeWallFromBackend(point.lineData.wallUuid); break;
case 'Floor': removeFloorFromBackend(point.lineData.floorUuid); break;
case 'Zone': removeZoneFromBackend(point.lineData.zoneUuid); break;
case 'Aisle': removeAisleFromBackend(point.lineData.aisleUuid); break;
}
};
const handleUpdate = (point: UndoRedo2DDataTypeSchema) => {
if (!point.newData) return;
switch (point.type) {
case 'Wall': updateWallFromBackend(point.newData.wallUuid, point.newData); break;
case 'Floor': updateFloorFromBackend(point.newData.floorUuid, point.newData); break;
case 'Zone': updateZoneFromBackend(point.newData.zoneUuid, point.newData); break;
case 'Aisle': updateAisleFromBackend(point.newData.aisleUuid, point.newData); break;
}
};
const createWallFromBackend = (wallData: Wall) => {
addWall(wallData);
if (projectId) {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', wallData);
// SOCKET
const data = {
wallData: wallData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
};
const removeWallFromBackend = (wallUuid: string) => {
removeWall(wallUuid);
if (projectId) {
// API
// deleteWallApi(projectId, selectedVersion?.versionId || '', wallUuid);
// SOCKET
const data = {
wallUuid: wallUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:delete', data);
}
};
const updateWallFromBackend = (wallUuid: string, updatedData: Wall) => {
updateWall(wallUuid, updatedData);
if (projectId) {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedData);
// SOCKET
const data = {
wallData: updatedData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
};
const createFloorFromBackend = (floorData: Floor) => {
addFloor(floorData);
if (projectId) {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floorData);
// SOCKET
const data = {
floorData: floorData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
};
const removeFloorFromBackend = (floorUuid: string) => {
removeFloor(floorUuid);
if (projectId) {
// API
// deleteFloorApi(projectId, selectedVersion?.versionId || '', floorUuid);
// SOCKET
const data = {
floorUuid: floorUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:delete', data);
}
};
const updateFloorFromBackend = (floorUuid: string, updatedData: Floor) => {
updateFloor(floorUuid, updatedData);
if (projectId) {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedData);
// SOCKET
const data = {
floorData: updatedData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
};
const createZoneFromBackend = (zoneData: Zone) => {
addZone(zoneData);
if (projectId) {
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', zoneData);
// SOCKET
const data = {
zoneData: zoneData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:add', data);
}
};
const removeZoneFromBackend = (zoneUuid: string) => {
removeZone(zoneUuid);
if (projectId) {
// API
// deleteZoneApi(projectId, selectedVersion?.versionId || '', zoneUuid);
// SOCKET
const data = {
zoneUuid,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
};
socket.emit('v1:zone:delete', data);
}
};
const updateZoneFromBackend = (zoneUuid: string, updatedData: Zone) => {
updateZone(zoneUuid, updatedData);
if (projectId) {
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedData);
// SOCKET
const data = {
zoneData: updatedData,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
};
socket.emit('v1:zone:add', data);
}
};
const createAisleFromBackend = (aisleData: Aisle) => {
addAisle(aisleData);
if (projectId) {
// API
// upsertAisleApi(projectId, selectedVersion?.versionId || '', aisleData);
// SOCKET
const data = {
aisleData,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
};
socket.emit('v1:model-aisle:add', data);
}
};
const removeAisleFromBackend = (aisleUuid: string) => {
removeAisle(aisleUuid);
if (projectId) {
// API
// deleteAisleApi(projectId, selectedVersion?.versionId || '', aisleUuid);
// SOCKET
const data = {
aisleUuid,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
};
socket.emit('v1:model-aisle:delete', data);
}
};
const updateAisleFromBackend = (aisleUuid: string, updatedData: Aisle) => {
updateAisle(aisleUuid, updatedData);
if (projectId) {
// API
// upsertAisleApi(projectId, selectedVersion?.versionId || '', updatedData);
// SOCKET
const data = {
aisleData: updatedData,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
};
socket.emit('v1:model-aisle:add', data);
}
};
return { handleRedo };
}
export default useRedoHandler;

View File

@@ -0,0 +1,356 @@
import { useParams } from "react-router-dom";
import { getUserData } from "../../../../../functions/getUserData";
import { useVersionContext } from "../../../../builder/version/versionContext";
import { useSceneContext } from "../../../sceneContext";
import { useSocketStore } from "../../../../../store/builder/store";
// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi";
// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi";
// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi";
// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
// import { deleteFloorApi } from "../../../../../services/factoryBuilder/floor/deleteFloorApi";
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
function useUndoHandler() {
const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
const { undo2D, peekUndo2D } = undoRedo2DStore();
const { addWall, removeWall, updateWall } = wallStore();
const { addFloor, removeFloor, updateFloor } = floorStore();
const { addZone, removeZone, updateZone } = zoneStore();
const { addAisle, removeAisle, updateAisle } = aisleStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
const { socket } = useSocketStore();
const handleUndo = () => {
const unDoData = peekUndo2D();
if (!unDoData) return;
if (unDoData.type === 'Draw') {
const { actions } = unDoData;
actions.forEach(action => {
const { actionType } = action;
if ('point' in action) {
const point = action.point;
if (actionType === 'Line-Create') {
handleRemove(point);
} else if (actionType === 'Line-Update') {
handleUpdate(point);
} else if (actionType === 'Line-Delete') {
handleCreate(point);
}
} else if ('points' in action) {
const points = action.points;
if (actionType === 'Lines-Create') {
points.forEach(handleRemove);
} else if (actionType === 'Lines-Update') {
points.forEach(handleUpdate);
} else if (actionType === 'Lines-Delete') {
points.forEach(handleCreate);
}
}
});
} else if (unDoData.type === 'UI') {
// Handle UI actions if needed
}
undo2D();
};
const handleCreate = (point: UndoRedo2DDataTypeSchema) => {
switch (point.type) {
case 'Wall': createWallFromBackend(point.lineData); break;
case 'Floor': createFloorFromBackend(point.lineData); break;
case 'Zone': createZoneFromBackend(point.lineData); break;
case 'Aisle': createAisleFromBackend(point.lineData); break;
}
};
const handleRemove = (point: UndoRedo2DDataTypeSchema) => {
switch (point.type) {
case 'Wall': removeWallFromBackend(point.lineData.wallUuid); break;
case 'Floor': removeFloorFromBackend(point.lineData.floorUuid); break;
case 'Zone': removeZoneFromBackend(point.lineData.zoneUuid); break;
case 'Aisle': removeAisleFromBackend(point.lineData.aisleUuid); break;
}
};
const handleUpdate = (point: UndoRedo2DDataTypeSchema) => {
switch (point.type) {
case 'Wall': updateWallFromBackend(point.lineData.wallUuid, point.lineData); break;
case 'Floor': updateFloorFromBackend(point.lineData.floorUuid, point.lineData); break;
case 'Zone': updateZoneFromBackend(point.lineData.zoneUuid, point.lineData); break;
case 'Aisle': updateAisleFromBackend(point.lineData.aisleUuid, point.lineData); break;
}
};
const createWallFromBackend = (wallData: Wall) => {
addWall(wallData);
if (projectId) {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', wallData);
// SOCKET
const data = {
wallData: wallData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
};
const removeWallFromBackend = (wallUuid: string) => {
removeWall(wallUuid);
if (projectId) {
// API
// deleteWallApi(projectId, selectedVersion?.versionId || '', wallUuid);
// SOCKET
const data = {
wallUuid: wallUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:delete', data);
}
};
const updateWallFromBackend = (wallUuid: string, updatedData: Wall) => {
updateWall(wallUuid, updatedData);
if (projectId) {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedData);
// SOCKET
const data = {
wallData: updatedData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
};
const createFloorFromBackend = (floorData: Floor) => {
addFloor(floorData);
if (projectId) {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floorData);
// SOCKET
const data = {
floorData: floorData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
};
const removeFloorFromBackend = (floorUuid: string) => {
removeFloor(floorUuid);
if (projectId) {
// API
// deleteFloorApi(projectId, selectedVersion?.versionId || '', floorUuid);
// SOCKET
const data = {
floorUuid: floorUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:delete', data);
}
};
const updateFloorFromBackend = (floorUuid: string, updatedData: Floor) => {
updateFloor(floorUuid, updatedData);
if (projectId) {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedData);
// SOCKET
const data = {
floorData: updatedData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
};
const createZoneFromBackend = (zoneData: Zone) => {
addZone(zoneData);
if (projectId) {
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', zoneData);
// SOCKET
const data = {
zoneData: zoneData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:add', data);
}
};
const removeZoneFromBackend = (zoneUuid: string) => {
removeZone(zoneUuid);
if (projectId) {
// API
// deleteZoneApi(projectId, selectedVersion?.versionId || '', zoneUuid);
// SOCKET
const data = {
zoneUuid,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
};
socket.emit('v1:zone:delete', data);
}
};
const updateZoneFromBackend = (zoneUuid: string, updatedData: Zone) => {
updateZone(zoneUuid, updatedData);
if (projectId) {
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedData);
// SOCKET
const data = {
zoneData: updatedData,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
};
socket.emit('v1:zone:add', data);
}
};
const createAisleFromBackend = (aisleData: Aisle) => {
addAisle(aisleData);
if (projectId) {
// API
// upsertAisleApi(projectId, selectedVersion?.versionId || '', aisleData);
// SOCKET
const data = {
aisleData,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
};
socket.emit('v1:model-aisle:add', data);
}
};
const removeAisleFromBackend = (aisleUuid: string) => {
removeAisle(aisleUuid);
if (projectId) {
// API
// deleteAisleApi(projectId, selectedVersion?.versionId || '', aisleUuid);
// SOCKET
const data = {
aisleUuid,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
};
socket.emit('v1:model-aisle:delete', data);
}
};
const updateAisleFromBackend = (aisleUuid: string, updatedData: Aisle) => {
updateAisle(aisleUuid, updatedData);
if (projectId) {
// API
// upsertAisleApi(projectId, selectedVersion?.versionId || '', updatedData);
// SOCKET
const data = {
aisleData: updatedData,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
};
socket.emit('v1:model-aisle:add', data);
}
};
return { handleUndo };
}
export default useUndoHandler;

View File

@@ -0,0 +1,49 @@
import { useEffect } from 'react'
import { useSceneContext } from '../../../sceneContext'
import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
import { useSocketStore, useToggleView } from '../../../../../store/builder/store';
import { useVersionContext } from '../../../../builder/version/versionContext';
import useUndoHandler from '../handlers/useUndoHandler';
import useRedoHandler from '../handlers/useRedoHandler';
function UndoRedo2DControls() {
const { undoRedo2DStore } = useSceneContext();
const { undoStack, redoStack } = undoRedo2DStore();
const { toggleView } = useToggleView();
const { handleUndo } = useUndoHandler();
const { handleRedo } = useRedoHandler();
const { socket } = useSocketStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
useEffect(() => {
console.log(undoStack, redoStack);
}, [undoStack, redoStack]);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === 'Ctrl+Z') {
handleUndo();
}
if (keyCombination === 'Ctrl+Y') {
handleRedo();
}
};
if (toggleView) {
window.addEventListener('keydown', handleKeyDown);
}
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [toggleView, undoStack, redoStack, socket, selectedVersion]);
return null;
}
export default UndoRedo2DControls;

View File

@@ -9,21 +9,17 @@ import {
useTileDistance,
} from "../../../store/builder/store";
import * as CONSTANTS from "../../../types/world/worldConstants";
const shadowWorker = new Worker(
new URL(
"../../../services/factoryBuilder/webWorkers/shadowWorker",
import.meta.url
)
);
const shadowWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/shadowWorker", import.meta.url));
export default function Shadows() {
const { shadows, setShadows } = useShadows();
const { sunPosition, setSunPosition } = useSunPosition();
const { shadows } = useShadows();
const { sunPosition } = useSunPosition();
const lightRef = useRef<THREE.DirectionalLight | null>(null);
const targetRef = useRef<THREE.Object3D | null>(null);
const { controls, gl } = useThree();
const { elevation, setElevation } = useElevation();
const { azimuth, setAzimuth } = useAzimuth();
const { elevation } = useElevation();
const { azimuth } = useAzimuth();
const { planeValue } = useTileDistance();
useEffect(() => {

View File

@@ -1,4 +1,4 @@
import { createContext, useContext, useMemo } from 'react';
import { createContext, useContext, useMemo, useRef } from 'react';
import { createAssetStore, AssetStoreType } from '../../store/builder/useAssetStore';
import { createWallAssetStore, WallAssetStoreType } from '../../store/builder/useWallAssetStore';
@@ -7,6 +7,8 @@ import { createAisleStore, AisleStoreType } from '../../store/builder/useAisleSt
import { createZoneStore, ZoneStoreType } from '../../store/builder/useZoneStore';
import { createFloorStore, FloorStoreType } from '../../store/builder/useFloorStore';
import { createUndoRedo2DStore, UndoRedo2DStoreType } from '../../store/builder/useUndoRedo2DStore';
import { createEventStore, EventStoreType } from '../../store/simulation/useEventsStore';
import { createProductStore, ProductStoreType } from '../../store/simulation/useProductStore';
@@ -27,6 +29,8 @@ type SceneContextValue = {
zoneStore: ZoneStoreType,
floorStore: FloorStoreType,
undoRedo2DStore: UndoRedo2DStoreType,
eventStore: EventStoreType,
productStore: ProductStoreType,
@@ -38,6 +42,8 @@ type SceneContextValue = {
storageUnitStore: StorageUnitStoreType;
humanStore: HumanStoreType;
humanEventManagerRef: React.RefObject<HumanEventManagerState>;
clearStores: () => void;
layout: 'Main Layout' | 'Comparison Layout';
@@ -60,6 +66,8 @@ export function SceneProvider({
const zoneStore = useMemo(() => createZoneStore(), []);
const floorStore = useMemo(() => createFloorStore(), []);
const undoRedo2DStore = useMemo(() => createUndoRedo2DStore(), []);
const eventStore = useMemo(() => createEventStore(), []);
const productStore = useMemo(() => createProductStore(), []);
@@ -71,6 +79,8 @@ export function SceneProvider({
const storageUnitStore = useMemo(() => createStorageUnitStore(), []);
const humanStore = useMemo(() => createHumanStore(), []);
const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] });
const clearStores = useMemo(() => () => {
assetStore.getState().clearAssets();
wallAssetStore.getState().clearWallAssets();
@@ -78,6 +88,7 @@ export function SceneProvider({
aisleStore.getState().clearAisles();
zoneStore.getState().clearZones();
floorStore.getState().clearFloors();
undoRedo2DStore.getState().clearUndoRedo2D();
eventStore.getState().clearEvents();
productStore.getState().clearProducts();
materialStore.getState().clearMaterials();
@@ -87,7 +98,8 @@ export function SceneProvider({
vehicleStore.getState().clearVehicles();
storageUnitStore.getState().clearStorageUnits();
humanStore.getState().clearHumans();
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]);
humanEventManagerRef.current.humanStates = [];
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]);
const contextValue = useMemo(() => (
{
@@ -97,6 +109,7 @@ export function SceneProvider({
aisleStore,
zoneStore,
floorStore,
undoRedo2DStore,
eventStore,
productStore,
materialStore,
@@ -106,10 +119,11 @@ export function SceneProvider({
vehicleStore,
storageUnitStore,
humanStore,
humanEventManagerRef,
clearStores,
layout
}
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, clearStores, layout]);
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, clearStores, layout]);
return (
<SceneContext.Provider value={contextValue}>

View File

@@ -37,7 +37,7 @@ export function useConveyorActions() {
switch (action.actionType) {
case 'default':
handleDefaultAction(action);
handleDefaultAction(action, materialId);
break;
case 'spawn':
handleSpawnAction(action);

View File

@@ -8,7 +8,7 @@ export function useWorkerHandler() {
const { getModelUuidByActionUuid } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { incrementHumanLoad, incrementLoadCount, addCurrentMaterial, addCurrentAction } = humanStore();
const { incrementHumanLoad, addCurrentMaterial, addCurrentAction } = humanStore();
const workerLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
@@ -24,7 +24,6 @@ export function useWorkerHandler() {
if (!modelUuid) return;
incrementHumanLoad(modelUuid, 1);
incrementLoadCount(modelUuid, 1);
addCurrentAction(modelUuid, action.actionUuid);
addCurrentMaterial(modelUuid, material.materialType, material.materialId);

View File

@@ -3,25 +3,30 @@ import { useFrame } from "@react-three/fiber";
import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore, useAnimationPlaySpeed } from "../../../../../store/usePlayButtonStore";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager";
export function useRetrieveHandler() {
const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext();
const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext();
const { selectedProductStore } = useProductContext();
const { addMaterial } = materialStore();
const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore();
const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore();
const { getVehicleById, incrementVehicleLoad, addCurrentMaterial } = vehicleStore();
const { getHumanById, incrementHumanLoad, incrementLoadCount, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore();
const { getHumanById, incrementHumanLoad, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore();
const { getAssetById, setCurrentAnimation } = assetStore();
const { selectedProduct } = selectedProductStore();
const { getArmBotById, addCurrentAction } = armBotStore();
const { getMachineById } = machineStore();
const { isPlaying } = usePlayButtonStore();
const { speed } = useAnimationPlaySpeed();
const { isPaused } = usePauseButtonStore();
const { isReset } = useResetButtonStore();
const { addHumanToMonitor } = useHumanEventManager();
const [activeRetrievals, setActiveRetrievals] = useState<Map<string, { action: StorageAction, isProcessing: boolean, lastCheckTime: number }>>(new Map());
const retrievalTimeRef = useRef<Map<string, number>>(new Map());
const retrievalCountRef = useRef<Map<string, number>>(new Map());
const monitoredHumansRef = useRef<Set<string>>(new Set());
const [initialDelayComplete, setInitialDelayComplete] = useState(false);
const delayTimerRef = useRef<NodeJS.Timeout | null>(null);
@@ -300,28 +305,120 @@ export function useRetrieveHandler() {
const humanAsset = getAssetById(triggeredModel.modelUuid);
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
if (human && !human.isScheduled && human.state === 'idle' && human.currentLoad < (action as HumanAction).loadCapacity) {
if (humanAsset && humanAsset.animationState?.current === 'idle') {
setCurrentAnimation(human.modelUuid, 'pickup', true, false, false);
} else if (humanAsset && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState.isCompleted) {
if (!action || action.actionType !== 'worker') return;
const currentCount = retrievalCountRef.current.get(actionUuid) ?? 0;
if (currentCount >= action.loadCount) {
completedActions.push(actionUuid);
hasChanges = true;
return;
}
if (human && !human.isScheduled && human.state === 'idle' && human.currentLoad < action.loadCapacity) {
const triggeredModel = action.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid
? getEventByModelUuid(selectedProduct.productUuid, action.triggers[0].triggeredAsset.triggeredModel.modelUuid)
: null;
if (triggeredModel?.type === 'vehicle') {
const model = getVehicleById(triggeredModel.modelUuid);
if (model && !model.isActive && model.state === 'idle' && model.isPicking && model.currentLoad < model.point.action.loadCapacity) {
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 === 'roboticArm') {
const armBot = getArmBotById(triggeredModel.modelUuid);
if (armBot && !armBot.isActive && armBot.state === 'idle' && !armBot.currentAction) {
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') {
const machine = getMachineById(triggeredModel.modelUuid);
if (machine && !machine.isActive && machine.state === 'idle' && !machine.currentAction) {
if (!monitoredHumansRef.current.has(human.modelUuid)) {
addHumanToMonitor(human.modelUuid, () => {
if (humanAsset?.animationState?.current === 'idle') {
setCurrentAnimation(human.modelUuid, 'pickup', true, false, false);
}
}, action.actionUuid);
}
monitoredHumansRef.current.add(human.modelUuid);
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);
}
}
monitoredHumansRef.current.delete(human.modelUuid);
}
return;
}
} else if (triggeredModel?.type === 'storageUnit') {
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
if (lastMaterial) {
if (action && human.currentLoad < (action as HumanAction).loadCapacity) {
const material = createNewMaterial(
lastMaterial.materialId,
lastMaterial.materialType,
storageUnit.point.action
);
if (material) {
removeLastMaterial(storageUnit.modelUuid);
updateCurrentLoad(storageUnit.modelUuid, -1);
incrementHumanLoad(human.modelUuid, 1);
incrementLoadCount(human.modelUuid, 1);
addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId);
retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`);
}
if (human.currentLoad + 1 < (action as HumanAction).loadCapacity) {
}
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);
}
}
}
@@ -353,11 +450,12 @@ export function useRetrieveHandler() {
}, []);
useEffect(() => {
if (isReset) {
if (isReset || !isPlaying) {
setActiveRetrievals(new Map());
retrievalCountRef.current.clear();
setInitialDelayComplete(false);
}
}, [isReset]);
}, [isReset, isPlaying]);
return {
handleRetrieve,

View File

@@ -1,24 +1,24 @@
import { useEffect } from 'react'
import { useResetButtonStore } from '../../../../../store/usePlayButtonStore';
import { useFrame } from '@react-three/fiber'
import { useSceneContext } from '../../../../scene/sceneContext';
import { useProductContext } from '../../../products/productContext';
// import { findConveyorSubsequence } from '../../../simulator/functions/getConveyorSequencesInProduct';
function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) {
const { materialStore, conveyorStore, productStore } = useSceneContext();
const { getProductById } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { getMaterialsByCurrentModelUuid, materials } = materialStore();
const { isReset } = useResetButtonStore();
const { getMaterialsByCurrentModelUuid } = materialStore();
const { setConveyorPaused } = conveyorStore();
useEffect(() => {
useFrame(() => {
const product = getProductById(selectedProduct.productUuid);
if (!product) return;
const conveyorMaterials = getMaterialsByCurrentModelUuid(conveyor.modelUuid);
if (conveyorMaterials && conveyorMaterials?.length > 0) {
if (conveyorMaterials && conveyorMaterials.length > 0) {
const hasPausedMaterials = conveyorMaterials.some(material => material.isPaused);
@@ -52,7 +52,7 @@ function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) {
// }
// });
}, [materials, conveyor.modelUuid, getMaterialsByCurrentModelUuid, setConveyorPaused, isReset, selectedProduct.productUuid, getProductById]);
});
useEffect(() => {
// console.log('conveyor: ', conveyor);
@@ -61,7 +61,7 @@ function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) {
return (
<>
</>
)
};
);
}
export default ConveyorInstance

View File

@@ -1,170 +1,142 @@
import { useEffect, useRef } from 'react';
import { useEffect } from 'react';
import { useFrame } from '@react-three/fiber';
import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore';
import { useSceneContext } from '../../../scene/sceneContext';
import { useProductContext } from '../../products/productContext';
export function useHumanEventManager() {
const { humanStore, productStore, assetStore } = useSceneContext();
const { getHumanById, clearLoadCount, setCurrentPhase } = humanStore();
const { humanStore, productStore, assetStore, humanEventManagerRef } = useSceneContext();
const { getHumanById, setCurrentPhase, removeCurrentAction } = humanStore();
const { getAssetById } = assetStore();
const { getActionByUuid } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const callbacksRef = useRef<Map<string, (() => void)[]>>(new Map());
const actionQueueRef = useRef<Map<string, { actionType: "worker" | "assembly", actionUuid: string }[]>>(new Map());
const isCooldownRef = useRef<Map<string, boolean>>(new Map());
const isMonitoringRef = useRef(false);
const { isPlaying } = usePlayButtonStore();
const { isPaused } = usePauseButtonStore();
const { isReset } = useResetButtonStore();
useEffect(() => {
if (isReset) {
callbacksRef.current.clear();
actionQueueRef.current.clear();
isCooldownRef.current.clear();
isMonitoringRef.current = false;
if ((isReset || !isPlaying) && humanEventManagerRef.current) {
humanEventManagerRef.current.humanStates = [];
}
}, [isReset]);
}, [isReset, isPlaying]);
const addHumanToMonitor = (humanId: string, callback: () => void, actionUuid: string) => {
const action = getActionByUuid(selectedProduct.productUuid, actionUuid || '') as HumanAction | undefined;
const human = getHumanById(humanId);
const action = getActionByUuid(selectedProduct.productUuid, actionUuid);
if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker') || !humanEventManagerRef.current) return;
if (!action) return;
const actionType = action.actionType;
if (actionType !== "worker" && actionType !== "assembly") return;
if (!callbacksRef.current.has(humanId)) {
callbacksRef.current.set(humanId, []);
actionQueueRef.current.set(humanId, []);
let state = humanEventManagerRef.current.humanStates.find(h => h.humanId === humanId);
if (!state) {
state = { humanId, actionQueue: [], isCooldown: false };
humanEventManagerRef.current.humanStates.push(state);
}
callbacksRef.current.get(humanId)!.push(callback);
actionQueueRef.current.get(humanId)!.push({ actionType, actionUuid });
const existingAction = state.actionQueue.find(a => a.actionUuid === actionUuid);
if (existingAction) {
const currentCount = existingAction.count ?? 0;
if (existingAction.actionType === 'worker') {
if (currentCount < existingAction.maxLoadCount) {
existingAction.callback = callback;
existingAction.isMonitored = true;
existingAction.isCompleted = false;
}
} else if (existingAction.actionType === 'assembly') {
if (currentCount < existingAction.maxAssemblyCount) {
existingAction.callback = callback;
existingAction.isMonitored = true;
existingAction.isCompleted = false;
}
}
return;
}
isMonitoringRef.current = true;
state.actionQueue.push({
actionType: action.actionType,
actionUuid,
actionName: action.actionName,
maxLoadCount: action.loadCount ?? 0,
maxAssemblyCount: action.assemblyCount ?? 0,
count: 0,
isMonitored: true,
isCompleted: false,
callback
});
};
const removeHumanFromMonitor = (humanId: string) => {
callbacksRef.current.delete(humanId);
actionQueueRef.current.delete(humanId);
isCooldownRef.current.delete(humanId);
const removeHumanFromMonitor = (humanId: string, actionUuid: string) => {
if (!humanEventManagerRef.current) return;
const state = humanEventManagerRef.current.humanStates.find(h => h.humanId === humanId);
if (!state) return;
if (callbacksRef.current.size === 0) {
isMonitoringRef.current = false;
const action = state.actionQueue.find(a => a.actionUuid === actionUuid);
if (action) {
action.callback = undefined;
action.isMonitored = false;
}
};
useFrame(() => {
if (!isMonitoringRef.current || !isPlaying || isPaused) return;
if (!humanEventManagerRef.current || humanEventManagerRef.current.humanStates.length === 0 || !isPlaying || isPaused) return;
callbacksRef.current.forEach((queue, humanId) => {
if (queue.length === 0 || isCooldownRef.current.get(humanId)) return;
for (const humanState of humanEventManagerRef.current.humanStates) {
if (humanState.isCooldown) continue;
const actionQueue = actionQueueRef.current.get(humanId);
if (!actionQueue || actionQueue.length === 0) return;
const { humanId, actionQueue } = humanState;
if (!actionQueue || actionQueue.length === 0) continue;
const action = actionQueue.find(a => !a.isCompleted);
if (!action || !action.isMonitored || !action.callback) continue;
const { actionType: expectedActionType, actionUuid } = actionQueue[0];
const human = getHumanById(humanId);
const humanAsset = getAssetById(humanId);
const action = getActionByUuid(selectedProduct.productUuid, actionUuid) as HumanAction | undefined;
const currentAction = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '') as HumanAction | undefined;
if (!humanAsset || !human || !action || action.actionType !== expectedActionType) return;
if (!human || !humanAsset || !currentAction) continue;
if (human.isActive || human.state !== "idle" || humanAsset.animationState?.current !== 'idle') continue;
let conditionMet = false;
const currentAction = getActionByUuid(selectedProduct.productUuid, human.currentAction?.actionUuid || '') as HumanAction | undefined;
if (expectedActionType === "worker") {
if (currentAction && currentAction.actionType === 'worker') {
conditionMet = (
!human.isActive &&
human.state === "idle" &&
humanAsset.animationState?.current === 'idle' &&
human.currentLoad < currentAction.loadCapacity
);
if (human.totalLoadCount >= currentAction.loadCount && actionUuid === human.currentAction?.actionUuid) {
queue.shift();
actionQueue.shift();
if (queue.length === 0) {
removeHumanFromMonitor(humanId);
}
return;
}
if (conditionMet && actionUuid !== human.currentAction?.actionUuid) {
setCurrentPhase(human.modelUuid, 'init');
}
} else {
conditionMet = (
!human.isActive &&
human.state === "idle" &&
humanAsset.animationState?.current === 'idle' &&
human.currentLoad < action.loadCapacity
);
if (conditionMet && actionUuid !== human.currentAction?.actionUuid) {
setCurrentPhase(human.modelUuid, 'init');
}
if (currentAction.actionType === 'worker') {
if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) {
conditionMet = true;
} else if (action.actionType === 'assembly') {
conditionMet = true;
}
} else if (expectedActionType === "assembly") {
if (currentAction && currentAction.actionType === 'worker') {
conditionMet = (
!human.isActive &&
human.state === "idle" &&
humanAsset.animationState?.current === 'idle' &&
human.currentLoad < currentAction.loadCapacity
);
if (conditionMet && actionUuid !== human.currentAction?.actionUuid) {
setCurrentPhase(human.modelUuid, 'init');
}
} else {
conditionMet = (
!human.isActive &&
human.state === "idle" &&
humanAsset.animationState?.current === 'idle' &&
human.currentLoad < action.loadCapacity
)
}
if (conditionMet) {
clearLoadCount(human.modelUuid);
} else if (currentAction.actionType === 'assembly') {
if (action.actionType === 'assembly') {
conditionMet = true;
} else if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) {
conditionMet = true;
}
}
if (conditionMet) {
const callback = queue.shift();
actionQueue.shift();
if (callback) callback();
if (queue.length === 0) {
removeHumanFromMonitor(humanId);
} else {
isCooldownRef.current.set(humanId, true);
setTimeout(() => {
isCooldownRef.current.set(humanId, false);
}, 1000);
if (action.actionUuid !== human.currentAction?.actionUuid) {
setCurrentPhase(human.modelUuid, 'init');
removeCurrentAction(human.modelUuid);
}
}
});
}, 0);
useEffect(() => {
return () => {
callbacksRef.current.clear();
actionQueueRef.current.clear();
isCooldownRef.current.clear();
isMonitoringRef.current = false;
};
}, []);
action.callback();
action.count = (action.count ?? 0) + 1;
action.isMonitored = false;
if ((action.actionType === 'worker' && action.count >= action.maxLoadCount) ||
(action.actionType === 'assembly' && action.count >= action.maxAssemblyCount)) {
action.isCompleted = true;
}
humanState.isCooldown = true;
setTimeout(() => {
humanState.isCooldown = false;
}, 1000);
removeHumanFromMonitor(human.modelUuid, action.actionUuid);
}
}
}, 0);
return {
addHumanToMonitor,
removeHumanFromMonitor,
removeHumanFromMonitor
};
}
}

View File

@@ -0,0 +1,172 @@
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 AssemblerAnimatorProps {
path: [number, number, number][];
handleCallBack: () => void;
reset: () => void;
human: HumanStatus;
}
function AssemblerAnimator({ path, handleCallBack, human, reset }: Readonly<AssemblerAnimatorProps>) {
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 completedRef = useRef<boolean>(false);
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>((action as HumanAction)?.assemblyPoint?.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;
if (human.currentPhase === 'init-assembly' && path.length > 0) {
setCurrentPath(path);
setObjectRotation((action as HumanAction)?.assemblyPoint?.rotation ?? null);
}
}, [human.currentPhase, path, objectRotation, selectedProduct, human.currentAction?.actionUuid]);
useEffect(() => {
completedRef.current = false;
}, [currentPath]);
useEffect(() => {
if (isReset || !isPlaying) {
reset();
setCurrentPath([]);
completedRef.current = false;
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;
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 AssemblerAnimator;

View File

@@ -50,7 +50,7 @@ const MaterialAnimator = ({ human }: { human: HumanStatus; }) => {
return (
<>
{hasLoad && (action as HumanAction).actionType === 'worker' && human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup') && (
{hasLoad && action && (action as HumanAction).actionType === 'worker' && human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup') && (
<MaterialModel
matRef={meshRef}
materialId={human.currentMaterials[0].materialId || ''}

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react';
import { useFrame, useThree } from '@react-three/fiber';
import { RapierRigidBody } from '@react-three/rapier';
import * as THREE from 'three';
@@ -7,7 +7,7 @@ import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useRese
import { useSceneContext } from '../../../../scene/sceneContext';
import { useProductContext } from '../../../products/productContext';
interface HumanAnimatorProps {
interface WorkerAnimatorProps {
path: [number, number, number][];
handleCallBack: () => void;
reset: () => void;
@@ -15,7 +15,7 @@ interface HumanAnimatorProps {
human: HumanStatus;
}
function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly<HumanAnimatorProps>) {
function WorkerAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly<WorkerAnimatorProps>) {
const { humanStore, assetStore, productStore } = useSceneContext();
const { getActionByUuid } = productStore();
const { selectedProductStore } = useProductContext();
@@ -30,7 +30,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
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 [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 rigidBodyRef = useRef<RapierRigidBody>(null);
@@ -41,15 +41,12 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
if (human.currentPhase === 'init-pickup' && path.length > 0) {
setCurrentPath(path);
setObjectRotation((action as HumanAction).pickUpPoint?.rotation ?? null)
} else if (human.currentPhase === 'init-assembly' && path.length > 0) {
setObjectRotation((action as HumanAction)?.assemblyPoint?.rotation ?? null)
setCurrentPath(path);
setObjectRotation((action as HumanAction).pickUpPoint?.rotation ?? null);
} else if (human.currentPhase === 'pickup-drop' && path.length > 0) {
setObjectRotation((action as HumanAction)?.dropPoint?.rotation ?? null)
setObjectRotation((action as HumanAction)?.dropPoint?.rotation ?? null);
setCurrentPath(path);
} else if (human.currentPhase === 'drop-pickup' && path.length > 0) {
setObjectRotation((action as HumanAction)?.pickUpPoint?.rotation ?? null)
setObjectRotation((action as HumanAction)?.pickUpPoint?.rotation ?? null);
setCurrentPath(path);
}
}, [human.currentPhase, path, objectRotation, selectedProduct, human.currentAction?.actionUuid]);
@@ -74,7 +71,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
object.rotation.set(humanData.rotation[0], humanData.rotation[1], humanData.rotation[2]);
}
}
}, [isReset, isPlaying])
}, [isReset, isPlaying]);
const lastTimeRef = useRef(performance.now());
@@ -111,7 +108,9 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
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 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);
@@ -120,13 +119,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
object.quaternion.copy(targetQuaternion);
} else {
const step = rotationSpeed * delta * speed * human.speed;
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < step) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
object.quaternion.rotateTowards(targetQuaternion, step);
}
const isAligned = angle < 0.01;
@@ -136,13 +129,13 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
const position = start.clone().lerp(end, t);
object.position.copy(position);
if (human.currentMaterials.length > 0 && action?.actionType === 'worker' && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) {
if (human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) {
setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true);
} else {
setCurrentAnimation(human.modelUuid, 'walking', true, true, true);
}
} else {
if (human.currentMaterials.length > 0 && action?.actionType === 'worker' && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) {
if (human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) {
setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true);
} else {
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
@@ -153,7 +146,6 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
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);
@@ -164,21 +156,14 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
setRestingRotation(false);
} else {
const step = rotationSpeed * delta * speed * human.speed;
const angle = object.quaternion.angleTo(targetQuaternion);
if (angle < step) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
}
if (human.currentMaterials.length > 0) {
setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true);
} else {
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
object.quaternion.rotateTowards(targetQuaternion, step);
}
setCurrentAnimation(
human.modelUuid,
human.currentMaterials.length > 0 ? 'idle_with_box' : 'idle',
true, true, true
);
return;
}
}
@@ -198,7 +183,6 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
return (
<>
{currentPath.length > 0 && (
// helper
<group visible={false}>
<Line points={currentPath} color="blue" lineWidth={3} />
{currentPath.map((point, index) => (
@@ -210,7 +194,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
</group>
)}
</>
)
);
}
export default HumanAnimator
export default WorkerAnimator;

View File

@@ -0,0 +1,221 @@
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 AssemblerAnimator from '../../animator/assemblerAnimator';
function AssemblerInstance({ human }: { human: HumanStatus }) {
const { navMesh } = useNavMesh();
const { isPlaying } = usePlayButtonStore();
const { scene } = useThree();
const { assetStore, materialStore, humanStore, productStore } = useSceneContext();
const { setMaterial } = materialStore();
const { triggerPointActions } = useTriggerHandler();
const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore();
const { getActionByUuid } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { setHumanActive, setHumanState, decrementHumanLoad, removeLastMaterial, setCurrentPhase } = humanStore();
const [path, setPath] = useState<[number, number, number][]>([]);
const isPausedRef = useRef<boolean>(false);
const isSpeedRef = useRef<number>(0);
const { speed } = useAnimationPlaySpeed();
const { isPaused } = usePauseButtonStore();
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;
}, [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');
resetAnimation(human.modelUuid);
setPath([]);
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]);
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 as HumanAction).assemblyPoint || (action as HumanAction).actionType === 'worker') 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 as HumanAction)?.assemblyPoint?.position || [0, 0, 0]);
setPath(toPickupPath);
setHumanState(human.modelUuid, 'idle');
setCurrentPhase(human.modelUuid, 'init-assembly');
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' && human.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(human.modelUuid, 'assembling');
setHumanActive(human.modelUuid, true);
processStartTimeRef.current = performance.now();
processTimeRef.current = (action as HumanAction).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 ((action as HumanAction).assemblyPoint && human.currentPhase === 'assembling') {
setHumanState(human.modelUuid, 'idle');
setCurrentPhase(human.modelUuid, 'waiting');
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((action as HumanAction), material.materialId);
}
}
}
} else {
reset()
}
}, [human, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]);
const trackAssemblyProcess = useCallback(() => {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const now = performance.now();
if (!processStartTimeRef.current || !(action as HumanAction).processTime || !action) {
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 = ((action as HumanAction).processTime || 1) * 1000;
if (elapsed >= totalProcessTimeMs / 2 && !hasLoggedHalfway.current) {
hasLoggedHalfway.current = true;
if (human.currentMaterials.length > 0) {
setMaterial(human.currentMaterials[0].materialId, (action as HumanAction).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.currentMaterials]);
function handleCallBack() {
if (human.currentPhase === 'init-assembly') {
setCurrentPhase(human.modelUuid, 'waiting');
setHumanState(human.modelUuid, 'idle');
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Reached assembly point, waiting for material');
setPath([]);
}
}
return (
<>
<AssemblerAnimator
path={path}
handleCallBack={handleCallBack}
human={human}
reset={reset}
/>
</>
)
}
export default AssemblerInstance;

View File

@@ -0,0 +1,615 @@
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 WorkerAnimator from '../../animator/workerAnimator';
function WorkerInstance({ human }: { human: HumanStatus }) {
const { navMesh } = useNavMesh();
const { isPlaying } = usePlayButtonStore();
const { scene } = useThree();
const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext();
const { removeMaterial, setEndTime, setIsVisible } = materialStore();
const { getStorageUnitById } = storageUnitStore();
const { getArmBotById } = armBotStore();
const { getConveyorById } = conveyorStore();
const { getVehicleById } = vehicleStore();
const { getMachineById } = machineStore();
const { triggerPointActions } = useTriggerHandler();
const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore();
const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { 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 !== 'worker' || !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-pickup');
setHumanState(human.modelUuid, 'running');
setHumanActive(human.modelUuid, true);
setCurrentAnimation(human.modelUuid, 'walking', true, true, true);
humanStatus(human.modelUuid, 'Started from init, heading to pickup');
return;
} else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'picking') {
if (humanAsset && human.currentLoad === action.loadCapacity && human.currentMaterials.length > 0 && human.currentLoad > 0 && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState?.isCompleted) {
if (action.pickUpPoint && action.dropPoint) {
const toDrop = computePath(action.pickUpPoint.position || [0, 0, 0], action.dropPoint.position || [0, 0, 0]);
setPath(toDrop);
setCurrentPhase(human.modelUuid, 'pickup-drop');
setHumanState(human.modelUuid, 'running');
setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true);
humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point');
}
} else if (human.currentMaterials.length > 0 && human.currentLoad > 0 && humanAsset?.animationState?.current !== 'pickup') {
if (human.currentMaterials[0]?.materialId) {
setIsVisible(human.currentMaterials[0]?.materialId, false);
}
setCurrentAnimation(human.modelUuid, 'pickup', true, false, false);
}
} else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'dropping' && human.currentLoad === 0) {
if (action.pickUpPoint && action.dropPoint) {
const dropToPickup = computePath(action.dropPoint.position || [0, 0, 0], action.pickUpPoint.position || [0, 0, 0]);
setPath(dropToPickup);
setCurrentPhase(human.modelUuid, 'drop-pickup');
setHumanState(human.modelUuid, 'running');
setHumanActive(human.modelUuid, true);
setCurrentAnimation(human.modelUuid, 'walking', true, true, true);
humanStatus(human.modelUuid, 'Started from dropping point, heading to pickup point');
}
}
} else {
reset()
}
}, [human, human.currentAction, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]);
function handleCallBack() {
if (human.currentPhase === 'init-pickup') {
setCurrentPhase(human.modelUuid, 'picking');
setHumanState(human.modelUuid, 'idle');
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Reached pickup point, waiting for material');
setPath([]);
} else if (human.currentPhase === 'pickup-drop') {
setCurrentPhase(human.modelUuid, 'dropping');
setHumanState(human.modelUuid, 'idle');
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
humanStatus(human.modelUuid, 'Reached drop point');
setPath([]);
} else if (human.currentPhase === 'drop-pickup') {
setCurrentPhase(human.modelUuid, 'picking');
setHumanState(human.modelUuid, 'idle');
setHumanActive(human.modelUuid, false);
setHumanScheduled(human.modelUuid, false);
setPath([]);
clearCurrentMaterials(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Reached pickup point again, cycle complete');
}
}
function startUnloadingProcess() {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
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 ((action as HumanAction).triggers.length > 0) {
const trigger = getTriggerByUuid(selectedProduct.productUuid, (action as HumanAction).triggers[0]?.triggerUuid);
const model = getEventByModelUuid(selectedProduct.productUuid, trigger?.triggeredAsset?.triggeredModel?.modelUuid || '');
if (trigger && model) {
if (model.type === 'transfer') {
if (action) {
handleMaterialDropToConveyor(model);
}
} else if (model.type === 'machine') {
if (action) {
handleMaterialDropToMachine(model);
}
} else if (model.type === 'roboticArm') {
if (action) {
handleMaterialDropToArmBot(model);
}
} else if (model.type === 'storageUnit') {
if (action) {
handleMaterialDropToStorageUnit(model);
}
} else if (model.type === 'vehicle') {
if (action) {
handleMaterialDropToVehicle(model);
}
}
} else {
const droppedMaterial = human.currentLoad;
handleMaterialDropByDefault(droppedMaterial);
}
} else {
const droppedMaterial = human.currentLoad;
handleMaterialDropByDefault(droppedMaterial);
}
} else {
requestAnimationFrame(startUnloadingProcess);
}
}
function handleMaterialDropToStorageUnit(model: StorageEventSchema) {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const humanAsset = getAssetById(human.modelUuid);
if (model && humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
const checkAnimation = () => {
if (humanAsset?.animationState?.isCompleted) {
if (model.point.action.actionType === 'store') {
loopMaterialDropToStorage(
human.modelUuid,
human.currentLoad,
model.modelUuid,
model.point.action.storageCapacity,
(action as HumanAction)
);
}
} else {
requestAnimationFrame(checkAnimation);
}
};
checkAnimation();
}
function loopMaterialDropToStorage(
humanId: string,
humanCurrentLoad: number,
storageUnitId: string,
storageMaxCapacity: number,
action: HumanAction
) {
const storageUnit = getStorageUnitById(storageUnitId);
const humanAsset = getAssetById(human.modelUuid);
if (!storageUnit || humanCurrentLoad <= 0 || storageUnit.currentLoad >= storageMaxCapacity) {
return;
}
decrementHumanLoad(humanId, 1);
humanCurrentLoad -= 1;
const material = removeLastMaterial(humanId);
if (material) {
triggerPointActions(action, material.materialId);
}
if (humanCurrentLoad > 0 && storageUnit.currentLoad < storageMaxCapacity) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
const waitForNextDrop = () => {
if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) {
loopMaterialDropToStorage(
humanId,
humanCurrentLoad,
storageUnitId,
storageMaxCapacity,
action
);
} else {
requestAnimationFrame(waitForNextDrop);
}
};
waitForNextDrop();
}
}
function handleMaterialDropToConveyor(model: ConveyorEventSchema) {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
const checkAnimation = () => {
if (humanAsset?.animationState?.isCompleted) {
const conveyor = getConveyorById(model.modelUuid);
if (conveyor) {
loopMaterialDropToConveyor(
human.modelUuid,
human.currentLoad,
conveyor.modelUuid,
(action as HumanAction)
);
}
} else {
requestAnimationFrame(checkAnimation);
}
};
checkAnimation();
}
function loopMaterialDropToConveyor(
humanId: string,
humanCurrentLoad: number,
conveyorId: string,
action: HumanAction
) {
const conveyor = getConveyorById(conveyorId);
const humanAsset = getAssetById(human.modelUuid);
if (!conveyor || humanCurrentLoad <= 0) {
return;
}
decrementHumanLoad(humanId, 1);
humanCurrentLoad -= 1;
const material = removeLastMaterial(humanId);
if (material) {
triggerPointActions(action, material.materialId);
}
if (humanCurrentLoad > 0) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
const waitForNextDrop = () => {
if (humanAsset?.animationState?.isCompleted) {
loopMaterialDropToConveyor(
humanId,
humanCurrentLoad,
conveyorId,
action
);
} else {
requestAnimationFrame(waitForNextDrop);
}
};
waitForNextDrop();
}
}
function handleMaterialDropToArmBot(model: RoboticArmEventSchema) {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
const checkAnimation = () => {
if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) {
const armBot = getArmBotById(model.modelUuid);
if (armBot && armBot.state === 'idle' && !armBot.isActive) {
loopMaterialDropToArmBot(
human.modelUuid,
human.currentLoad,
model.modelUuid,
(action as HumanAction)
);
}
} else {
requestAnimationFrame(checkAnimation);
}
};
checkAnimation();
}
function loopMaterialDropToArmBot(
humanId: string,
humanCurrentLoad: number,
armBotId: string,
action: HumanAction
) {
const armBot = getArmBotById(armBotId);
const humanAsset = getAssetById(human.modelUuid);
if (!armBot || armBot.state !== 'idle' || armBot.isActive || humanCurrentLoad <= 0) {
return;
}
decrementHumanLoad(humanId, 1);
humanCurrentLoad -= 1;
const material = removeLastMaterial(humanId);
if (material) {
triggerPointActions(action, material.materialId);
}
if (humanCurrentLoad > 0) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
const waitForNextTransfer = () => {
const currentArmBot = getArmBotById(armBotId);
if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) {
if (humanAsset?.animationState?.isCompleted) {
loopMaterialDropToArmBot(
humanId,
humanCurrentLoad,
armBotId,
action
);
} else {
requestAnimationFrame(waitForNextTransfer);
}
} else {
requestAnimationFrame(waitForNextTransfer);
}
};
waitForNextTransfer();
}
}
function handleMaterialDropToVehicle(model: VehicleEventSchema) {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
const checkAnimation = () => {
if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) {
const vehicle = getVehicleById(model.modelUuid);
if (vehicle && vehicle.state === 'idle' && !vehicle.isActive) {
loopMaterialDropToVehicle(
human.modelUuid,
human.currentLoad,
model.modelUuid,
(action as HumanAction)
);
}
} else {
requestAnimationFrame(checkAnimation);
}
};
checkAnimation();
}
function loopMaterialDropToVehicle(
humanId: string,
humanCurrentLoad: number,
vehicleId: string,
action: HumanAction
) {
const vehicle = getVehicleById(vehicleId);
const humanAsset = getAssetById(human.modelUuid);
if (!vehicle || vehicle.state !== 'idle' || vehicle.isActive || humanCurrentLoad <= 0) {
return;
}
decrementHumanLoad(humanId, 1);
humanCurrentLoad -= 1;
const material = removeLastMaterial(humanId);
if (material) {
triggerPointActions(action, material.materialId);
}
if (humanCurrentLoad > 0) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
const waitForNextTransfer = () => {
const currentVehicle = getVehicleById(vehicleId);
if (currentVehicle && currentVehicle.state === 'idle' && !currentVehicle.isActive) {
if (humanAsset?.animationState?.isCompleted) {
loopMaterialDropToVehicle(
humanId,
humanCurrentLoad,
vehicleId,
action
);
} else {
requestAnimationFrame(waitForNextTransfer);
}
} else {
requestAnimationFrame(waitForNextTransfer);
}
};
waitForNextTransfer();
}
}
function handleMaterialDropToMachine(model: MachineEventSchema) {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
const checkAnimation = () => {
if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) {
const machine = getMachineById(model.modelUuid);
if (machine && machine.state === 'idle' && !machine.isActive) {
loopMaterialDropToMachine(
human.modelUuid,
human.currentLoad,
model.modelUuid,
(action as HumanAction)
);
}
} else {
requestAnimationFrame(checkAnimation);
}
};
checkAnimation();
}
function loopMaterialDropToMachine(
humanId: string,
humanCurrentLoad: number,
machineId: string,
action: HumanAction
) {
const machine = getMachineById(machineId);
const humanAsset = getAssetById(human.modelUuid);
if (!machine || machine.state !== 'idle' || machine.isActive || humanCurrentLoad <= 0) {
return;
}
decrementHumanLoad(humanId, 1);
humanCurrentLoad -= 1;
const material = removeLastMaterial(humanId);
if (material) {
triggerPointActions(action, material.materialId);
}
if (humanCurrentLoad > 0) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
const waitForNextTransfer = () => {
const currentMachine = getMachineById(machineId);
if (currentMachine && currentMachine.state === 'idle' && !currentMachine.isActive) {
if (humanAsset?.animationState?.isCompleted) {
loopMaterialDropToMachine(
humanId,
humanCurrentLoad,
machineId,
action
);
} else {
requestAnimationFrame(waitForNextTransfer);
}
} else {
requestAnimationFrame(waitForNextTransfer);
}
};
waitForNextTransfer();
}
}
function handleMaterialDropByDefault(droppedMaterial: number) {
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
if (humanAsset?.animationState?.isCompleted) {
const remainingMaterials = droppedMaterial - 1;
decrementHumanLoad(human.modelUuid, 1);
const material = removeLastMaterial(human.modelUuid);
if (material) {
setEndTime(material.materialId, performance.now());
removeMaterial(material.materialId);
}
if (remainingMaterials > 0) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
requestAnimationFrame(() => handleMaterialDropByDefault(remainingMaterials));
}
return;
}
requestAnimationFrame(() => handleMaterialDropByDefault(droppedMaterial));
}
return (
<>
<WorkerAnimator
path={path}
handleCallBack={handleCallBack}
human={human}
reset={reset}
startUnloadingProcess={startUnloadingProcess}
/>
</>
)
}
export default WorkerInstance;

View File

@@ -1,36 +1,20 @@
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 { useEffect, useRef } from 'react';
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
import { useSceneContext } from '../../../../scene/sceneContext';
import { useProductContext } from '../../../products/productContext';
import HumanAnimator from '../animator/humanAnimator';
import MaterialAnimator from '../animator/materialAnimator';
import AssemblerInstance from './actions/assemberInstance';
import WorkerInstance from './actions/workerInstance';
function HumanInstance({ human }: { human: HumanStatus }) {
const { navMesh } = useNavMesh();
const { isPlaying } = usePlayButtonStore();
const { scene } = useThree();
const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext();
const { removeMaterial, setEndTime, setMaterial, setIsVisible } = materialStore();
const { getStorageUnitById } = storageUnitStore();
const { getArmBotById } = armBotStore();
const { getConveyorById } = conveyorStore();
const { getVehicleById } = vehicleStore();
const { getMachineById } = machineStore();
const { triggerPointActions } = useTriggerHandler();
const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore();
const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore();
const { humanStore, productStore } = useSceneContext();
const { getActionByUuid } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { setHumanActive, setHumanState, clearCurrentMaterials, setHumanLoad, setHumanScheduled, decrementHumanLoad, removeLastMaterial, incrementIdleTime, incrementActiveTime, resetTime, setCurrentPhase } = humanStore();
const { incrementIdleTime, incrementActiveTime } = 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);
@@ -39,14 +23,7 @@ function HumanInstance({ human }: { human: HumanStatus }) {
const { isPaused } = usePauseButtonStore();
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);
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
useEffect(() => {
isPausedRef.current = isPaused;
@@ -56,259 +33,6 @@ function HumanInstance({ human }: { human: HumanStatus }) {
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
}
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]);
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 as HumanAction).assemblyPoint || (action as HumanAction).actionType === 'worker') 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 as HumanAction)?.assemblyPoint?.position || [0, 0, 0]);
setPath(toPickupPath);
setHumanState(human.modelUuid, 'idle');
setCurrentPhase(human.modelUuid, 'init-assembly');
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' && human.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(human.modelUuid, 'assembling');
setHumanActive(human.modelUuid, true);
processStartTimeRef.current = performance.now();
processTimeRef.current = (action as HumanAction).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 ((action as HumanAction).assemblyPoint && human.currentPhase === 'assembling') {
setHumanState(human.modelUuid, 'idle');
setCurrentPhase(human.modelUuid, 'waiting');
setHumanActive(human.modelUuid, false);
setHumanScheduled(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((action as HumanAction), material.materialId);
}
}
}
} else {
reset()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [human, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]);
const trackAssemblyProcess = useCallback(() => {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const now = performance.now();
if (!processStartTimeRef.current || !(action as HumanAction).processTime || !action) {
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 = ((action as HumanAction).processTime || 1) * 1000;
if (elapsed >= totalProcessTimeMs / 2 && !hasLoggedHalfway.current) {
hasLoggedHalfway.current = true;
if (human.currentMaterials.length > 0) {
setMaterial(human.currentMaterials[0].materialId, (action as HumanAction).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.currentMaterials]);
useEffect(() => {
if (isPlaying) {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
if (!action || action.actionType !== 'worker' || !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-pickup');
setHumanState(human.modelUuid, 'running');
setHumanActive(human.modelUuid, true);
setCurrentAnimation(human.modelUuid, 'walking', true, true, true);
humanStatus(human.modelUuid, 'Started from init, heading to pickup');
return;
} else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'picking') {
if (humanAsset && human.currentLoad === action.loadCapacity && human.currentMaterials.length > 0 && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState?.isCompleted) {
if (action.pickUpPoint && action.dropPoint) {
const toDrop = computePath(action.pickUpPoint.position || [0, 0, 0], action.dropPoint.position || [0, 0, 0]);
setPath(toDrop);
setCurrentPhase(human.modelUuid, 'pickup-drop');
setHumanState(human.modelUuid, 'running');
setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true);
humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point');
}
} else if (human.currentMaterials.length > 0 && humanAsset?.animationState?.current !== 'pickup') {
if (human.currentMaterials[0]?.materialId) {
setIsVisible(human.currentMaterials[0]?.materialId, false);
}
setCurrentAnimation(human.modelUuid, 'pickup', true, false, false);
}
} else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'dropping' && human.currentLoad === 0) {
if (action.pickUpPoint && action.dropPoint) {
const dropToPickup = computePath(action.dropPoint.position || [0, 0, 0], action.pickUpPoint.position || [0, 0, 0]);
setPath(dropToPickup);
setCurrentPhase(human.modelUuid, 'drop-pickup');
setHumanState(human.modelUuid, 'running');
setHumanActive(human.modelUuid, true);
setCurrentAnimation(human.modelUuid, 'walking', true, true, true);
humanStatus(human.modelUuid, 'Started from dropping point, heading to pickup point');
}
}
} else {
reset()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [human, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]);
function handleCallBack() {
if (human.currentPhase === 'init-pickup') {
setCurrentPhase(human.modelUuid, 'picking');
setHumanState(human.modelUuid, 'idle');
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Reached pickup point, waiting for material');
setPath([]);
} if (human.currentPhase === 'init-assembly') {
setCurrentPhase(human.modelUuid, 'waiting');
setHumanState(human.modelUuid, 'idle');
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Reached assembly point, waiting for material');
setPath([]);
} else if (human.currentPhase === 'pickup-drop') {
setCurrentPhase(human.modelUuid, 'dropping');
setHumanState(human.modelUuid, 'idle');
setHumanActive(human.modelUuid, false);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
humanStatus(human.modelUuid, 'Reached drop point');
setPath([]);
} else if (human.currentPhase === 'drop-pickup') {
setCurrentPhase(human.modelUuid, 'picking');
setHumanState(human.modelUuid, 'idle');
setHumanActive(human.modelUuid, false);
setHumanScheduled(human.modelUuid, false);
setPath([]);
clearCurrentMaterials(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
humanStatus(human.modelUuid, 'Reached pickup point again, cycle complete');
}
}
function animate(currentTime: number) {
if (previousTimeRef.current === null) {
previousTimeRef.current = currentTime;
@@ -353,438 +77,15 @@ function HumanInstance({ human }: { human: HumanStatus }) {
};
}, [human, isPlaying]);
function startUnloadingProcess() {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
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 ((action as HumanAction).triggers.length > 0) {
const trigger = getTriggerByUuid(selectedProduct.productUuid, (action as HumanAction).triggers[0]?.triggerUuid);
const model = getEventByModelUuid(selectedProduct.productUuid, trigger?.triggeredAsset?.triggeredModel?.modelUuid || '');
if (trigger && model) {
if (model.type === 'transfer') {
if (action) {
handleMaterialDropToConveyor(model);
}
} else if (model.type === 'machine') {
if (action) {
handleMaterialDropToMachine(model);
}
} else if (model.type === 'roboticArm') {
if (action) {
handleMaterialDropToArmBot(model);
}
} else if (model.type === 'storageUnit') {
if (action) {
handleMaterialDropToStorageUnit(model);
}
} else if (model.type === 'vehicle') {
if (action) {
handleMaterialDropToVehicle(model);
}
}
} else {
const droppedMaterial = human.currentLoad;
handleMaterialDropByDefault(droppedMaterial);
}
} else {
const droppedMaterial = human.currentLoad;
handleMaterialDropByDefault(droppedMaterial);
}
} else {
requestAnimationFrame(startUnloadingProcess);
}
}
function handleMaterialDropToStorageUnit(model: StorageEventSchema) {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const humanAsset = getAssetById(human.modelUuid);
if (model && humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
const checkAnimation = () => {
if (humanAsset?.animationState?.isCompleted) {
if (model.point.action.actionType === 'store') {
loopMaterialDropToStorage(
human.modelUuid,
human.currentLoad,
model.modelUuid,
model.point.action.storageCapacity,
(action as HumanAction)
);
}
} else {
requestAnimationFrame(checkAnimation);
}
};
checkAnimation();
}
function loopMaterialDropToStorage(
humanId: string,
humanCurrentLoad: number,
storageUnitId: string,
storageMaxCapacity: number,
action: HumanAction
) {
const storageUnit = getStorageUnitById(storageUnitId);
const humanAsset = getAssetById(human.modelUuid);
if (!storageUnit || humanCurrentLoad <= 0 || storageUnit.currentLoad >= storageMaxCapacity) {
return;
}
decrementHumanLoad(humanId, 1);
humanCurrentLoad -= 1;
const material = removeLastMaterial(humanId);
if (material) {
triggerPointActions(action, material.materialId);
}
if (humanCurrentLoad > 0 && storageUnit.currentLoad < storageMaxCapacity) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
const waitForNextDrop = () => {
if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) {
loopMaterialDropToStorage(
humanId,
humanCurrentLoad,
storageUnitId,
storageMaxCapacity,
action
);
} else {
requestAnimationFrame(waitForNextDrop);
}
};
waitForNextDrop();
}
}
function handleMaterialDropToConveyor(model: ConveyorEventSchema) {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
const checkAnimation = () => {
if (humanAsset?.animationState?.isCompleted) {
const conveyor = getConveyorById(model.modelUuid);
if (conveyor) {
loopMaterialDropToConveyor(
human.modelUuid,
human.currentLoad,
conveyor.modelUuid,
(action as HumanAction)
);
}
} else {
requestAnimationFrame(checkAnimation);
}
};
checkAnimation();
}
function loopMaterialDropToConveyor(
humanId: string,
humanCurrentLoad: number,
conveyorId: string,
action: HumanAction
) {
const conveyor = getConveyorById(conveyorId);
const humanAsset = getAssetById(human.modelUuid);
if (!conveyor || humanCurrentLoad <= 0) {
return;
}
decrementHumanLoad(humanId, 1);
humanCurrentLoad -= 1;
const material = removeLastMaterial(humanId);
if (material) {
triggerPointActions(action, material.materialId);
}
if (humanCurrentLoad > 0) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
const waitForNextDrop = () => {
if (humanAsset?.animationState?.isCompleted) {
loopMaterialDropToConveyor(
humanId,
humanCurrentLoad,
conveyorId,
action
);
} else {
requestAnimationFrame(waitForNextDrop);
}
};
waitForNextDrop();
}
}
function handleMaterialDropToArmBot(model: RoboticArmEventSchema) {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
const checkAnimation = () => {
if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) {
const armBot = getArmBotById(model.modelUuid);
if (armBot && armBot.state === 'idle' && !armBot.isActive) {
loopMaterialDropToArmBot(
human.modelUuid,
human.currentLoad,
model.modelUuid,
(action as HumanAction)
);
}
} else {
requestAnimationFrame(checkAnimation);
}
};
checkAnimation();
}
function loopMaterialDropToArmBot(
humanId: string,
humanCurrentLoad: number,
armBotId: string,
action: HumanAction
) {
const armBot = getArmBotById(armBotId);
const humanAsset = getAssetById(human.modelUuid);
if (!armBot || armBot.state !== 'idle' || armBot.isActive || humanCurrentLoad <= 0) {
return;
}
decrementHumanLoad(humanId, 1);
humanCurrentLoad -= 1;
const material = removeLastMaterial(humanId);
if (material) {
triggerPointActions(action, material.materialId);
}
if (humanCurrentLoad > 0) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
const waitForNextTransfer = () => {
const currentArmBot = getArmBotById(armBotId);
if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) {
if (humanAsset?.animationState?.isCompleted) {
loopMaterialDropToArmBot(
humanId,
humanCurrentLoad,
armBotId,
action
);
} else {
requestAnimationFrame(waitForNextTransfer);
}
} else {
requestAnimationFrame(waitForNextTransfer);
}
};
waitForNextTransfer();
}
}
function handleMaterialDropToVehicle(model: VehicleEventSchema) {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
const checkAnimation = () => {
if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) {
const vehicle = getVehicleById(model.modelUuid);
if (vehicle && vehicle.state === 'idle' && !vehicle.isActive) {
loopMaterialDropToVehicle(
human.modelUuid,
human.currentLoad,
model.modelUuid,
(action as HumanAction)
);
}
} else {
requestAnimationFrame(checkAnimation);
}
};
checkAnimation();
}
function loopMaterialDropToVehicle(
humanId: string,
humanCurrentLoad: number,
vehicleId: string,
action: HumanAction
) {
const vehicle = getVehicleById(vehicleId);
const humanAsset = getAssetById(human.modelUuid);
if (!vehicle || vehicle.state !== 'idle' || vehicle.isActive || humanCurrentLoad <= 0) {
return;
}
decrementHumanLoad(humanId, 1);
humanCurrentLoad -= 1;
const material = removeLastMaterial(humanId);
if (material) {
triggerPointActions(action, material.materialId);
}
if (humanCurrentLoad > 0) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
const waitForNextTransfer = () => {
const currentVehicle = getVehicleById(vehicleId);
if (currentVehicle && currentVehicle.state === 'idle' && !currentVehicle.isActive) {
if (humanAsset?.animationState?.isCompleted) {
loopMaterialDropToVehicle(
humanId,
humanCurrentLoad,
vehicleId,
action
);
} else {
requestAnimationFrame(waitForNextTransfer);
}
} else {
requestAnimationFrame(waitForNextTransfer);
}
};
waitForNextTransfer();
}
}
function handleMaterialDropToMachine(model: MachineEventSchema) {
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
const checkAnimation = () => {
if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) {
const machine = getMachineById(model.modelUuid);
if (machine && machine.state === 'idle' && !machine.isActive) {
loopMaterialDropToMachine(
human.modelUuid,
human.currentLoad,
model.modelUuid,
(action as HumanAction)
);
}
} else {
requestAnimationFrame(checkAnimation);
}
};
checkAnimation();
}
function loopMaterialDropToMachine(
humanId: string,
humanCurrentLoad: number,
machineId: string,
action: HumanAction
) {
const machine = getMachineById(machineId);
const humanAsset = getAssetById(human.modelUuid);
if (!machine || machine.state !== 'idle' || machine.isActive || humanCurrentLoad <= 0) {
return;
}
decrementHumanLoad(humanId, 1);
humanCurrentLoad -= 1;
const material = removeLastMaterial(humanId);
if (material) {
triggerPointActions(action, material.materialId);
}
if (humanCurrentLoad > 0) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
const waitForNextTransfer = () => {
const currentMachine = getMachineById(machineId);
if (currentMachine && currentMachine.state === 'idle' && !currentMachine.isActive) {
if (humanAsset?.animationState?.isCompleted) {
loopMaterialDropToMachine(
humanId,
humanCurrentLoad,
machineId,
action
);
} else {
requestAnimationFrame(waitForNextTransfer);
}
} else {
requestAnimationFrame(waitForNextTransfer);
}
};
waitForNextTransfer();
}
}
function handleMaterialDropByDefault(droppedMaterial: number) {
const humanAsset = getAssetById(human.modelUuid);
if (humanAsset?.animationState?.current !== 'drop') {
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
}
if (humanAsset?.animationState?.isCompleted) {
const remainingMaterials = droppedMaterial - 1;
decrementHumanLoad(human.modelUuid, 1);
const material = removeLastMaterial(human.modelUuid);
if (material) {
setEndTime(material.materialId, performance.now());
removeMaterial(material.materialId);
}
if (remainingMaterials > 0) {
resetAnimation(human.modelUuid);
setCurrentAnimation(human.modelUuid, 'drop', true, false, false);
requestAnimationFrame(() => handleMaterialDropByDefault(remainingMaterials));
}
return;
}
requestAnimationFrame(() => handleMaterialDropByDefault(droppedMaterial));
}
return (
<>
<HumanAnimator
path={path}
handleCallBack={handleCallBack}
human={human}
reset={reset}
startUnloadingProcess={startUnloadingProcess}
/>
{action && action.actionType === 'worker' &&
<WorkerInstance human={human} />
}
{action && action.actionType === 'assembly' &&
<AssemblerInstance human={human} />
}
<MaterialAnimator human={human} />
</>

View File

@@ -84,7 +84,6 @@ const MachineAnimator = ({ currentPhase, handleCallBack, processingTime, machine
animationFrameId.current = null;
handleCallBack();
}
}
}

View File

@@ -23,6 +23,7 @@ function MachineInstance({ machineDetail }: { readonly machineDetail: MachineSta
const { triggerPointActions } = useTriggerHandler();
const { speed } = useAnimationPlaySpeed();
const { isPaused } = usePauseButtonStore();
useEffect(() => {
isPausedRef.current = isPaused;
}, [isPaused]);
@@ -50,6 +51,7 @@ function MachineInstance({ machineDetail }: { readonly machineDetail: MachineSta
function machineStatus(modelId: string, status: string) {
// console.log(`${modelId} , ${status}`);
}
function animate(currentTime: number) {
if (previousTimeRef.current === null) {
previousTimeRef.current = currentTime;
@@ -114,7 +116,7 @@ function MachineInstance({ machineDetail }: { readonly machineDetail: MachineSta
function handleCallBack() {
if (currentPhase == "processing") {
setMachineState(machineDetail.modelUuid, 'idle');
setMachineActive(machineDetail.modelUuid, false);
setMachineActive(machineDetail.modelUuid, true);
setCurrentPhase("idle")
isIncrememtable.current = true;
machineStatus(machineDetail.modelUuid, "Machine has completed the processing")
@@ -130,6 +132,7 @@ function MachineInstance({ machineDetail }: { readonly machineDetail: MachineSta
return (
<>
<MachineAnimator processingTime={machineDetail.point.action.processTime} handleCallBack={handleCallBack} currentPhase={currentPhase} machineUuid={machineDetail.modelUuid} machineStatus={machineStatus} reset={reset} />
</>

View File

@@ -23,7 +23,7 @@ export function useTriggerHandler() {
const { addHumanToMonitor } = useHumanEventManager();
const { getVehicleById } = vehicleStore();
const { getHumanById, setHumanScheduled } = humanStore();
const { getMachineById } = machineStore();
const { getMachineById, setMachineActive } = machineStore();
const { getStorageUnitById } = storageUnitStore();
const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore();
@@ -325,48 +325,17 @@ export function useTriggerHandler() {
if (model?.type === 'vehicle') {
const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid);
if (human) {
if (human.isActive === false && human.state === 'idle') {
const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid);
if (vehicle) {
if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
// Handle current action from vehicle
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (vehicle) {
addVehicleToMonitor(vehicle.modelUuid, () => {
addHumanToMonitor(human.modelUuid, () => {
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
handleAction(action, materialId);
} else {
// Handle current action using Event Manager
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
addVehicleToMonitor(vehicle.modelUuid, () => {
handleAction(action, materialId);
})
}
}
} else {
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
addHumanToMonitor(human.modelUuid, () => {
const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (vehicle) {
if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
// Handle current action from vehicle
setIsPaused(materialId, true);
handleAction(action, materialId);
} else {
// Handle current action using Event Manager
setIsPaused(materialId, true);
addVehicleToMonitor(vehicle.modelUuid, () => {
handleAction(action, materialId);
})
}
}
}, action.actionUuid)
}, action.actionUuid)
})
}
}
} else if (model?.type === 'transfer') {
@@ -374,61 +343,30 @@ export function useTriggerHandler() {
if (human) {
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
addHumanToMonitor(human.modelUuid, () => {
const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (conveyor) {
// Handle current action using Event Manager
setIsPaused(materialId, true);
addConveyorToMonitor(conveyor.modelUuid, () => {
addHumanToMonitor(human.modelUuid, () => {
handleAction(action, materialId);
}, action.actionUuid)
}, [materialId])
}
}, action.actionUuid)
const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (conveyor) {
addConveyorToMonitor(conveyor.modelUuid, () => {
addHumanToMonitor(human.modelUuid, () => {
setIsPaused(materialId, true);
handleAction(action, materialId);
}, action.actionUuid)
}, [materialId])
}
}
} else if (model?.type === 'machine') {
const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid);
if (human) {
if (human.isActive === false && human.state === 'idle') {
const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid);
if (machine) {
if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) {
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (machine) {
addMachineToMonitor(machine.modelUuid, () => {
addHumanToMonitor(human.modelUuid, () => {
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
handleAction(action, materialId);
} else {
// Handle current action using Event Manager
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
addMachineToMonitor(machine.modelUuid, () => {
handleAction(action, materialId);
})
}
}
} else {
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
addHumanToMonitor(human.modelUuid, () => {
const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (machine) {
if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) {
setIsPaused(materialId, true);
handleAction(action, materialId);
} else {
// Handle current action using Event Manager
setIsPaused(materialId, true);
addMachineToMonitor(machine.modelUuid, () => {
handleAction(action, materialId);
})
}
}
}, action.actionUuid);
}, action.actionUuid);
})
}
}
} else {
@@ -465,7 +403,6 @@ export function useTriggerHandler() {
addHumanToMonitor(human.modelUuid, () => {
handleAction(action, materialId)
}, action.actionUuid);
}
}
}
@@ -478,8 +415,10 @@ export function useTriggerHandler() {
if (toEvent?.type === 'transfer') {
// Vehicle to Transfer
if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
const conveyor = getConveyorById(toEvent.modelUuid);
const material = getMaterialById(materialId);
if (material) {
if (material && conveyor) {
const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid);
setPreviousLocation(material.materialId, {
@@ -494,23 +433,29 @@ export function useTriggerHandler() {
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
});
setIsPaused(materialId, false);
setIsVisible(materialId, true);
if (action &&
action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid &&
action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid
) {
setNextLocation(material.materialId, {
modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid,
pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid,
modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '',
pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '',
});
handleAction(action, materialId);
}
addConveyorToMonitor(conveyor.modelUuid, () => {
setIsPaused(materialId, false);
handleAction(action, materialId);
})
}
}
}
} else if (toEvent?.type === 'vehicle') {
// Vehicle to Vehicle
@@ -539,7 +484,6 @@ export function useTriggerHandler() {
setNextLocation(material.materialId, null);
if (action && armBot) {
if (armBot.isActive === false && armBot.state === 'idle') {
@@ -686,21 +630,24 @@ export function useTriggerHandler() {
const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || '');
if (previousModel) {
if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) {
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId)
} else {
addConveyorToMonitor(conveyor.modelUuid,
() => {
handleAction(action, materialId)
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId);
}
)
}
} else {
handleAction(action, materialId)
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId);
}
// handleAction(action, materialId)
}
} else {
handleAction(action, materialId)
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId);
}
} else {
@@ -716,21 +663,24 @@ export function useTriggerHandler() {
const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || '');
if (previousModel) {
if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) {
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId)
} else {
addConveyorToMonitor(conveyor.modelUuid,
() => {
handleAction(action, materialId)
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId);
}
)
}
} else {
handleAction(action, materialId)
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId);
}
// handleAction(action, materialId)
}
} else {
handleAction(action, materialId)
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId);
}
}
);
@@ -770,10 +720,7 @@ export function useTriggerHandler() {
setIsVisible(materialId, false);
if (action && human) {
if (human.isActive === false && human.state === 'idle') {
// Handle current action from arm bot
addHumanToMonitor(human.modelUuid, () => {
const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (model?.type === 'transfer') {
const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
@@ -782,20 +729,20 @@ export function useTriggerHandler() {
if (previousModel) {
if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) {
setHumanScheduled(human.modelUuid, true);
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId)
} else {
setHumanScheduled(human.modelUuid, true);
addConveyorToMonitor(conveyor.modelUuid,
() => {
handleAction(action, materialId)
}
)
addConveyorToMonitor(conveyor.modelUuid, () => {
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId)
})
}
} else {
setHumanScheduled(human.modelUuid, true);
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId)
}
// handleAction(action, materialId)
}
} else if (model?.type === 'vehicle') {
const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
@@ -804,82 +751,26 @@ export function useTriggerHandler() {
// Handle current action from vehicle
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId);
} else {
// Handle current action using Event Manager
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
addVehicleToMonitor(vehicle.modelUuid,
() => {
handleAction(action, materialId);
}
)
addVehicleToMonitor(vehicle.modelUuid, () => {
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId);
})
}
}
} else {
setHumanScheduled(human.modelUuid, true);
setMachineActive(trigger.triggerUuid, false);
handleAction(action, materialId)
}
} else {
// Handle current action using Event Manager
addHumanToMonitor(human.modelUuid, () => {
const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (model?.type === 'transfer') {
const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (conveyor) {
const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || '');
if (previousModel) {
if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) {
setHumanScheduled(human.modelUuid, true);
handleAction(action, materialId)
} else {
setHumanScheduled(human.modelUuid, true);
addConveyorToMonitor(conveyor.modelUuid,
() => {
handleAction(action, materialId)
}
)
}
} else {
setHumanScheduled(human.modelUuid, true);
handleAction(action, materialId)
}
}
} else if (model?.type === 'vehicle') {
const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (vehicle) {
if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
// Handle current action from vehicle
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
handleAction(action, materialId);
} else {
// Handle current action using Event Manager
setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
addVehicleToMonitor(vehicle.modelUuid,
() => {
handleAction(action, materialId);
}
)
}
}
} else {
setHumanScheduled(human.modelUuid, true);
handleAction(action, materialId)
}
}, action.actionUuid);
}
}, action.actionUuid);
}
}
}
@@ -1439,13 +1330,11 @@ export function useTriggerHandler() {
const material = getMaterialById(materialId);
if (material) {
setIsPaused(material.materialId, false);
setIsPaused(material.materialId, true);
const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid);
const vehicle = getVehicleById(trigger.triggeredAsset?.triggeredModel.modelUuid);
setNextLocation(material.materialId, null);
if (action) {
if (vehicle) {
@@ -1466,16 +1355,15 @@ export function useTriggerHandler() {
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
});
setNextLocation(material.materialId, null);
// Handle current action from vehicle
handleAction(action, materialId);
} else {
setIsPaused(materialId, true);
addVehicleToMonitor(vehicle.modelUuid,
() => {
setIsPaused(materialId, false);
setIsVisible(materialId, false);
setPreviousLocation(material.materialId, {
@@ -1493,6 +1381,7 @@ export function useTriggerHandler() {
setNextLocation(material.materialId, null);
// Handle current action from vehicle
console.log('action: ', action);
handleAction(action, materialId);
}
)

View File

@@ -378,7 +378,6 @@ function DraggableLineSegment({
};
const onPointerMove = (e: ThreeEvent<PointerEvent>) => {
console.log('isAnyDragging: ', isAnyDragging);
if (isAnyDragging !== "line" || activeTool !== 'pen') return;
const intersect = new THREE.Vector3();

View File

@@ -19,7 +19,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, productStore, assetStore } = useSceneContext();
const { removeMaterial, setEndTime, setIsVisible } = materialStore();
const { getStorageUnitById } = storageUnitStore();
const { getHumanById, addCurrentAction, addCurrentMaterial, incrementHumanLoad , incrementLoadCount } = humanStore();
const { getHumanById, addCurrentAction, addCurrentMaterial, incrementHumanLoad } = humanStore();
const { getArmBotById } = armBotStore();
const { getConveyorById } = conveyorStore();
const { triggerPointActions } = useTriggerHandler();
@@ -302,7 +302,6 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
setIsVisible(material.materialId, false);
addCurrentMaterial(humanId, material.materialType, material.materialId);
incrementHumanLoad(humanId, 1);
incrementLoadCount(humanId, 1);
}
return;
}