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

This commit is contained in:
2025-07-29 09:11:53 +05:30
10 changed files with 150 additions and 183 deletions

View File

@@ -16,12 +16,7 @@ import { getUserData } from "../../../functions/getUserData";
import { useSceneContext } from "../../scene/sceneContext";
import { useVersionContext } from "../version/versionContext";
const gltfLoaderWorker = new Worker(
new URL(
"../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
import.meta.url
)
);
const gltfLoaderWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", import.meta.url));
function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
const { activeModule } = useModuleStore();

View File

@@ -1,47 +1,67 @@
import { Line } from "@react-three/drei";
import { Box3, Vector3 } from "three";
import { Box3, Vector3, Quaternion } from "three";
import { useMemo } from "react";
import { Cylinder } from "@react-three/drei";
export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => {
const { points, size, center } = useMemo(() => {
if (!boundingBox) { return { points: [], center: new Vector3(), size: new Vector3(), }; }
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 min = boundingBox.min;
const max = boundingBox.max;
const center = boundingBox.getCenter(new Vector3());
const size = boundingBox.getSize(new Vector3());
const edges: Array<[number, number, number]> = [
[min.x, min.y, min.z], [max.x, min.y, min.z],
[max.x, min.y, min.z], [max.x, max.y, min.z],
[max.x, max.y, min.z], [min.x, max.y, min.z],
[min.x, max.y, min.z], [min.x, min.y, min.z],
[min.x, min.y, max.z], [max.x, min.y, max.z],
[max.x, min.y, max.z], [max.x, max.y, max.z],
[max.x, max.y, max.z], [min.x, max.y, max.z],
[min.x, max.y, max.z], [min.x, min.y, max.z],
[min.x, min.y, min.z], [min.x, min.y, max.z],
[max.x, min.y, min.z], [max.x, min.y, max.z],
[max.x, max.y, min.z], [max.x, max.y, max.z],
[min.x, max.y, min.z], [min.x, max.y, max.z],
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),
];
return { points: edges, center, size };
}, [boundingBox]);
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 (
<group name={name}>
<Line
segments
depthWrite={false}
points={points}
color={color}
lineWidth={lineWidth}
/>
{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} position={center}>
<boxGeometry args={[size.x, size.y, size.z]} />

View File

@@ -3,8 +3,8 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
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, useSelectedAssets, 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';
@@ -16,23 +16,23 @@ import { getUserData } from '../../../../../functions/getUserData';
import { useSceneContext } from '../../../../scene/sceneContext';
import { useVersionContext } from '../../../version/versionContext';
import { SkeletonUtils } from 'three-stdlib';
import { useAnimationPlaySpeed, usePauseButtonStore } from '../../../../../store/usePlayButtonStore';
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
import { ModelAnimator } from './animator/modelAnimator';
function Model({ asset }: { readonly asset: Asset }) {
function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boolean }) {
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
const savedTheme: string = localStorage.getItem("theme") || "light";
const { camera, controls, gl } = useThree();
const { controls, gl } = useThree();
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();
@@ -43,23 +43,15 @@ 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 [isSelected, setIsSelected] = useState(false);
const groupRef = useRef<THREE.Group>(null);
const mixerRef = useRef<THREE.AnimationMixer>();
const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
const [previousAnimation, setPreviousAnimation] = useState<string | null>(null);
const [ikData, setIkData] = useState<any>();
const blendFactor = useRef(0);
const blendDuration = 0.5;
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
@@ -133,16 +125,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;
}
@@ -202,21 +184,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') {
@@ -382,50 +349,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;
@@ -526,9 +449,15 @@ function Model({ asset }: { readonly asset: Asset }) {
{gltfScene && (
<>
{isRendered ? (
<primitive object={gltfScene} />
<>
<primitive object={gltfScene} />
<ModelAnimator asset={asset} gltfScene={gltfScene} />
</>
) : (
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={1} />
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={2.5} />
)}
{isSelected &&
<AssetBoundingBox name='Asset BBox' boundingBox={boundingBox} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} />

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

@@ -164,7 +164,7 @@ function MoveControls2D({
return new THREE.Vector3().subVectors(pointPosition, hitPoint);
}, []);
const movePoints = 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

@@ -42,8 +42,8 @@ const DuplicationControls3D = ({
right: false,
});
const calculateDragOffset = useCallback((point: THREE.Vector3, hitPoint: THREE.Vector3) => {
const pointPosition = new THREE.Vector3().copy(point);
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
const pointPosition = new THREE.Vector3().copy(point.position);
return new THREE.Vector3().subVectors(pointPosition, hitPoint);
}, []);
@@ -120,11 +120,9 @@ const DuplicationControls3D = ({
if (hit) {
if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) {
const assetUuid = duplicatedObjects[0].userData.modelUuid;
const asset = getAssetById(assetUuid);
if (!asset) return;
if (duplicatedObjects[0]) {
const newOffset = calculateDragOffset(new THREE.Vector3(...asset.position), intersectionPoint);
const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid);
if (model) {
const newOffset = calculateDragOffset(model, intersectionPoint);
setDragOffset(newOffset);
}
return;
@@ -177,7 +175,7 @@ const DuplicationControls3D = ({
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit) {
const offset = calculateDragOffset(selectedAssets[0].position, hit);
const offset = calculateDragOffset(selectedAssets[0], hit);
setDragOffset(offset);
}

View File

@@ -46,10 +46,7 @@ function MoveControls3D({
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 mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
const updateBackend = (
productName: string,
@@ -79,9 +76,9 @@ function MoveControls3D({
};
const onKeyUp = (event: KeyboardEvent) => {
const isModifierKey = event.key === "Control" || event.key === "Shift";
const isModifierKey = (!event.shiftKey && !event.ctrlKey);
if (isModifierKey) {
if (isModifierKey && keyEvent !== "") {
setKeyEvent("");
}
};
@@ -196,7 +193,6 @@ function MoveControls3D({
raycaster.setFromCamera(pointer, camera);
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit) {
if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) {
if (movedObjects[0]) {
@@ -209,18 +205,18 @@ function MoveControls3D({
if (dragOffset) {
const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset);
let moveSpeed = keyEvent.includes("Shift") ? 0.05 : 1;
let moveDistance = keyEvent.includes("Shift") ? 0.05 : 1;
const initialBasePosition = initialPositions[movedObjects[0].uuid];
const positionDifference = new THREE.Vector3().subVectors(rawBasePosition, initialBasePosition);
const adjustedDifference = positionDifference.multiplyScalar(moveSpeed);
let adjustedDifference = positionDifference.multiplyScalar(moveDistance);
const baseNewPosition = new THREE.Vector3().addVectors(initialBasePosition, adjustedDifference);
if (keyEvent.includes("Ctrl")) {
baseNewPosition.x = snapControls(baseNewPosition.x, "Ctrl");
baseNewPosition.z = snapControls(baseNewPosition.z, "Ctrl");
baseNewPosition.x = snapControls(baseNewPosition.x, keyEvent);
baseNewPosition.z = snapControls(baseNewPosition.z, keyEvent);
}
movedObjects.forEach((movedAsset: THREE.Object3D) => {

View File

@@ -3,17 +3,15 @@ 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, humanEventManagerRef } = useSceneContext();
const { materialStore, armBotStore, 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, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore();
const { addHumanToMonitor } = useHumanEventManager();
const { getAssetById, setCurrentAnimation } = assetStore();
const { selectedProduct } = selectedProductStore();
const { getArmBotById, addCurrentAction } = armBotStore();
@@ -303,19 +301,10 @@ export function useRetrieveHandler() {
const humanAsset = getAssetById(triggeredModel.modelUuid);
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
if (!action || action.actionType !== 'worker' || !humanEventManagerRef.current) return;
if (!action || action.actionType !== 'worker') return;
let state = humanEventManagerRef.current.humanStates.find(h => h.humanId === triggeredModel.modelUuid);
console.log('state: ', state);
console.log('human: ', human);
const currentCount = retrievalCountRef.current.get(actionUuid) ?? 0;
let conditionMet = false;
if (state) {
console.log('state.actionQueue: ', state.actionQueue);
// state.actionQueue[0].count
}
if (currentCount >= action.loadCount) {
completedActions.push(actionUuid);
hasChanges = true;
@@ -337,12 +326,10 @@ export function useRetrieveHandler() {
removeLastMaterial(storageUnit.modelUuid);
updateCurrentLoad(storageUnit.modelUuid, -1);
incrementHumanLoad(human.modelUuid, 1);
addHumanToMonitor(human.modelUuid, () => {
addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId);
retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`);
addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId);
retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`);
retrievalCountRef.current.set(actionUuid, currentCount + 1);
}, actionUuid)
retrievalCountRef.current.set(actionUuid, currentCount + 1);
}
}
}

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