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

@@ -216,6 +216,7 @@ const GlobalProperties: React.FC = () => {
// setRenderDistance(parseInt(e.target.value));
// }
// }
return (
<div className="global-properties-container">
<section>
@@ -239,12 +240,12 @@ const GlobalProperties: React.FC = () => {
label="Wall Visibility"
onClick={changeWallVisibility}
/>
{/* <InputToggle
<InputToggle
value={shadows}
inputKey="3"
label="Shadows Visibility"
onClick={shadowVisibility}
/> */}
/>
<LabeledButton
label="Reset Camera"
onClick={toggleResetCamera}

View File

@@ -1,5 +1,6 @@
import React from "react";
import InputRange from "../../../../../ui/inputs/InputRange";
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
import SwapAction from "./SwapAction";
interface AssemblyActionProps {
@@ -10,6 +11,15 @@ interface AssemblyActionProps {
disabled?: boolean,
onChange: (value: number) => void;
};
assemblyCount: {
value: number;
min: number;
max: number;
step: number;
defaultValue: string,
disabled: false,
onChange: (value: number) => void;
}
swapOptions: string[];
swapDefaultOption: string;
onSwapSelect: (value: string) => void;
@@ -18,6 +28,7 @@ interface AssemblyActionProps {
const AssemblyAction: React.FC<AssemblyActionProps> = ({
processTime,
assemblyCount,
swapOptions,
swapDefaultOption,
onSwapSelect,
@@ -34,6 +45,21 @@ const AssemblyAction: React.FC<AssemblyActionProps> = ({
onClick={() => { }}
onChange={processTime.onChange}
/>
{assemblyCount && (
<InputWithDropDown
label="Assembly Count"
value={assemblyCount.value.toString()}
min={assemblyCount.min}
max={assemblyCount.max}
disabled={assemblyCount.disabled}
defaultValue={assemblyCount.defaultValue}
step={assemblyCount.step}
activeOption="unit"
onClick={() => { }}
onChange={(value) => assemblyCount.onChange(parseInt(value))}
/>
)}
<SwapAction
options={swapOptions}
defaultOption={swapDefaultOption}

View File

@@ -11,7 +11,7 @@ interface WorkerActionProps {
disabled?: boolean;
onChange: (value: string) => void;
};
loadCount?: {
loadCount: {
value: number;
min: number;
max: number;

View File

@@ -20,6 +20,7 @@ function HumanMechanics() {
const [activeOption, setActiveOption] = useState<"worker" | "assembly">("worker");
const [speed, setSpeed] = useState("0.5");
const [loadCount, setLoadCount] = useState(0);
const [assemblyCount, setAssemblyCount] = useState(0);
const [loadCapacity, setLoadCapacity] = useState("1");
const [processTime, setProcessTime] = useState(10);
const [swappedMaterial, setSwappedMaterial] = useState("Default material");
@@ -56,6 +57,7 @@ function HumanMechanics() {
setLoadCapacity(firstAction.loadCapacity.toString());
setActiveOption(firstAction.actionType);
setLoadCount(firstAction.loadCount || 0);
setAssemblyCount(firstAction.assemblyCount || 0);
setProcessTime(firstAction.processTime || 10);
setSwappedMaterial(firstAction.swapMaterial || "Default material");
}
@@ -84,6 +86,7 @@ function HumanMechanics() {
setActiveOption(newCurrentAction.actionType);
setLoadCapacity(newCurrentAction.loadCapacity.toString());
setLoadCount(newCurrentAction.loadCount || 0);
setAssemblyCount(newCurrentAction.assemblyCount || 0);
if (newCurrentAction.actionType === 'assembly') {
setProcessTime(newCurrentAction.processTime || 10);
@@ -200,6 +203,28 @@ function HumanMechanics() {
setLoadCount(value);
};
const handleAssemblyCountChange = (value: number) => {
if (!currentAction || !selectedPointData || !selectedAction.actionId) return;
const updatedAction = { ...currentAction, assemblyCount: value };
const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action);
const updatedPoint = { ...selectedPointData, actions: updatedActions };
const event = updateAction(
selectedProduct.productUuid,
selectedAction.actionId,
updatedAction
);
if (event) {
updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event);
}
setCurrentAction(updatedAction);
setSelectedPointData(updatedPoint);
setAssemblyCount(value);
};
const handleProcessTimeChange = (value: number) => {
if (!currentAction || !selectedPointData || !selectedAction.actionId) return;
@@ -281,6 +306,7 @@ function HumanMechanics() {
actionName: `Action ${selectedPointData.actions.length + 1}`,
actionType: "worker",
loadCount: 1,
assemblyCount: 1,
loadCapacity: 1,
processTime: 10,
triggers: [],
@@ -304,15 +330,15 @@ function HumanMechanics() {
setSelectedAction(newAction.actionUuid, newAction.actionName);
};
const handleDeleteAction = () => {
if (!selectedPointData || !selectedAction.actionId) return;
const handleDeleteAction = (actionUuid: string) => {
if (!selectedPointData || !actionUuid) return;
const updatedActions = selectedPointData.actions.filter(action => action.actionUuid !== selectedAction.actionId);
const updatedActions = selectedPointData.actions.filter(action => action.actionUuid !== actionUuid);
const updatedPoint = { ...selectedPointData, actions: updatedActions };
const event = removeAction(
selectedProduct.productUuid,
selectedAction.actionId
actionUuid
);
if (event) {
@@ -389,7 +415,7 @@ function HumanMechanics() {
}}
loadCount={{
value: loadCount,
min: 0,
min: 1,
max: 20,
step: 1,
defaultValue: "1",
@@ -407,6 +433,15 @@ function HumanMechanics() {
max: 60,
onChange: handleProcessTimeChange,
}}
assemblyCount={{
value: assemblyCount,
min: 1,
max: 20,
step: 1,
defaultValue: "1",
disabled: false,
onChange: handleAssemblyCountChange,
}}
swapOptions={["Default material", "Material 1", "Material 2", "Material 3"]}
swapDefaultOption={swappedMaterial}
onSwapSelect={handleMaterialChange}

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;
}

View File

@@ -14,7 +14,6 @@ import FollowPerson from "../components/templates/FollowPerson";
import { useLogger } from "../components/ui/log/LoggerContext";
import RenderOverlay from "../components/templates/Overlay";
import LogList from "../components/ui/log/LogList";
import { useToggleStore } from "../store/useUIToggleStore";
import { getAllProjects } from "../services/dashboard/getAllProjects";
import { viewProject } from "../services/dashboard/viewProject";
import ComparisonSceneProvider from "../components/layout/scenes/ComparisonSceneProvider";
@@ -31,11 +30,9 @@ import { handleCanvasCursors } from "../utils/mouseUtils/handleCanvasCursors";
const Project: React.FC = () => {
let navigate = useNavigate();
const echo = useLogger();
const { setToggleUI } = useToggleStore();
const { setActiveModule } = useModuleStore();
const { setUserName } = useUserName();
const { setOrganization } = useOrganization();
const { isVersionSaved } = useSaveVersion();
const { projectId } = useParams();
const { setProjectName } = useProjectName();
const { userId, email, organization, userName } = getUserData();
@@ -99,13 +96,6 @@ const Project: React.FC = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [projectId]);
useEffect(() => {
if (!isVersionSaved) {
setToggleUI(true, true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isVersionSaved]);
useEffect(() => {
setActiveModule("builder");
if (email) {

View File

@@ -0,0 +1,21 @@
import { Vector3 } from "three";
onmessage = function (e) {
const { modelUuid, assetPosition, cameraPosition, limitDistance, renderDistance, isRendered, } = e.data;
const assetVec = new Vector3(assetPosition.x, assetPosition.y, assetPosition.z);
const cameraVec = new Vector3(cameraPosition.x, cameraPosition.y, cameraPosition.z);
const distance = assetVec.distanceTo(cameraVec);
if (limitDistance) {
if (!isRendered && distance <= renderDistance) {
postMessage({ shouldRender: true, modelUuid });
} else if (isRendered && distance > renderDistance) {
postMessage({ shouldRender: false, modelUuid });
}
} else {
if (!isRendered) {
postMessage({ shouldRender: true, modelUuid });
}
}
};

View File

@@ -1,12 +1,5 @@
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
loader.setDRACOLoader(dracoLoader);
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
onmessage = async (event) => {
@@ -17,8 +10,8 @@ onmessage = async (event) => {
);
for (const item of uniqueItems) {
if(item.assetId === null || item.assetId === undefined) {
continue; // Skip items without a valid assetId
if (item.assetId === null || item.assetId === undefined) {
continue;
}
const modelID = item.assetId;
const indexedDBModel = await retrieveGLTF(modelID);
@@ -37,5 +30,5 @@ onmessage = async (event) => {
}
}
postMessage({ message: 'done' })
postMessage({ message: 'done' });
};

View File

@@ -28,7 +28,7 @@ interface FloorStore {
getFloorById: (uuid: string) => Floor | undefined;
getFloorsByPointId: (uuid: string) => Floor[] | [];
getFloorByPoints: (points: Point[]) => Floor | undefined;
getFloorsByPoints: (points: [Point, Point]) => Floor[] | [];
getFloorPointById: (uuid: string) => Point | undefined;
getConnectedPoints: (uuid: string) => Point[];
}
@@ -74,10 +74,13 @@ export const createFloorStore = () => {
const updatedFloors: Floor[] = [];
set(state => {
const newFloors: Floor[] = [];
for (const floor of state.floors) {
const pointIndex = floor.points.findIndex(p => p.pointUuid === pointUuid);
if (pointIndex === -1) {
updatedFloors.push(JSON.parse(JSON.stringify(floor)));
newFloors.push(floor);
continue;
}
@@ -87,11 +90,13 @@ export const createFloorStore = () => {
removedFloors.push(JSON.parse(JSON.stringify(floor)));
continue;
}
floor.points = remainingPoints;
updatedFloors.push(JSON.parse(JSON.stringify(floor)));
const updatedFloor = { ...floor, points: remainingPoints };
updatedFloors.push(JSON.parse(JSON.stringify(updatedFloor)));
newFloors.push(updatedFloor);
}
state.floors = updatedFloors;
state.floors = newFloors;
});
return { removedFloors, updatedFloors };
@@ -102,6 +107,7 @@ export const createFloorStore = () => {
const updatedFloors: Floor[] = [];
set(state => {
const newFloors: Floor[] = [];
for (const floor of state.floors) {
const indices = floor.points.map((p, i) => ({ uuid: p.pointUuid, index: i }));
@@ -110,7 +116,7 @@ export const createFloorStore = () => {
const idxB = indices.find(i => i.uuid === pointB.pointUuid)?.index ?? -1;
if (idxA === -1 || idxB === -1) {
updatedFloors.push(JSON.parse(JSON.stringify(floor)));
newFloors.push(floor);
continue;
}
@@ -120,7 +126,7 @@ export const createFloorStore = () => {
(idxB === 0 && idxA === floor.points.length - 1);
if (!areAdjacent) {
updatedFloors.push(JSON.parse(JSON.stringify(floor)));
newFloors.push(floor);
continue;
}
@@ -129,14 +135,15 @@ export const createFloorStore = () => {
);
if (remainingPoints.length > 2) {
floor.points = remainingPoints;
updatedFloors.push(JSON.parse(JSON.stringify(floor)));
const updatedFloor = { ...floor, points: remainingPoints };
updatedFloors.push(JSON.parse(JSON.stringify(updatedFloor)));
newFloors.push(updatedFloor);
} else {
removedFloors.push(JSON.parse(JSON.stringify(floor)));
}
}
state.floors = updatedFloors;
state.floors = newFloors;
});
return { removedFloors, updatedFloors };
@@ -253,12 +260,32 @@ export const createFloorStore = () => {
});
},
getFloorByPoints: (points) => {
return get().floors.find(floor => {
const floorPointIds = new Set(floor.points.map(p => p.pointUuid));
const givenPointIds = new Set(points.map(p => p.pointUuid));
return floorPointIds.size === givenPointIds.size && [...floorPointIds].every(id => givenPointIds.has(id));
});
getFloorsByPoints: ([pointA, pointB]) => {
const Floors: Floor[] = [];
for (const floor of get().floors) {
const indices = floor.points.map((p, i) => ({ uuid: p.pointUuid, index: i }));
const idxA = indices.find(i => i.uuid === pointA.pointUuid)?.index ?? -1;
const idxB = indices.find(i => i.uuid === pointB.pointUuid)?.index ?? -1;
if (idxA === -1 || idxB === -1) {
continue;
}
const areAdjacent =
Math.abs(idxA - idxB) === 1 ||
(idxA === 0 && idxB === floor.points.length - 1) ||
(idxB === 0 && idxA === floor.points.length - 1);
if (!areAdjacent) {
continue;
}
Floors.push(JSON.parse(JSON.stringify(floor)));
}
return Floors;
},
getFloorPointById: (pointUuid) => {

View File

@@ -0,0 +1,78 @@
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
import { undoRedoConfig } from '../../types/world/worldConstants';
type UndoRedo2DStore = {
undoStack: UndoRedo2DTypes[];
redoStack: UndoRedo2DTypes[];
push2D: (entry: UndoRedo2DTypes) => void;
undo2D: () => UndoRedo2DTypes | undefined;
redo2D: () => UndoRedo2DTypes | undefined;
clearUndoRedo2D: () => void;
peekUndo2D: () => UndoRedo2DTypes | undefined;
peekRedo2D: () => UndoRedo2DTypes | undefined;
};
export const createUndoRedo2DStore = () => {
return create<UndoRedo2DStore>()(
immer((set, get) => ({
undoStack: [],
redoStack: [],
push2D: (entry) => {
set((state) => {
state.undoStack.push(entry);
if (state.undoStack.length > undoRedoConfig.undoRedoCount) {
state.undoStack.shift();
}
state.redoStack = [];
});
},
undo2D: () => {
let lastAction: UndoRedo2DTypes | undefined;
set((state) => {
lastAction = state.undoStack.pop();
if (lastAction) {
state.redoStack.unshift(lastAction);
}
});
return lastAction;
},
redo2D: () => {
let redoAction: UndoRedo2DTypes | undefined;
set((state) => {
redoAction = state.redoStack.shift();
if (redoAction) {
state.undoStack.push(redoAction);
}
});
return redoAction;
},
clearUndoRedo2D: () => {
set((state) => {
state.undoStack = [];
state.redoStack = [];
});
},
peekUndo2D: () => {
const stack = get().undoStack;
return stack.length > 0 ? stack[stack.length - 1] : undefined;
},
peekRedo2D: () => {
const stack = get().redoStack;
return stack.length > 0 ? stack[0] : undefined;
},
}))
)
}
export type UndoRedo2DStoreType = ReturnType<typeof createUndoRedo2DStore>;

View File

@@ -21,7 +21,7 @@ interface ZoneStore {
getZoneById: (uuid: string) => Zone | undefined;
getZonesByPointId: (uuid: string) => Zone[] | [];
getZoneByPoints: (points: Point[]) => Zone | undefined;
getZonesByPoints: (points: Point[]) => Zone[] | [];
getZonePointById: (uuid: string) => Point | undefined;
getConnectedPoints: (uuid: string) => Point[];
}
@@ -76,10 +76,13 @@ export const createZoneStore = () => {
const updatedZones: Zone[] = [];
set(state => {
const newZones: Zone[] = [];
for (const zone of state.zones) {
const pointIndex = zone.points.findIndex(p => p.pointUuid === pointUuid);
if (pointIndex === -1) {
updatedZones.push(JSON.parse(JSON.stringify(zone)));
newZones.push(zone);
continue;
}
@@ -89,11 +92,13 @@ export const createZoneStore = () => {
removedZones.push(JSON.parse(JSON.stringify(zone)));
continue;
}
zone.points = remainingPoints;
updatedZones.push(JSON.parse(JSON.stringify(zone)));
const updatedZone = { ...zone, points: remainingPoints };
updatedZones.push(JSON.parse(JSON.stringify(updatedZone)));
newZones.push(updatedZone);
}
state.zones = updatedZones;
state.zones = newZones;
});
return { removedZones, updatedZones };
@@ -104,6 +109,7 @@ export const createZoneStore = () => {
const updatedZones: Zone[] = [];
set(state => {
const newZones: Zone[] = [];
for (const zone of state.zones) {
const indices = zone.points.map((p, i) => ({ uuid: p.pointUuid, index: i }));
@@ -112,7 +118,7 @@ export const createZoneStore = () => {
const idxB = indices.find(i => i.uuid === pointB.pointUuid)?.index ?? -1;
if (idxA === -1 || idxB === -1) {
updatedZones.push(JSON.parse(JSON.stringify(zone)));
newZones.push(zone);
continue;
}
@@ -122,7 +128,7 @@ export const createZoneStore = () => {
(idxB === 0 && idxA === zone.points.length - 1);
if (!areAdjacent) {
updatedZones.push(JSON.parse(JSON.stringify(zone)));
newZones.push(zone);
continue;
}
@@ -131,14 +137,15 @@ export const createZoneStore = () => {
);
if (remainingPoints.length > 2) {
zone.points = remainingPoints;
updatedZones.push(JSON.parse(JSON.stringify(zone)));
const updatedZone = { ...zone, points: remainingPoints };
updatedZones.push(JSON.parse(JSON.stringify(updatedZone)));
newZones.push(updatedZone);
} else {
removedZones.push(JSON.parse(JSON.stringify(zone)));
}
}
state.zones = updatedZones;
state.zones = newZones;
});
return { removedZones, updatedZones };
@@ -180,12 +187,32 @@ export const createZoneStore = () => {
});
},
getZoneByPoints: (points) => {
return get().zones.find(zone => {
const zonePointIds = new Set(zone.points.map(p => p.pointUuid));
const givenPointIds = new Set(points.map(p => p.pointUuid));
return zonePointIds.size === givenPointIds.size && [...zonePointIds].every(id => givenPointIds.has(id));
});
getZonesByPoints: ([pointA, pointB]) => {
const Zones: Zone[] = [];
for (const zone of get().zones) {
const indices = zone.points.map((p, i) => ({ uuid: p.pointUuid, index: i }));
const idxA = indices.find(i => i.uuid === pointA.pointUuid)?.index ?? -1;
const idxB = indices.find(i => i.uuid === pointB.pointUuid)?.index ?? -1;
if (idxA === -1 || idxB === -1) {
continue;
}
const areAdjacent =
Math.abs(idxA - idxB) === 1 ||
(idxA === 0 && idxB === zone.points.length - 1) ||
(idxB === 0 && idxA === zone.points.length - 1);
if (!areAdjacent) {
continue;
}
Zones.push(JSON.parse(JSON.stringify(zone)));
}
return Zones;
},
getZonePointById: (pointUuid) => {

View File

@@ -27,11 +27,6 @@ interface HumansStore {
incrementHumanLoad: (modelUuid: string, incrementBy: number) => void;
decrementHumanLoad: (modelUuid: string, decrementBy: number) => void;
incrementLoadCount: (modelUuid: string, incrementBy: number) => void;
decrementLoadCount: (modelUuid: string, decrementBy: number) => void;
clearLoadCount: (modelUuid: string) => void;
addCurrentMaterial: (modelUuid: string, materialType: string, materialId: string) => void;
setCurrentMaterials: (modelUuid: string, materials: { materialType: string; materialId: string }[]) => void;
removeLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined;
@@ -65,7 +60,6 @@ export const createHumanStore = () => {
isScheduled: false,
idleTime: 0,
activeTime: 0,
totalLoadCount: 0,
currentLoad: 0,
currentMaterials: [],
distanceTraveled: 0
@@ -182,33 +176,6 @@ export const createHumanStore = () => {
});
},
incrementLoadCount: (modelUuid, incrementBy) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.totalLoadCount += incrementBy;
}
});
},
decrementLoadCount: (modelUuid, decrementBy) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.totalLoadCount -= decrementBy;
}
});
},
clearLoadCount: (modelUuid) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.totalLoadCount = 0;
}
});
},
addCurrentMaterial: (modelUuid, materialType, materialId) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);

View File

@@ -215,3 +215,41 @@ interface Aisle {
}
type Aisles = Aisle[];
// Undo/Redo 2D
type UndoRedo2DDataTypeSchema =
| { type: 'Wall'; lineData: Wall; newData?: Wall; timeStamp: string }
| { type: 'Floor'; lineData: Floor; newData?: Floor; timeStamp: string }
| { type: 'Zone'; lineData: Zone; newData?: Zone; timeStamp: string }
| { type: 'Aisle'; lineData: Aisle; newData?: Aisle; timeStamp: string };
type UndoRedo2DLineActionSchema = {
actionType: 'Line-Create' | 'Line-Update' | 'Line-Delete';
point: UndoRedo2DDataTypeSchema;
}
type UndoRedo2DLinesActionSchema = {
actionType: 'Lines-Create' | 'Lines-Update' | 'Lines-Delete';
points: UndoRedo2DDataTypeSchema[];
}
type UndoRedo2DAction = UndoRedo2DLineActionSchema | UndoRedo2DLinesActionSchema;
type UndoRedo2DDraw = {
type: 'Draw';
actions: UndoRedo2DAction[];
};
type UndoRedo2DUi = {
type: 'UI';
action: any; // Define UI actions as needed
}
type UndoRedo2DTypes = UndoRedo2DDraw | UndoRedo2DUi
type UndoRedo2D = {
undoStack: UndoRedo2DTypes[];
redoStack: UndoRedo2DTypes[];
};

View File

@@ -102,6 +102,7 @@ interface HumanAction {
pickUpPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; }
dropPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; }
loadCount: number;
assemblyCount: number;
loadCapacity: number;
triggers: TriggerSchema[];
}
@@ -252,7 +253,6 @@ interface HumanStatus extends HumanEventSchema {
isScheduled: boolean;
idleTime: number;
activeTime: number;
totalLoadCount: number;
currentLoad: number;
currentMaterials: { materialType: string; materialId: string; }[];
distanceTraveled: number;
@@ -262,6 +262,26 @@ interface HumanStatus extends HumanEventSchema {
};
}
type HumanEventState = {
humanId: string;
actionQueue: {
actionType: 'worker' | 'assembly';
actionUuid: string;
actionName: string;
maxLoadCount: number;
maxAssemblyCount: number;
count?: number;
isMonitored: boolean;
isCompleted: boolean;
callback?: () => void;
}[];
isCooldown: boolean;
};
type HumanEventManagerState = {
humanStates: HumanEventState[];
};
// Materials
interface MaterialSchema {
materialId: string;

View File

@@ -1,373 +1,383 @@
const savedTheme: string | null = localStorage.getItem("theme");
export type Controls = {
azimuthRotateSpeed: number;
polarRotateSpeed: number;
truckSpeed: number;
minDistance: number;
maxDistance: number;
maxPolarAngle: number;
leftMouse: number;
forwardSpeed: number;
backwardSpeed: number;
leftSpeed: number;
rightSpeed: number;
azimuthRotateSpeed: number;
polarRotateSpeed: number;
truckSpeed: number;
minDistance: number;
maxDistance: number;
maxPolarAngle: number;
leftMouse: number;
forwardSpeed: number;
backwardSpeed: number;
leftSpeed: number;
rightSpeed: number;
};
export type ThirdPersonControls = {
azimuthRotateSpeed: number;
polarRotateSpeed: number;
truckSpeed: number;
maxDistance: number;
maxPolarAngle: number;
minZoom: number;
maxZoom: number;
targetOffset: number;
cameraHeight: number;
leftMouse: number;
rightMouse: number;
wheelMouse: number;
middleMouse: number;
azimuthRotateSpeed: number;
polarRotateSpeed: number;
truckSpeed: number;
maxDistance: number;
maxPolarAngle: number;
minZoom: number;
maxZoom: number;
targetOffset: number;
cameraHeight: number;
leftMouse: number;
rightMouse: number;
wheelMouse: number;
middleMouse: number;
};
export type ControlsTransition = {
leftMouse: number;
rightMouse: number;
wheelMouse: number;
middleMouse: number;
leftMouse: number;
rightMouse: number;
wheelMouse: number;
middleMouse: number;
};
export type TwoDimension = {
defaultPosition: [x: number, y: number, z: number];
defaultTarget: [x: number, y: number, z: number];
defaultAzimuth: number;
minDistance: number;
leftMouse: number;
rightMouse: number;
defaultPosition: [x: number, y: number, z: number];
defaultTarget: [x: number, y: number, z: number];
defaultAzimuth: number;
minDistance: number;
leftMouse: number;
rightMouse: number;
};
export type ThreeDimension = {
defaultPosition: [x: number, y: number, z: number];
defaultTarget: [x: number, y: number, z: number];
defaultRotation: [x: number, y: number, z: number];
defaultAzimuth: number;
boundaryBottom: [x: number, y: number, z: number];
boundaryTop: [x: number, y: number, z: number];
minDistance: number;
leftMouse: number;
rightMouse: number;
defaultPosition: [x: number, y: number, z: number];
defaultTarget: [x: number, y: number, z: number];
defaultRotation: [x: number, y: number, z: number];
defaultAzimuth: number;
boundaryBottom: [x: number, y: number, z: number];
boundaryTop: [x: number, y: number, z: number];
minDistance: number;
leftMouse: number;
rightMouse: number;
};
export type GridConfig = {
size: number;
divisions: number;
primaryColor: string;
secondaryColor: string;
position2D: [x: number, y: number, z: number];
position3D: [x: number, y: number, z: number];
size: number;
divisions: number;
primaryColor: string;
secondaryColor: string;
position2D: [x: number, y: number, z: number];
position3D: [x: number, y: number, z: number];
};
export type PlaneConfig = {
position2D: [x: number, y: number, z: number];
position3D: [x: number, y: number, z: number];
rotation: number;
width: number;
height: number;
color: string;
position2D: [x: number, y: number, z: number];
position3D: [x: number, y: number, z: number];
rotation: number;
width: number;
height: number;
color: string;
};
export type ShadowConfig = {
shadowOffset: number;
shadowmapSizewidth: number;
shadowmapSizeheight: number;
shadowcamerafar: number;
shadowcameranear: number;
shadowcameratop: number;
shadowcamerabottom: number;
shadowcameraleft: number;
shadowcameraright: number;
shadowbias: number;
shadownormalBias: number;
shadowMaterialPosition: [x: number, y: number, z: number];
shadowMaterialRotation: [x: number, y: number, z: number];
shadowMaterialOpacity: number;
shadowOffset: number;
shadowmapSizewidth: number;
shadowmapSizeheight: number;
shadowcamerafar: number;
shadowcameranear: number;
shadowcameratop: number;
shadowcamerabottom: number;
shadowcameraleft: number;
shadowcameraright: number;
shadowbias: number;
shadownormalBias: number;
shadowMaterialPosition: [x: number, y: number, z: number];
shadowMaterialRotation: [x: number, y: number, z: number];
shadowMaterialOpacity: number;
};
export type SkyConfig = {
defaultTurbidity: number;
maxTurbidity: number;
minTurbidity: number;
defaultRayleigh: number;
mieCoefficient: number;
mieDirectionalG: number;
skyDistance: number;
defaultTurbidity: number;
maxTurbidity: number;
minTurbidity: number;
defaultRayleigh: number;
mieCoefficient: number;
mieDirectionalG: number;
skyDistance: number;
};
export type AssetConfig = {
defaultScaleBeforeGsap: [number, number, number];
defaultScaleAfterGsap: [number, number, number];
defaultScaleBeforeGsap: [number, number, number];
defaultScaleAfterGsap: [number, number, number];
};
export type PointConfig = {
defaultInnerColor: string;
defaultOuterColor: string;
deleteColor: string;
boxScale: [number, number, number];
wallOuterColor: string;
floorOuterColor: string;
aisleOuterColor: string;
zoneOuterColor: string;
snappingThreshold: number;
helperColor: string;
defaultInnerColor: string;
defaultOuterColor: string;
deleteColor: string;
boxScale: [number, number, number];
wallOuterColor: string;
floorOuterColor: string;
aisleOuterColor: string;
zoneOuterColor: string;
snappingThreshold: number;
helperColor: string;
};
export type LineConfig = {
tubularSegments: number;
radius: number;
radialSegments: number;
wallName: string;
floorName: string;
aisleName: string;
zoneName: string;
referenceName: string;
lineIntersectionPoints: number;
defaultColor: string;
wallColor: string;
floorColor: string;
aisleColor: string;
zoneColor: string;
deleteColor: string;
helperColor: string;
tubularSegments: number;
radius: number;
radialSegments: number;
wallName: string;
floorName: string;
aisleName: string;
zoneName: string;
referenceName: string;
lineIntersectionPoints: number;
defaultColor: string;
wallColor: string;
floorColor: string;
aisleColor: string;
zoneColor: string;
deleteColor: string;
helperColor: string;
};
export type WallConfig = {
defaultColor: string;
height: number;
width: number;
defaultColor: string;
height: number;
width: number;
};
export type FloorConfig = {
defaultColor: string;
height: number;
textureScale: number;
defaultColor: string;
height: number;
textureScale: number;
};
export type RoofConfig = {
defaultColor: string;
height: number;
defaultColor: string;
height: number;
};
export type AisleConfig = {
width: number;
height: number;
defaultColor: string;
width: number;
height: number;
defaultColor: string;
};
export type ZoneConfig = {
defaultColor: string;
height: number;
color: string;
defaultColor: string;
height: number;
color: string;
};
export type ColumnConfig = {
defaultColor: string;
defaultColor: string;
};
export type OutlineConfig = {
assetSelectColor: number;
assetDeleteColor: number;
assetSelectColor: number;
assetDeleteColor: number;
};
export type DistanceConfig = {
minDistance: number;
maxDistance: number;
minDistance: number;
maxDistance: number;
};
export type undoRedoCount = {
undoRedoCount: number;
};
export const firstPersonControls: Controls = {
azimuthRotateSpeed: 0.3, // Speed of rotation around the azimuth axis
polarRotateSpeed: 0.3, // Speed of rotation around the polar axis
truckSpeed: 10, // Speed of truck movement
minDistance: 0, // Minimum distance from the target
maxDistance: 0, // Maximum distance from the target
maxPolarAngle: Math.PI, // Maximum polar angle
leftMouse: 1, // Mouse button for rotation (ROTATE)
forwardSpeed: 0.1, // Speed of forward movement
backwardSpeed: -0.1, // Speed of backward movement
leftSpeed: -0.1, // Speed of left movement
rightSpeed: 0.1, // Speed of right movement
azimuthRotateSpeed: 0.3, // Speed of rotation around the azimuth axis
polarRotateSpeed: 0.3, // Speed of rotation around the polar axis
truckSpeed: 10, // Speed of truck movement
minDistance: 0, // Minimum distance from the target
maxDistance: 0, // Maximum distance from the target
maxPolarAngle: Math.PI, // Maximum polar angle
leftMouse: 1, // Mouse button for rotation (ROTATE)
forwardSpeed: 0.1, // Speed of forward movement
backwardSpeed: -0.1, // Speed of backward movement
leftSpeed: -0.1, // Speed of left movement
rightSpeed: 0.1, // Speed of right movement
};
export const thirdPersonControls: ThirdPersonControls = {
azimuthRotateSpeed: 1, // Speed of rotation around the azimuth axis
polarRotateSpeed: 1, // Speed of rotation around the polar axis
truckSpeed: 2, // Speed of truck movement
maxDistance: 100, // Maximum distance from the target
maxPolarAngle: Math.PI / 2 - 0.05, // Maximum polar angle
minZoom: 6, // Minimum zoom level
maxZoom: 100, // Maximum zoom level
targetOffset: 20, // Offset of the target from the camera
cameraHeight: 30, // Height of the camera
leftMouse: 2, // Mouse button for panning
rightMouse: 1, // Mouse button for rotation
wheelMouse: 8, // Mouse button for zooming
middleMouse: 8, // Mouse button for zooming
azimuthRotateSpeed: 1, // Speed of rotation around the azimuth axis
polarRotateSpeed: 1, // Speed of rotation around the polar axis
truckSpeed: 2, // Speed of truck movement
maxDistance: 100, // Maximum distance from the target
maxPolarAngle: Math.PI / 2 - 0.05, // Maximum polar angle
minZoom: 6, // Minimum zoom level
maxZoom: 100, // Maximum zoom level
targetOffset: 20, // Offset of the target from the camera
cameraHeight: 30, // Height of the camera
leftMouse: 2, // Mouse button for panning
rightMouse: 1, // Mouse button for rotation
wheelMouse: 8, // Mouse button for zooming
middleMouse: 8, // Mouse button for zooming
};
export const controlsTransition: ControlsTransition = {
leftMouse: 0, // Mouse button for no action
rightMouse: 0, // Mouse button for no action
wheelMouse: 0, // Mouse button for no action
middleMouse: 0, // Mouse button for no action
leftMouse: 0, // Mouse button for no action
rightMouse: 0, // Mouse button for no action
wheelMouse: 0, // Mouse button for no action
middleMouse: 0, // Mouse button for no action
};
export const twoDimension: TwoDimension = {
defaultPosition: [0, 100, 0], // Default position of the camera
defaultTarget: [0, 0, 0], // Default target of the camera
defaultAzimuth: 0, // Default azimuth of the camera
minDistance: 25, // Minimum distance from the target
leftMouse: 2, // Mouse button for panning
rightMouse: 0, // Mouse button for no action
defaultPosition: [0, 100, 0], // Default position of the camera
defaultTarget: [0, 0, 0], // Default target of the camera
defaultAzimuth: 0, // Default azimuth of the camera
minDistance: 25, // Minimum distance from the target
leftMouse: 2, // Mouse button for panning
rightMouse: 0, // Mouse button for no action
};
export const camPositionUpdateInterval: number = 200; // Interval for updating the camera position
export const gridConfig: GridConfig = {
size: 150, // Size of the grid
divisions: 75, // Number of divisions in the grid
primaryColor: savedTheme === "dark" ? "#131313" : "#d5d5d5", // Primary color of the grid
secondaryColor: savedTheme === "dark" ? "#434343" : "#e3e3e3", // Secondary color of the grid
size: 150, // Size of the grid
divisions: 75, // Number of divisions in the grid
primaryColor: savedTheme === "dark" ? "#131313" : "#d5d5d5", // Primary color of the grid
secondaryColor: savedTheme === "dark" ? "#434343" : "#e3e3e3", // Secondary color of the grid
position2D: [0, 0.1, 0], // Position of the grid in 2D view
position3D: [0, -0.5, 0], // Position of the grid in 3D view
position2D: [0, 0.1, 0], // Position of the grid in 2D view
position3D: [0, -0.5, 0], // Position of the grid in 3D view
};
export const threeDimension: ThreeDimension = {
defaultPosition: [0, 40, 30], // Default position of the camera
defaultTarget: [0, 0, 0], // Default target of the camera
defaultRotation: [0, 0, 0], // Default rotation of the camera
defaultAzimuth: 0, // Default azimuth of the camera
boundaryBottom: [-gridConfig.size / 2, 0, -gridConfig.size / 2], // Bottom boundary of the camera movement
boundaryTop: [gridConfig.size / 2, 100, gridConfig.size / 2], // Top boundary of the camera movement
minDistance: 1, // Minimum distance from the target
leftMouse: 2, // Mouse button for panning
rightMouse: 1, // Mouse button for rotation
defaultPosition: [0, 40, 30], // Default position of the camera
defaultTarget: [0, 0, 0], // Default target of the camera
defaultRotation: [0, 0, 0], // Default rotation of the camera
defaultAzimuth: 0, // Default azimuth of the camera
boundaryBottom: [-gridConfig.size / 2, 0, -gridConfig.size / 2], // Bottom boundary of the camera movement
boundaryTop: [gridConfig.size / 2, 100, gridConfig.size / 2], // Top boundary of the camera movement
minDistance: 1, // Minimum distance from the target
leftMouse: 2, // Mouse button for panning
rightMouse: 1, // Mouse button for rotation
};
export const planeConfig: PlaneConfig = {
position2D: [0, -0.5, 0], // Position of the plane
position3D: [0, -0.65, 0], // Position of the plane
rotation: -Math.PI / 2, // Rotation of the plane
position2D: [0, -0.5, 0], // Position of the plane
position3D: [0, -0.65, 0], // Position of the plane
rotation: -Math.PI / 2, // Rotation of the plane
width: 150, // Width of the plane
height: 150, // Height of the plane
color: savedTheme === "dark" ? "#323232" : "#f3f3f3", // Color of the plane
width: 150, // Width of the plane
height: 150, // Height of the plane
color: savedTheme === "dark" ? "#323232" : "#f3f3f3", // Color of the plane
};
export const shadowConfig: ShadowConfig = {
shadowOffset: 50, // Offset of the shadow
shadowmapSizewidth: 1024, // Width of the shadow map
shadowmapSizeheight: 1024, // Height of the shadow map
// shadowmapSizewidth: 8192, // Width of the shadow map
// shadowmapSizeheight: 8192, // Height of the shadow map
shadowcamerafar: 70, // Far plane of the shadow camera
shadowcameranear: 0.1, // Near plane of the shadow camera
shadowcameratop: 30, // Top plane of the shadow camera
shadowcamerabottom: -30, // Bottom plane of the shadow camera
shadowcameraleft: -30, // Left plane of the shadow camera
shadowcameraright: 30, // Right plane of the shadow camera
shadowbias: -0.001, // Bias of the shadow
shadownormalBias: 0.02, // Normal bias of the shadow
shadowMaterialPosition: [0, 0.01, 0], // Position of the shadow material
shadowMaterialRotation: [-Math.PI / 2, 0, 0], // Rotation of the shadow material
shadowMaterialOpacity: 0.1, // Opacity of the shadow material
shadowOffset: 50, // Offset of the shadow
// shadowmapSizewidth: 1024, // Width of the shadow map
// shadowmapSizeheight: 1024, // Height of the shadow map
shadowmapSizewidth: 2048, // Width of the shadow map
shadowmapSizeheight: 2048, // Height of the shadow map
// shadowmapSizewidth: 8192, // Width of the shadow map
// shadowmapSizeheight: 8192, // Height of the shadow map
shadowcamerafar: 70, // Far plane of the shadow camera
shadowcameranear: 0.1, // Near plane of the shadow camera
shadowcameratop: 30, // Top plane of the shadow camera
shadowcamerabottom: -30, // Bottom plane of the shadow camera
shadowcameraleft: -30, // Left plane of the shadow camera
shadowcameraright: 30, // Right plane of the shadow camera
shadowbias: -0.001, // Bias of the shadow
shadownormalBias: 0.02, // Normal bias of the shadow
shadowMaterialPosition: [0, 0.01, 0], // Position of the shadow material
shadowMaterialRotation: [-Math.PI / 2, 0, 0], // Rotation of the shadow material
shadowMaterialOpacity: 0.1, // Opacity of the shadow material
};
export const skyConfig: SkyConfig = {
defaultTurbidity: 10.0, // Default turbidity of the sky
maxTurbidity: 20.0, // Maximum turbidity of the sky
minTurbidity: 0.0, // Minimum turbidity of the sky
defaultRayleigh: 1.9, // Default Rayleigh scattering coefficient
mieCoefficient: 0.1, // Mie scattering coefficient
mieDirectionalG: 1.0, // Mie directional G
skyDistance: 2000, // Distance of the sky
defaultTurbidity: 10.0, // Default turbidity of the sky
maxTurbidity: 20.0, // Maximum turbidity of the sky
minTurbidity: 0.0, // Minimum turbidity of the sky
defaultRayleigh: 1.9, // Default Rayleigh scattering coefficient
mieCoefficient: 0.1, // Mie scattering coefficient
mieDirectionalG: 1.0, // Mie directional G
skyDistance: 2000, // Distance of the sky
};
export const assetConfig: AssetConfig = {
defaultScaleBeforeGsap: [0.1, 0.1, 0.1], // Default scale of the assets
defaultScaleAfterGsap: [1, 1, 1], // Default scale of the assets
defaultScaleBeforeGsap: [0.1, 0.1, 0.1], // Default scale of the assets
defaultScaleAfterGsap: [1, 1, 1], // Default scale of the assets
};
export const pointConfig: PointConfig = {
defaultInnerColor: "#ffffff", // Default inner color of the points
defaultOuterColor: "#ffffff", // Default outer color of the points
deleteColor: "#ff0000", // Color of the points when deleting
boxScale: [0.5, 0.5, 0.5], // Scale of the points
wallOuterColor: "#C7C7C7", // Outer color of the wall points
floorOuterColor: "#808080", // Outer color of the floor points
aisleOuterColor: "#FBBC05", // Outer color of the aisle points
zoneOuterColor: "#007BFF", // Outer color of the zone points
snappingThreshold: 1, // Threshold for snapping
helperColor: "#C164FF", // Color of the helper lines
defaultInnerColor: "#ffffff", // Default inner color of the points
defaultOuterColor: "#ffffff", // Default outer color of the points
deleteColor: "#ff0000", // Color of the points when deleting
boxScale: [0.5, 0.5, 0.5], // Scale of the points
wallOuterColor: "#C7C7C7", // Outer color of the wall points
floorOuterColor: "#808080", // Outer color of the floor points
aisleOuterColor: "#FBBC05", // Outer color of the aisle points
zoneOuterColor: "#007BFF", // Outer color of the zone points
snappingThreshold: 1, // Threshold for snapping
helperColor: "#C164FF", // Color of the helper lines
};
export const lineConfig: LineConfig = {
tubularSegments: 64, // Number of tubular segments
radius: 0.15, // Radius of the lines
radialSegments: 8, // Number of radial segments
wallName: "WallLine", // Name of the wall lines
floorName: "FloorLine", // Name of the floor lines
aisleName: "AisleLine", // Name of the aisle lines
zoneName: "ZoneLine", // Name of the zone lines
referenceName: "ReferenceLine", // Name of the reference lines
lineIntersectionPoints: 300, // Number of intersection points
defaultColor: "#000000", // Default color of the lines
wallColor: "#C7C7C7", // Color of the wall lines
floorColor: "#808080", // Color of the floor lines
aisleColor: "#FBBC05", // Color of the aisle lines
zoneColor: "#007BFF", // Color of the zone lines
deleteColor: "#ff0000", // Color of the line when deleting
helperColor: "#C164FF", // Color of the helper lines
tubularSegments: 64, // Number of tubular segments
radius: 0.15, // Radius of the lines
radialSegments: 8, // Number of radial segments
wallName: "WallLine", // Name of the wall lines
floorName: "FloorLine", // Name of the floor lines
aisleName: "AisleLine", // Name of the aisle lines
zoneName: "ZoneLine", // Name of the zone lines
referenceName: "ReferenceLine", // Name of the reference lines
lineIntersectionPoints: 300, // Number of intersection points
defaultColor: "#000000", // Default color of the lines
wallColor: "#C7C7C7", // Color of the wall lines
floorColor: "#808080", // Color of the floor lines
aisleColor: "#FBBC05", // Color of the aisle lines
zoneColor: "#007BFF", // Color of the zone lines
deleteColor: "#ff0000", // Color of the line when deleting
helperColor: "#C164FF", // Color of the helper lines
};
export const wallConfig: WallConfig = {
defaultColor: "#f2f2f2", // Default color of the walls
height: 7.5, // Height of the walls
width: 0.05, // Width of the walls
defaultColor: "#f2f2f2", // Default color of the walls
height: 7.5, // Height of the walls
width: 0.05, // Width of the walls
};
export const floorConfig: FloorConfig = {
defaultColor: "grey", // Default color of the floors
height: 0.1, // Height of the floors
textureScale: 1, // Scale of the floor texture
defaultColor: "grey", // Default color of the floors
height: 0.1, // Height of the floors
textureScale: 1, // Scale of the floor texture
};
export const roofConfig: RoofConfig = {
defaultColor: "grey", // Default color of the roofs
height: 0.1, // Height of the roofs
defaultColor: "grey", // Default color of the roofs
height: 0.1, // Height of the roofs
};
export const aisleConfig: AisleConfig = {
width: 0.1, // Width of the aisles
height: 0.01, // Height of the aisles
defaultColor: '#E2AC09', // Default color of the aisles
width: 0.1, // Width of the aisles
height: 0.01, // Height of the aisles
defaultColor: '#E2AC09', // Default color of the aisles
};
export const zoneConfig: ZoneConfig = {
defaultColor: "black", // Default color of the zones
height: 3,
color: "#8656DF", // Color of the zones
defaultColor: "black", // Default color of the zones
height: 3,
color: "#8656DF", // Color of the zones
};
export const columnConfig: ColumnConfig = {
defaultColor: "White", // Default color of the columns
defaultColor: "White", // Default color of the columns
};
export const outlineConfig: OutlineConfig = {
assetSelectColor: 0x0054fe, // Color of the selected assets
assetDeleteColor: 0xff0000, // Color of the deleted assets
assetSelectColor: 0x0054fe, // Color of the selected assets
assetDeleteColor: 0xff0000, // Color of the deleted assets
};
export const distanceConfig: DistanceConfig = {
minDistance: 20,
maxDistance: 75,
minDistance: 20,
maxDistance: 75,
};
export const undoRedoConfig: undoRedoCount = {
undoRedoCount: 50,
}

View File

@@ -95,10 +95,15 @@ const KeyPressListener: React.FC = () => {
const toggleTo2D = toggleView;
setToggleView(!toggleTo2D);
setToggleThreeD(toggleTo2D);
setToggleUI(toggleTo2D, toggleTo2D);
if (toggleTo2D) {
setSelectedWallItem(null);
setAddAction(null);
setToggleUI(
localStorage.getItem("navBarUiLeft") !== "false",
localStorage.getItem("navBarUiRight") !== "false"
);
} else {
setToggleUI(false, false);
}
setActiveTool("cursor");
setActiveSubTool("cursor");