Merge remote-tracking branch 'origin/main-dev' into feature/agv-edit

This commit is contained in:
2025-07-05 11:20:50 +05:30
68 changed files with 3868 additions and 860 deletions

View File

@@ -3,12 +3,14 @@ import { useEffect, useMemo, useRef, useState } from 'react'
import { useThree } from '@react-three/fiber';
import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi';
import { useParams } from 'react-router-dom';
import { useVersionContext } from '../../version/versionContext';
import { useSceneContext } from '../../../scene/sceneContext';
import ReferenceAisle from './referenceAisle';
import ReferencePoint from '../../point/reference/referencePoint';
import { getUserData } from '../../../../functions/getUserData';
// import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi';
function AisleCreator() {
const { scene, camera, raycaster, gl, pointer } = useThree();
@@ -23,6 +25,7 @@ function AisleCreator() {
const isLeftMouseDown = useRef(false);
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
const [tempPoints, setTempPoints] = useState<Point[]>([]);
@@ -106,7 +109,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -129,7 +147,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -151,7 +184,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -172,7 +220,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -195,7 +258,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -217,7 +295,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -238,7 +331,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -260,7 +368,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}

View File

@@ -1,4 +1,4 @@
import * as THREE from "three"
import * as THREE from "three"
import { useEffect } from 'react'
import { getFloorAssets } from '../../../services/factoryBuilder/asset/floorAsset/getFloorItemsApi';
import { useLoadingProgress, useRenameModeStore, useSelectedFloorItem, useSelectedItem, useSocketStore } from '../../../store/builder/store';
@@ -226,7 +226,8 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
modelUuid: item.modelUuid,
modelName: item.modelName,
position: item.position,
rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle",
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
state: "idle",
type: "storageUnit",
point: {
uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
@@ -242,6 +243,30 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
}
};
addEvent(storageEvent);
} else if (item.eventData.type === 'Human') {
const humanEvent: HumanEventSchema = {
modelUuid: item.modelUuid,
modelName: item.modelName,
position: item.position,
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
state: "idle",
type: "human",
speed: 1,
point: {
uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0],
rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "worker",
loadCapacity: 1,
triggers: []
}
}
}
addEvent(humanEvent);
}
} else {
assets.push({

View File

@@ -170,7 +170,7 @@ async function handleModelLoad(
if (!data || !data.points) return;
const eventData: any = { type: selectedItem.type, };
const eventData: any = { type: selectedItem.type };
if (selectedItem.type === "Conveyor") {
const ConveyorEvent: ConveyorEventSchema = {
@@ -360,6 +360,35 @@ async function handleModelLoad(
position: storageEvent.point.position,
rotation: storageEvent.point.rotation,
};
} else if (selectedItem.type === "Human") {
const humanEvent: HumanEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: newFloorItem.rotation,
state: "idle",
type: "human",
speed: 1,
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [data.points[0].x, data.points[0].y, data.points[0].z],
rotation: [0, 0, 0],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "worker",
loadCapacity: 1,
triggers: []
}
}
}
addEvent(humanEvent);
eventData.point = {
uuid: humanEvent.point.uuid,
position: humanEvent.point.position,
rotation: humanEvent.point.rotation,
}
}
const completeData = {

View File

@@ -6,7 +6,7 @@ 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 { AssetBoundingBox } from '../../functions/assetBoundingBox';
import { CameraControls, Html } from '@react-three/drei';
import { CameraControls } from '@react-three/drei';
import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore';
import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore';
import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore';
@@ -15,6 +15,8 @@ import { useParams } from 'react-router-dom';
import { getUserData } from '../../../../../functions/getUserData';
import { useSceneContext } from '../../../../scene/sceneContext';
import { useVersionContext } from '../../../version/versionContext';
import { SkeletonUtils } from 'three-stdlib';
import { useAnimationPlaySpeed } from '../../../../../store/usePlayButtonStore';
function Model({ asset }: { readonly asset: Asset }) {
const { camera, controls, gl } = useThree();
@@ -22,8 +24,9 @@ function Model({ asset }: { readonly asset: Asset }) {
const { toggleView } = useToggleView();
const { subModule } = useSubModuleStore();
const { activeModule } = useModuleStore();
const { speed } = useAnimationPlaySpeed();
const { assetStore, eventStore, productStore } = useSceneContext();
const { assets, removeAsset, setAnimations } = assetStore();
const { removeAsset, setAnimations, resetAnimation, setAnimationComplete, setCurrentAnimation: setAnmationAnimation } = assetStore();
const { setTop } = useTopData();
const { setLeft } = useLeftData();
const { getIsEventInProduct } = productStore();
@@ -33,7 +36,7 @@ function Model({ asset }: { readonly asset: Asset }) {
const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset();
const { socket } = useSocketStore();
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
const { setSelectedFloorItem } = useSelectedFloorItem();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { limitDistance } = useLimitDistance();
const { renderDistance } = useRenderDistance();
const [isRendered, setIsRendered] = useState(false);
@@ -46,13 +49,18 @@ function Model({ asset }: { readonly asset: Asset }) {
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const { userId, organization } = getUserData();
const [animationNames, setAnimationNames] = useState<string[]>([]);
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;
useEffect(() => {
setDeletableFloorItem(null);
}, [activeModule, toolMode])
if (selectedFloorItem === null) {
resetAnimation(asset.modelUuid);
}
}, [activeModule, toolMode, selectedFloorItem])
useEffect(() => {
const loader = new GLTFLoader();
@@ -62,40 +70,21 @@ function Model({ asset }: { readonly asset: Asset }) {
loader.setDRACOLoader(dracoLoader);
const loadModel = async () => {
try {
// Check Cache
// const assetId = asset.assetId;
// const cachedModel = THREE.Cache.get(assetId);
// if (cachedModel) {
// setGltfScene(cachedModel.scene.clone());
// calculateBoundingBox(cachedModel.scene);
// return;
// }
// Check Cache
// const assetId = asset.assetId;
// console.log('assetId: ', assetId);
// const cachedModel = THREE.Cache.get(assetId);
// console.log('cachedModel: ', cachedModel);
// if (cachedModel) {
// setGltfScene(cachedModel.scene.clone());
// calculateBoundingBox(cachedModel.scene);
// return;
// }
const assetId = asset.assetId;
const cachedModel = THREE.Cache.get(assetId);
if (cachedModel) {
const clonedScene = cachedModel.scene.clone();
clonedScene.animations = cachedModel.animations || [];
setGltfScene(clonedScene);
calculateBoundingBox(clonedScene);
if (cachedModel.animations && clonedScene.animations.length > 0) {
const animationName = clonedScene.animations.map((clip: any) => clip.name);
setAnimationNames(animationName)
const clone: any = SkeletonUtils.clone(cachedModel.scene);
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(clonedScene);
mixerRef.current = new THREE.AnimationMixer(clone);
clonedScene.animations.forEach((animation: any) => {
clone.animations.forEach((animation: any) => {
const action = mixerRef.current!.clipAction(animation);
actions.current[animation.name] = action;
});
@@ -293,28 +282,50 @@ function Model({ asset }: { readonly asset: Asset }) {
clearSelectedAsset()
}
}
const handleAnimationComplete = useCallback(() => {
if (asset.animationState) {
setAnimationComplete(asset.modelUuid, true);
}
}, [asset.animationState]);
useFrame((_, delta) => {
if (mixerRef.current) {
mixerRef.current.update(delta);
mixerRef.current.update(delta * speed);
}
});
useEffect(() => {
const handlePlay = (clipName: string) => {
if (!mixerRef.current) return;
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) {
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());
}
const action = actions.current[clipName];
if (action && asset.animationState?.playing) {
action.reset().setLoop(THREE.LoopOnce, 1).play();
return () => {
if (mixerRef.current) {
mixerRef.current.removeEventListener('finished', handleAnimationComplete);
}
};
handlePlay(asset.animationState?.current || '');
}, [asset])
}, [asset.animationState?.current, asset.animationState?.isPlaying]);
return (
<group
@@ -362,17 +373,6 @@ function Model({ asset }: { readonly asset: Asset }) {
<AssetBoundingBox boundingBox={boundingBox} />
)
)}
{/* <group >
<Html>
<div style={{ position: 'absolute', }}>
{animationNames.map((name) => (
<button key={name} onClick={() => handlePlay(name)} style={{ margin: 4 }}>
{name}
</button>
))}
</div>
</Html>
</group> */}
</group >
);
}

View File

@@ -10,8 +10,8 @@ import { useParams } from 'react-router-dom';
import { useVersionContext } from '../version/versionContext';
import { useSceneContext } from '../../scene/sceneContext';
import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi';
import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi';
// import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi';
// import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi';
// import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
// import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi';
// import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi';
@@ -159,7 +159,22 @@ function Point({ point }: { readonly point: Point }) {
const updatedAisles = getAislesByPointId(point.pointUuid);
if (updatedAisles.length > 0 && projectId) {
updatedAisles.forEach((updatedAisle) => {
upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: updatedAisle.aisleUuid,
points: updatedAisle.points,
type: updatedAisle.type
})
})
}
} else if (point.pointType === 'Wall') {
@@ -238,7 +253,22 @@ function Point({ point }: { readonly point: Point }) {
if (removedAisles.length > 0) {
removedAisles.forEach(aisle => {
if (projectId) {
deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '')
// API
// deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '');
// SOCKET
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid
}
socket.emit('v1:model-aisle:delete', data);
}
});
setHoveredPoint(null);

View File

@@ -20,7 +20,7 @@ function ZoneCreator() {
const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore();
const { zoneStore } = useSceneContext();
const { addZone, getZonePointById, getZoneByPoints } = zoneStore();
const { zones, addZone, getZonePointById, getZoneByPoints } = zoneStore();
const drag = useRef(false);
const isLeftMouseDown = useRef(false);
const { selectedVersionStore } = useVersionContext();
@@ -32,6 +32,7 @@ function ZoneCreator() {
const [isCreating, setIsCreating] = useState(false);
const { zoneColor, zoneHeight, snappedPosition, snappedPoint, setSnappedPoint, setSnappedPosition } = useBuilderStore();
useEffect(() => {
const canvasElement = gl.domElement;
@@ -92,7 +93,7 @@ function ZoneCreator() {
if (tempPoints.length > 2 && isCreating && snappedPoint && snappedPoint.pointUuid === tempPoints[0].pointUuid) {
const zone: Zone = {
zoneUuid: THREE.MathUtils.generateUUID(),
zoneName: "Zone",
zoneName: `Zone `,
points: tempPoints,
zoneColor,
zoneHeight,