Merge pull request 'v2' (#76) from v2 into main

Reviewed-on: http://185.100.212.76:7776/Dwinzo-Beta/Dwinzo_dev/pulls/76
This commit was merged in pull request #76.
This commit is contained in:
2025-05-03 13:53:42 +00:00
28 changed files with 1352 additions and 638 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -103,7 +103,7 @@ function ConveyorMechanics() {
const handleSpawnCountChange = (value: string) => {
if (!selectedEventData || !selectedPointData) return;
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
spawnCount: value === "inherit" ? "inherit" : parseFloat(value),
spawnCount: parseFloat(value),
});
if (event) {
@@ -119,7 +119,7 @@ function ConveyorMechanics() {
const handleSpawnIntervalChange = (value: string) => {
if (!selectedEventData || !selectedPointData) return;
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
spawnInterval: value === "inherit" ? "inherit" : parseFloat(value),
spawnInterval: parseFloat(value),
});
if (event) {
@@ -149,7 +149,7 @@ function ConveyorMechanics() {
const handleDelayChange = (value: string) => {
if (!selectedEventData || !selectedPointData) return;
const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, {
delay: value === "inherit" ? "inherit" : parseFloat(value),
delay: parseFloat(value),
});
if (event) {
@@ -271,7 +271,7 @@ function ConveyorMechanics() {
</div>
</div>
<div className="tirgger">
<Trigger selectedPointData={selectedPointData as any} type= {'Conveyor'}/>
<Trigger selectedPointData={selectedPointData as any} type={'Conveyor'} />
</div>
</section>
</>

View File

@@ -1,25 +1,25 @@
import { useFrame, useThree } from "@react-three/fiber";
import {
useActiveTool,
useAsset3dWidget,
useCamMode,
useDeletableFloorItem,
useDeleteTool,
useFloorItems,
useLoadingProgress,
useRenderDistance,
useSelectedFloorItem,
useSelectedItem,
useSocketStore,
useToggleView,
useTransformMode,
useActiveTool,
useAsset3dWidget,
useCamMode,
useDeletableFloorItem,
useDeleteTool,
useFloorItems,
useLoadingProgress,
useRenderDistance,
useSelectedFloorItem,
useSelectedItem,
useSocketStore,
useToggleView,
useTransformMode,
} from "../../../store/store";
import assetVisibility from "../geomentries/assets/assetVisibility";
import { useEffect } from "react";
import * as THREE from "three";
import * as Types from "../../../types/world/worldTypes";
import assetManager, {
cancelOngoingTasks,
cancelOngoingTasks,
} from "../geomentries/assets/assetManager";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
@@ -33,421 +33,420 @@ import useModuleStore from "../../../store/useModuleStore";
import { useEventsStore } from "../../../store/simulation/useEventsStore";
const assetManagerWorker = new Worker(
new URL(
"../../../services/factoryBuilder/webWorkers/assetManagerWorker.js",
import.meta.url
)
new URL(
"../../../services/factoryBuilder/webWorkers/assetManagerWorker.js",
import.meta.url
)
);
const gltfLoaderWorker = new Worker(
new URL(
"../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
import.meta.url
)
new URL(
"../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
import.meta.url
)
);
const FloorItemsGroup = ({
itemsGroup,
hoveredDeletableFloorItem,
AttachedObject,
floorGroup,
tempLoader,
isTempLoader,
plane,
itemsGroup,
hoveredDeletableFloorItem,
AttachedObject,
floorGroup,
tempLoader,
isTempLoader,
plane,
}: any) => {
const state: Types.ThreeState = useThree();
const { raycaster, controls }: any = state;
const { renderDistance } = useRenderDistance();
const { toggleView } = useToggleView();
const { floorItems, setFloorItems } = useFloorItems();
const { camMode } = useCamMode();
const { deleteTool } = useDeleteTool();
const { setDeletableFloorItem } = useDeletableFloorItem();
const { transformMode } = useTransformMode();
const { setSelectedFloorItem } = useSelectedFloorItem();
const { activeTool } = useActiveTool();
const { selectedItem, setSelectedItem } = useSelectedItem();
const { setLoadingProgress } = useLoadingProgress();
const { activeModule } = useModuleStore();
const { socket } = useSocketStore();
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
const { addEvent } = useEventsStore();
const state: Types.ThreeState = useThree();
const { raycaster, controls }: any = state;
const { renderDistance } = useRenderDistance();
const { toggleView } = useToggleView();
const { floorItems, setFloorItems } = useFloorItems();
const { camMode } = useCamMode();
const { deleteTool } = useDeleteTool();
const { setDeletableFloorItem } = useDeletableFloorItem();
const { transformMode } = useTransformMode();
const { setSelectedFloorItem } = useSelectedFloorItem();
const { activeTool } = useActiveTool();
const { selectedItem, setSelectedItem } = useSelectedItem();
const { setLoadingProgress } = useLoadingProgress();
const { activeModule } = useModuleStore();
const { socket } = useSocketStore();
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
const { addEvent } = useEventsStore();
dracoLoader.setDecoderPath(
"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"
);
loader.setDRACOLoader(dracoLoader);
dracoLoader.setDecoderPath(
"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"
);
loader.setDRACOLoader(dracoLoader);
useEffect(() => {
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
useEffect(() => {
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
let totalAssets = 0;
let loadedAssets = 0;
let totalAssets = 0;
let loadedAssets = 0;
const updateLoadingProgress = (progress: number) => {
if (progress < 100) {
setLoadingProgress(progress);
} else if (progress === 100) {
setTimeout(() => {
setLoadingProgress(100);
setTimeout(() => {
setLoadingProgress(0);
}, 1500);
}, 1000);
}
};
const updateLoadingProgress = (progress: number) => {
if (progress < 100) {
setLoadingProgress(progress);
} else if (progress === 100) {
setTimeout(() => {
setLoadingProgress(100);
setTimeout(() => {
setLoadingProgress(0);
}, 1500);
}, 1000);
}
};
getFloorAssets(organization).then((data) => {
if (data.length > 0) {
const uniqueItems = (data as Types.FloorItems).filter(
(item, index, self) =>
index === self.findIndex((t) => t.modelfileID === item.modelfileID)
);
totalAssets = uniqueItems.length;
if (totalAssets === 0) {
updateLoadingProgress(100);
return;
}
gltfLoaderWorker.postMessage({ floorItems: data });
} else {
gltfLoaderWorker.postMessage({ floorItems: [] });
loadInitialFloorItems(
itemsGroup,
setFloorItems,
addEvent,
renderDistance
);
updateLoadingProgress(100);
}
});
gltfLoaderWorker.onmessage = async (event) => {
if (event.data.message === "gltfLoaded" && event.data.modelBlob) {
const blobUrl = URL.createObjectURL(event.data.modelBlob);
loader.load(blobUrl, (gltf) => {
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(event.data.modelID, gltf);
loadedAssets++;
const progress = Math.round((loadedAssets / totalAssets) * 100);
updateLoadingProgress(progress);
if (loadedAssets === totalAssets) {
loadInitialFloorItems(
itemsGroup,
setFloorItems,
addEvent,
renderDistance
);
updateLoadingProgress(100);
}
getFloorAssets(organization).then((data) => {
if (data.length > 0) {
const uniqueItems = (data as Types.FloorItems).filter(
(item, index, self) =>
index === self.findIndex((t) => t.modelfileID === item.modelfileID)
);
totalAssets = uniqueItems.length;
if (totalAssets === 0) {
updateLoadingProgress(100);
return;
}
gltfLoaderWorker.postMessage({ floorItems: data });
} else {
gltfLoaderWorker.postMessage({ floorItems: [] });
loadInitialFloorItems(
itemsGroup,
setFloorItems,
addEvent,
renderDistance
);
updateLoadingProgress(100);
}
});
}
};
}, []);
useEffect(() => {
assetManagerWorker.onmessage = async (event) => {
cancelOngoingTasks(); // Cancel the ongoing process
await assetManager(event.data, itemsGroup, loader);
};
}, [assetManagerWorker]);
gltfLoaderWorker.onmessage = async (event) => {
if (event.data.message === "gltfLoaded" && event.data.modelBlob) {
const blobUrl = URL.createObjectURL(event.data.modelBlob);
useEffect(() => {
if (toggleView) return;
loader.load(blobUrl, (gltf) => {
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(event.data.modelID, gltf);
const uuids: string[] = [];
itemsGroup.current?.children.forEach((child: any) => {
uuids.push(child.uuid);
});
const cameraPosition = state.camera.position;
loadedAssets++;
const progress = Math.round((loadedAssets / totalAssets) * 100);
updateLoadingProgress(progress);
assetManagerWorker.postMessage({
floorItems,
cameraPosition,
uuids,
renderDistance,
});
}, [camMode, renderDistance]);
if (loadedAssets === totalAssets) {
loadInitialFloorItems(
itemsGroup,
setFloorItems,
addEvent,
renderDistance
);
updateLoadingProgress(100);
}
});
}
};
}, []);
useEffect(() => {
const controls: any = state.controls;
const camera: any = state.camera;
useEffect(() => {
assetManagerWorker.onmessage = async (event) => {
cancelOngoingTasks(); // Cancel the ongoing process
await assetManager(event.data, itemsGroup, loader);
};
}, [assetManagerWorker]);
if (controls) {
let intervalId: NodeJS.Timeout | null = null;
const handleChange = () => {
useEffect(() => {
if (toggleView) return;
const uuids: string[] = [];
itemsGroup.current?.children.forEach((child: any) => {
uuids.push(child.uuid);
uuids.push(child.uuid);
});
const cameraPosition = camera.position;
const cameraPosition = state.camera.position;
assetManagerWorker.postMessage({
floorItems,
cameraPosition,
uuids,
renderDistance,
floorItems,
cameraPosition,
uuids,
renderDistance,
});
};
}, [camMode, renderDistance]);
const startInterval = () => {
if (!intervalId) {
intervalId = setInterval(handleChange, 50);
useEffect(() => {
const controls: any = state.controls;
const camera: any = state.camera;
if (controls) {
let intervalId: NodeJS.Timeout | null = null;
const handleChange = () => {
if (toggleView) return;
const uuids: string[] = [];
itemsGroup.current?.children.forEach((child: any) => {
uuids.push(child.uuid);
});
const cameraPosition = camera.position;
assetManagerWorker.postMessage({
floorItems,
cameraPosition,
uuids,
renderDistance,
});
};
const startInterval = () => {
if (!intervalId) {
intervalId = setInterval(handleChange, 50);
}
};
const stopInterval = () => {
handleChange();
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
};
controls.addEventListener("rest", handleChange);
controls.addEventListener("rest", stopInterval);
controls.addEventListener("control", startInterval);
controls.addEventListener("controlend", stopInterval);
return () => {
controls.removeEventListener("rest", handleChange);
controls.removeEventListener("rest", stopInterval);
controls.removeEventListener("control", startInterval);
controls.removeEventListener("controlend", stopInterval);
if (intervalId) {
clearInterval(intervalId);
}
};
}
};
}, [state.controls, floorItems, toggleView, renderDistance]);
const stopInterval = () => {
handleChange();
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
};
useEffect(() => {
const canvasElement = state.gl.domElement;
let drag = false;
let isLeftMouseDown = false;
controls.addEventListener("rest", handleChange);
controls.addEventListener("rest", stopInterval);
controls.addEventListener("control", startInterval);
controls.addEventListener("controlend", stopInterval);
return () => {
controls.removeEventListener("rest", handleChange);
controls.removeEventListener("rest", stopInterval);
controls.removeEventListener("control", startInterval);
controls.removeEventListener("controlend", stopInterval);
if (intervalId) {
clearInterval(intervalId);
}
};
}
}, [state.controls, floorItems, toggleView, renderDistance]);
useEffect(() => {
const canvasElement = state.gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = true;
drag = false;
}
};
const onMouseMove = () => {
console.log('isLeftMouseDown: ', isLeftMouseDown);
if (isLeftMouseDown) {
drag = true;
}
};
const onMouseUp = async (evt: any) => {
if (controls) {
(controls as any).enabled = true;
}
if (evt.button === 0) {
isLeftMouseDown = false;
if (drag) return;
if (deleteTool) {
DeleteFloorItems(
itemsGroup,
hoveredDeletableFloorItem,
setFloorItems,
socket
);
}
const Mode = transformMode;
if (Mode !== null || activeTool === "cursor") {
if (!itemsGroup.current) return;
let intersects = raycaster.intersectObjects(
itemsGroup.current.children,
true
);
if (
intersects.length > 0 &&
intersects[0]?.object?.parent?.parent?.position &&
intersects[0]?.object?.parent?.parent?.scale &&
intersects[0]?.object?.parent?.parent?.rotation
) {
// let currentObject = intersects[0].object;
// while (currentObject) {
// if (currentObject.name === "Scene") {
// break;
// }
// currentObject = currentObject.parent as THREE.Object3D;
// }
// if (currentObject) {
// AttachedObject.current = currentObject as any;
// setSelectedFloorItem(AttachedObject.current!);
// }
} else {
const target = controls.getTarget(new THREE.Vector3());
await controls.setTarget(target.x, 0, target.z, true);
setSelectedFloorItem(null);
}
}
}
};
const onDblClick = async (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = false;
if (drag) return;
const Mode = transformMode;
if (Mode !== null || activeTool === "cursor") {
if (!itemsGroup.current) return;
let intersects = raycaster.intersectObjects(
itemsGroup.current.children,
true
);
if (
intersects.length > 0 &&
intersects[0]?.object?.parent?.parent?.position &&
intersects[0]?.object?.parent?.parent?.scale &&
intersects[0]?.object?.parent?.parent?.rotation
) {
let currentObject = intersects[0].object;
while (currentObject) {
if (currentObject.name === "Scene") {
break;
}
currentObject = currentObject.parent as THREE.Object3D;
const onMouseDown = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = true;
drag = false;
}
if (currentObject) {
AttachedObject.current = currentObject as any;
// controls.fitToSphere(AttachedObject.current!, true);
};
const bbox = new THREE.Box3().setFromObject(
AttachedObject.current
);
const size = bbox.getSize(new THREE.Vector3());
const center = bbox.getCenter(new THREE.Vector3());
const front = new THREE.Vector3(0, 0, 1);
AttachedObject.current.localToWorld(front);
front.sub(AttachedObject.current.position).normalize();
const distance = Math.max(size.x, size.y, size.z) * 2;
const newPosition = center
.clone()
.addScaledVector(front, distance);
controls.setPosition(
newPosition.x,
newPosition.y,
newPosition.z,
true
);
controls.setTarget(center.x, center.y, center.z, true);
controls.fitToBox(AttachedObject.current!, true, {
cover: true,
paddingTop: 5,
paddingLeft: 5,
paddingBottom: 5,
paddingRight: 5,
});
setSelectedFloorItem(AttachedObject.current!);
const onMouseMove = () => {
if (isLeftMouseDown) {
drag = true;
}
};
const onMouseUp = async (evt: any) => {
if (controls) {
(controls as any).enabled = true;
}
if (evt.button === 0) {
isLeftMouseDown = false;
if (drag) return;
if (deleteTool) {
DeleteFloorItems(
itemsGroup,
hoveredDeletableFloorItem,
setFloorItems,
socket
);
}
const Mode = transformMode;
if (Mode !== null || activeTool === "cursor") {
if (!itemsGroup.current) return;
let intersects = raycaster.intersectObjects(
itemsGroup.current.children,
true
);
if (
intersects.length > 0 &&
intersects[0]?.object?.parent?.parent?.position &&
intersects[0]?.object?.parent?.parent?.scale &&
intersects[0]?.object?.parent?.parent?.rotation
) {
// let currentObject = intersects[0].object;
// while (currentObject) {
// if (currentObject.name === "Scene") {
// break;
// }
// currentObject = currentObject.parent as THREE.Object3D;
// }
// if (currentObject) {
// AttachedObject.current = currentObject as any;
// setSelectedFloorItem(AttachedObject.current!);
// }
} else {
const target = controls.getTarget(new THREE.Vector3());
await controls.setTarget(target.x, 0, target.z, true);
setSelectedFloorItem(null);
}
}
}
};
const onDblClick = async (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = false;
if (drag) return;
const Mode = transformMode;
if (Mode !== null || activeTool === "cursor") {
if (!itemsGroup.current) return;
let intersects = raycaster.intersectObjects(
itemsGroup.current.children,
true
);
if (
intersects.length > 0 &&
intersects[0]?.object?.parent?.parent?.position &&
intersects[0]?.object?.parent?.parent?.scale &&
intersects[0]?.object?.parent?.parent?.rotation
) {
let currentObject = intersects[0].object;
while (currentObject) {
if (currentObject.name === "Scene") {
break;
}
currentObject = currentObject.parent as THREE.Object3D;
}
if (currentObject) {
AttachedObject.current = currentObject as any;
// controls.fitToSphere(AttachedObject.current!, true);
const bbox = new THREE.Box3().setFromObject(
AttachedObject.current
);
const size = bbox.getSize(new THREE.Vector3());
const center = bbox.getCenter(new THREE.Vector3());
const front = new THREE.Vector3(0, 0, 1);
AttachedObject.current.localToWorld(front);
front.sub(AttachedObject.current.position).normalize();
const distance = Math.max(size.x, size.y, size.z) * 2;
const newPosition = center
.clone()
.addScaledVector(front, distance);
controls.setPosition(
newPosition.x,
newPosition.y,
newPosition.z,
true
);
controls.setTarget(center.x, center.y, center.z, true);
controls.fitToBox(AttachedObject.current!, true, {
cover: true,
paddingTop: 5,
paddingLeft: 5,
paddingBottom: 5,
paddingRight: 5,
});
setSelectedFloorItem(AttachedObject.current!);
}
} else {
const target = controls.getTarget(new THREE.Vector3());
await controls.setTarget(target.x, 0, target.z, true);
setSelectedFloorItem(null);
}
}
}
};
const onDrop = (event: any) => {
if (!event.dataTransfer?.files[0]) return;
if (selectedItem.id !== "" && event.dataTransfer?.files[0]) {
addAssetModel(
raycaster,
state.camera,
state.pointer,
floorGroup,
setFloorItems,
itemsGroup,
isTempLoader,
tempLoader,
socket,
selectedItem,
setSelectedItem,
addEvent,
plane
);
}
};
const onDragOver = (event: any) => {
event.preventDefault();
};
if (activeModule === "builder") {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener("dblclick", onDblClick);
canvasElement.addEventListener("drop", onDrop);
canvasElement.addEventListener("dragover", onDragOver);
} else {
if (controls) {
const target = controls.getTarget(new THREE.Vector3());
controls.setTarget(target.x, 0, target.z, true);
setSelectedFloorItem(null);
}
} else {
const target = controls.getTarget(new THREE.Vector3());
await controls.setTarget(target.x, 0, target.z, true);
setSelectedFloorItem(null);
}
}
}
};
const onDrop = (event: any) => {
if (!event.dataTransfer?.files[0]) return;
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener("dblclick", onDblClick);
canvasElement.removeEventListener("drop", onDrop);
canvasElement.removeEventListener("dragover", onDragOver);
};
}, [
deleteTool,
transformMode,
controls,
selectedItem,
state.camera,
state.pointer,
activeTool,
activeModule,
]);
if (selectedItem.id !== "" && event.dataTransfer?.files[0]) {
addAssetModel(
raycaster,
state.camera,
state.pointer,
floorGroup,
setFloorItems,
itemsGroup,
isTempLoader,
tempLoader,
socket,
selectedItem,
setSelectedItem,
addEvent,
plane
);
}
};
useFrame(() => {
if (controls)
if (deleteTool && activeModule === "builder") {
// assetVisibility(itemsGroup, state.camera.position, renderDistance);
DeletableHoveredFloorItems(
state,
itemsGroup,
hoveredDeletableFloorItem,
setDeletableFloorItem
);
} else if (!deleteTool) {
if (hoveredDeletableFloorItem.current) {
hoveredDeletableFloorItem.current = undefined;
setDeletableFloorItem(null);
}
}
});
const onDragOver = (event: any) => {
event.preventDefault();
};
if (activeModule === "builder") {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener("dblclick", onDblClick);
canvasElement.addEventListener("drop", onDrop);
canvasElement.addEventListener("dragover", onDragOver);
} else {
if (controls) {
const target = controls.getTarget(new THREE.Vector3());
controls.setTarget(target.x, 0, target.z, true);
setSelectedFloorItem(null);
}
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener("dblclick", onDblClick);
canvasElement.removeEventListener("drop", onDrop);
canvasElement.removeEventListener("dragover", onDragOver);
};
}, [
deleteTool,
transformMode,
controls,
selectedItem,
state.camera,
state.pointer,
activeTool,
activeModule,
]);
useFrame(() => {
if (controls)
if (deleteTool && activeModule === "builder") {
// assetVisibility(itemsGroup, state.camera.position, renderDistance);
DeletableHoveredFloorItems(
state,
itemsGroup,
hoveredDeletableFloorItem,
setDeletableFloorItem
);
} else if (!deleteTool) {
if (hoveredDeletableFloorItem.current) {
hoveredDeletableFloorItem.current = undefined;
setDeletableFloorItem(null);
}
}
});
return <group ref={itemsGroup} name="itemsGroup"></group>;
return <group ref={itemsGroup} name="itemsGroup"></group>;
};
export default FloorItemsGroup;

View File

@@ -0,0 +1,129 @@
import { useCallback, useEffect, useRef } from "react";
import * as THREE from 'three';
import { useFrame } from "@react-three/fiber";
import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore";
import { useProductStore } from "../../../../../store/simulation/useProductStore";
import { useSelectedProduct } from "../../../../../store/simulation/useSimulationStore";
export function useSpawnHandler() {
const { addMaterial } = useMaterialStore();
const { getModelUuidByActionUuid } = useProductStore();
const { selectedProduct } = useSelectedProduct();
const lastSpawnTime = useRef<number | null>(null);
const startTime = useRef<number | null>(null);
const spawnCountRef = useRef<number>(0);
const spawnParams = useRef<{
material: string;
intervalMs: number;
totalCount: number;
point: ConveyorPointSchema;
} | null>(null);
const clearCurrentSpawn = useCallback(() => {
lastSpawnTime.current = null;
startTime.current = null;
spawnCountRef.current = 0;
spawnParams.current = null;
}, []);
const spawnLogStatus = (materialUuid: string, status: string) => {
// console.log(`${materialUuid}, ${status}`);
}
const createNewMaterial = useCallback((materialType: string, point: ConveyorPointSchema) => {
const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, point.action.actionUuid);
if (!modelUuid || !point.uuid) return;
const newMaterial: MaterialSchema = {
materialId: THREE.MathUtils.generateUUID(),
materialName: `${materialType}-${Date.now()}`,
materialType: materialType,
isActive: false,
isVisible: true,
isRendered: true,
current: {
modelUuid: modelUuid,
pointUuid: point.uuid,
actionUuid: point.action?.actionUuid || ''
},
weight: 1,
cost: 1
};
addMaterial(newMaterial);
return newMaterial;
}, [addMaterial]);
useFrame(() => {
if (!spawnParams.current || !startTime.current) return;
const currentTime = performance.now();
const { material, intervalMs, totalCount, point } = spawnParams.current;
const isFirstSpawn = lastSpawnTime.current === null;
const elapsed = currentTime - startTime.current;
// First spawn
if (isFirstSpawn) {
if (elapsed >= intervalMs) {
const createdMaterial = createNewMaterial(material, point);
if (createdMaterial) {
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
}
lastSpawnTime.current = currentTime;
spawnCountRef.current = 1;
if (totalCount <= 1) {
clearCurrentSpawn();
}
}
return;
}
// Subsequent spawns
if (lastSpawnTime.current !== null) {
const timeSinceLast = currentTime - lastSpawnTime.current;
if (timeSinceLast >= intervalMs) {
const count = spawnCountRef.current + 1;
const createdMaterial = createNewMaterial(material, point);
if (createdMaterial) {
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
}
lastSpawnTime.current = currentTime;
spawnCountRef.current = count;
if (count >= totalCount) {
clearCurrentSpawn();
}
}
}
});
const handleSpawn = useCallback((point: ConveyorPointSchema) => {
if (!point.action || point.action.actionType !== 'spawn') return;
const { material, spawnInterval = 0, spawnCount = 1 } = point.action;
const intervalMs = spawnInterval * 1000;
clearCurrentSpawn();
spawnParams.current = {
material,
intervalMs,
totalCount: spawnCount,
point: point
};
startTime.current = performance.now();
}, [clearCurrentSpawn]);
useEffect(() => {
return () => {
clearCurrentSpawn();
};
}, [clearCurrentSpawn]);
return {
handleSpawn,
clearCurrentSpawn
};
}

View File

@@ -0,0 +1,40 @@
import { useEffect } from "react";
import { useSpawnHandler } from "./actionHandler/spawnActionHandler";
// Conveyor Actions
export function useConveyorActions(point: ConveyorPointSchema) {
const { handleSpawn } = useSpawnHandler();
useEffect(() => {
if (!point.action) return;
const { actionType, material, delay, spawnInterval, spawnCount } = point.action;
const handleAction = () => {
switch (actionType) {
case 'default':
console.log(`Default conveyor action at point ${point.uuid}`);
break;
case 'spawn':
console.log(`Spawning material ${material} at point ${point.uuid}`);
handleSpawn(point);
break;
case 'swap':
console.log(`Swapping to material ${material} at point ${point.uuid}`);
break;
case 'delay':
console.log(`Delaying for ${delay}ms at point ${point.uuid}`);
break;
case 'despawn':
console.log(`Despawning material at point ${point.uuid}`);
break;
}
};
handleAction();
return () => {
// Cleanup if needed
};
}, [point]);
}

View File

@@ -0,0 +1,23 @@
import { useEffect } from 'react';
// Machine Actions
export function useMachineActions(point: MachinePointSchema) {
useEffect(() => {
if (!point.action) return;
const { actionType, processTime, swapMaterial } = point.action;
const handleAction = () => {
if (actionType === 'process') {
console.log(`Machine processing for ${processTime}ms at point ${point.uuid}`);
if (swapMaterial) {
console.log(`Swapping material to ${swapMaterial}`);
}
}
};
return () => {
// Cleanup if needed
};
}, [point]);
}

View File

@@ -0,0 +1,26 @@
import { useEffect } from 'react';
// Robotic Arm Actions
export function useRoboticArmActions(point: RoboticArmPointSchema) {
useEffect(() => {
point.actions.forEach(action => {
const { actionType, process } = action;
const handleAction = () => {
if (actionType === 'pickAndPlace') {
console.log(`Robotic arm pick and place at point ${point.uuid}`);
if (process.startPoint) {
console.log(`Start point: ${process.startPoint}`);
}
if (process.endPoint) {
console.log(`End point: ${process.endPoint}`);
}
}
};
});
return () => {
// Cleanup if needed
};
}, [point]);
}

View File

@@ -0,0 +1,22 @@
import { useEffect } from 'react';
// Storage Actions
export function useStorageActions(point: StoragePointSchema) {
useEffect(() => {
if (!point.action) return;
const { actionType, materials, storageCapacity } = point.action;
const handleAction = () => {
if (actionType === 'store') {
console.log(`Storage action at point ${point.uuid}`);
console.log(`Materials: ${materials.map(m => m.materialName).join(', ')}`);
console.log(`Capacity: ${storageCapacity}`);
}
};
return () => {
// Cleanup if needed
};
}, [point]);
}

View File

@@ -0,0 +1,25 @@
import { useConveyorActions } from "./conveyor/useConveyorActions";
import { useMachineActions } from "./machine/useMachineActions";
import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions";
import { useStorageActions } from "./storageUnit/useStorageUnitActions";
import { useVehicleActions } from "./vehicle/useVehicleActions";
// Master hook that selects the appropriate action handler
export function useActionHandler(point: PointsScheme) {
if ('actions' in point) {
// Robotic Arm
useRoboticArmActions(point);
} else if (point.action.actionType === 'travel') {
// Vehicle
useVehicleActions(point as VehiclePointSchema);
} else if (point.action.actionType === 'process') {
// Machine
useMachineActions(point as MachinePointSchema);
} else if (point.action.actionType === 'store') {
// Storage
useStorageActions(point as StoragePointSchema);
} else {
// Conveyor
useConveyorActions(point as ConveyorPointSchema);
}
}

View File

@@ -0,0 +1,26 @@
import { useEffect } from 'react';
// Vehicle Actions
export function useVehicleActions(point: VehiclePointSchema) {
useEffect(() => {
if (!point.action) return;
const { actionType, unLoadDuration, loadCapacity, steeringAngle } = point.action;
const handleAction = () => {
if (actionType === 'travel') {
console.log(`Vehicle travel action at point ${point.uuid}`);
if (point.action.pickUpPoint) {
console.log(`Pick up at: ${JSON.stringify(point.action.pickUpPoint)}`);
}
if (point.action.unLoadPoint) {
console.log(`Unload at: ${JSON.stringify(point.action.unLoadPoint)}`);
}
}
};
return () => {
// Cleanup if needed
};
}, [point]);
}

View File

@@ -6,8 +6,8 @@ function MachineInstances() {
const { machines } = useMachineStore();
return (
<>
{machines.map((val: MachineStatus) => (
<MachineInstance key={val.modelUuid} machineDetail={val} />
{machines.map((machine: MachineStatus) => (
<MachineInstance key={machine.modelUuid} machineDetail={machine} />
))}
</>

View File

@@ -1,9 +1,22 @@
import React from 'react'
import React, { useEffect, useState } from 'react'
import * as THREE from 'three';
import MaterialAnimator from '../animator/materialAnimator'
function MaterialInstance() {
return (
<></>
)
function MaterialInstance({ material }: { material: MaterialSchema }) {
const [position, setPosition] = useState<THREE.Vector3>();
const [rotation, setRotation] = useState<THREE.Vector3>();
useEffect(() => {
// console.log('material: ', material);
}, [material])
return (
<>
<MaterialAnimator />
</>
)
}
export default MaterialInstance

View File

@@ -1,14 +1,20 @@
import React from 'react'
import React, { useEffect } from 'react'
import MaterialInstance from './instance/materialInstance'
import MaterialAnimator from './animator/materialAnimator'
import { useMaterialStore } from '../../../../store/simulation/useMaterialStore';
function MaterialInstances() {
const { materials } = useMaterialStore();
useEffect(() => {
// console.log('materials: ', materials);
}, [materials])
return (
<>
<MaterialInstance />
<MaterialAnimator />
{materials.map((material: MaterialSchema) =>
<MaterialInstance key={material.materialId} material={material} />
)}
</>
)

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useRef, useState } from "react";
import { useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { Line } from "@react-three/drei";
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
import { Line } from '@react-three/drei';
import {
useAnimationPlaySpeed,
usePauseButtonStore,
usePlayButtonStore,
useResetButtonStore,
} from "../../../../../store/usePlayButtonStore";
useResetButtonStore
} from '../../../../../store/usePlayButtonStore';
function RoboticArmAnimator({
HandleCallback,
@@ -16,24 +16,21 @@ function RoboticArmAnimator({
targetBone,
armBot,
logStatus,
path,
path
}: any) {
const progressRef = useRef(0);
const curveRef = useRef<THREE.Vector3[] | null>(null);
const [currentPath, setCurrentPath] = useState<[number, number, number][]>(
[]
);
const [circlePoints, setCirclePoints] = useState<[number, number, number][]>(
[]
);
const [customCurvePoints, setCustomCurvePoints] = useState<
THREE.Vector3[] | null
>(null);
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]);
const [customCurvePoints, setCustomCurvePoints] = useState<THREE.Vector3[] | null>(null);
let curveHeight = 1.75
const totalDistanceRef = useRef(0);
const startTimeRef = useRef<number | null>(null);
const segmentDistancesRef = useRef<number[]>([]);
// Zustand stores
const { isPlaying } = usePlayButtonStore();
const { isPaused } = usePauseButtonStore();
const { isReset } = useResetButtonStore();
const { isReset, setReset } = useResetButtonStore();
const { speed } = useAnimationPlaySpeed();
// Update path state whenever `path` prop changes
@@ -41,20 +38,26 @@ function RoboticArmAnimator({
setCurrentPath(path);
}, [path]);
// Reset logic when `isPlaying` changes
useEffect(() => {
if (!isPlaying) {
setCurrentPath([]);
curveRef.current = null;
}
}, [isPlaying]);
// Handle circle points based on armBot position
useEffect(() => {
const points = generateRingPoints(1.6, 64);
const points = generateRingPoints(1.6, 64)
setCirclePoints(points);
}, [armBot.position]);
useEffect(() => {
if (isReset || !isPlaying) {
progressRef.current = 0;
curveRef.current = null;
setCurrentPath([]);
setCustomCurvePoints(null);
totalDistanceRef.current = 0;
startTimeRef.current = null;
segmentDistancesRef.current = [];
setReset(false);
}
}, [isReset, isPlaying])
function generateRingPoints(radius: any, segments: any) {
const points: [number, number, number][] = [];
for (let i = 0; i < segments; i++) {
@@ -68,11 +71,7 @@ function RoboticArmAnimator({
return points;
}
const findNearestIndex = (
nearestPoint: [number, number, number],
points: [number, number, number][],
epsilon = 1e-6
) => {
const findNearestIndex = (nearestPoint: [number, number, number], points: [number, number, number][], epsilon = 1e-6) => {
for (let i = 0; i < points.length; i++) {
const [x, y, z] = points[i];
if (
@@ -89,168 +88,150 @@ function RoboticArmAnimator({
// Handle nearest points and final path (including arc points)
useEffect(() => {
if (circlePoints.length > 0 && currentPath.length > 0) {
const start = currentPath[0];
const end = currentPath[currentPath.length - 1];
const raisedStart = [start[0], start[1] + 0.5, start[2]] as [
number,
number,
number
];
const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [
number,
number,
number
];
const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number];
const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number];
const findNearest = (target: [number, number, number]) => {
return circlePoints.reduce((nearest, point) => {
const distance = Math.hypot(
target[0] - point[0],
target[1] - point[1],
target[2] - point[2]
);
const nearestDistance = Math.hypot(
target[0] - nearest[0],
target[1] - nearest[1],
target[2] - nearest[2]
);
const distance = Math.hypot(target[0] - point[0], target[1] - point[1], target[2] - point[2]);
const nearestDistance = Math.hypot(target[0] - nearest[0], target[1] - nearest[1], target[2] - nearest[2]);
return distance < nearestDistance ? point : nearest;
}, circlePoints[0]);
};
const nearestToStart = findNearest(raisedStart);
const nearestToEnd = findNearest(raisedEnd);
const indexOfNearestStart = findNearestIndex(
nearestToStart,
circlePoints
);
const indexOfNearestStart = findNearestIndex(nearestToStart, circlePoints);
const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints);
// Find clockwise and counter-clockwise distances
const clockwiseDistance =
(indexOfNearestEnd - indexOfNearestStart + 64) % 64;
const counterClockwiseDistance =
(indexOfNearestStart - indexOfNearestEnd + 64) % 64;
const clockwiseDistance = (indexOfNearestEnd - indexOfNearestStart + 64) % 64;
const counterClockwiseDistance = (indexOfNearestStart - indexOfNearestEnd + 64) % 64;
const clockwiseIsShorter = clockwiseDistance <= counterClockwiseDistance;
// Collect arc points between start and end
let arcPoints: [number, number, number][] = [];
if (clockwiseIsShorter) {
if (indexOfNearestStart <= indexOfNearestEnd) {
arcPoints = circlePoints.slice(
indexOfNearestStart,
indexOfNearestEnd + 1
);
arcPoints = circlePoints.slice(indexOfNearestStart, indexOfNearestEnd + 1);
} else {
// Wrap around
arcPoints = [
...circlePoints.slice(indexOfNearestStart, 64),
...circlePoints.slice(0, indexOfNearestEnd + 1),
...circlePoints.slice(0, indexOfNearestEnd + 1)
];
}
} else if (indexOfNearestStart >= indexOfNearestEnd) {
for (
let i = indexOfNearestStart;
i !== (indexOfNearestEnd - 1 + 64) % 64;
i = (i - 1 + 64) % 64
) {
arcPoints.push(circlePoints[i]);
} else {
if (indexOfNearestStart >= indexOfNearestEnd) {
for (let i = indexOfNearestStart; i !== (indexOfNearestEnd - 1 + 64) % 64; i = (i - 1 + 64) % 64) {
arcPoints.push(circlePoints[i]);
}
} else {
for (let i = indexOfNearestStart; i !== (indexOfNearestEnd - 1 + 64) % 64; i = (i - 1 + 64) % 64) {
arcPoints.push(circlePoints[i]);
}
}
}
// Continue your custom path logic
const pathVectors = [
new THREE.Vector3(start[0], start[1], start[2]), // start
new THREE.Vector3(raisedStart[0], raisedStart[1], raisedStart[2]), // lift up
new THREE.Vector3(nearestToStart[0], raisedStart[1], nearestToStart[2]), // move to arc start
...arcPoints.map(
(point) => new THREE.Vector3(point[0], raisedStart[1], point[2])
),
new THREE.Vector3(nearestToEnd[0], raisedEnd[1], nearestToEnd[2]), // move from arc end
new THREE.Vector3(raisedEnd[0], raisedEnd[1], raisedEnd[2]), // lowered end
new THREE.Vector3(end[0], end[1], end[2]), // end
new THREE.Vector3(start[0], start[1], start[2]),
new THREE.Vector3(start[0], curveHeight, start[2]),
new THREE.Vector3(nearestToStart[0], curveHeight, nearestToStart[2]),
...arcPoints.map(point => new THREE.Vector3(point[0], curveHeight, point[2])),
new THREE.Vector3(nearestToEnd[0], curveHeight, nearestToEnd[2]),
new THREE.Vector3(end[0], curveHeight, end[2]),
new THREE.Vector3(end[0], end[1], end[2])
];
const customCurve = new THREE.CatmullRomCurve3(
pathVectors,
false,
"centripetal",
1
);
const generatedPoints = customCurve.getPoints(100);
setCustomCurvePoints(generatedPoints);
const pathSegments: [THREE.Vector3, THREE.Vector3][] = [];
for (let i = 0; i < pathVectors.length - 1; i++) {
pathSegments.push([pathVectors[i], pathVectors[i + 1]]);
}
const segmentDistances = pathSegments.map(([p1, p2]) => p1.distanceTo(p2));
segmentDistancesRef.current = segmentDistances;
const totalDistance = segmentDistances.reduce((sum, d) => sum + d, 0);
totalDistanceRef.current = totalDistance;
const movementSpeed = speed * armBot.speed;
const totalMoveTime = totalDistance / movementSpeed;
const segmentTimes = segmentDistances.map(distance => (distance / totalDistance) * totalMoveTime);
setCustomCurvePoints(pathVectors);
}
}, [circlePoints, currentPath]);
// Frame update for animation
useFrame((_, delta) => {
useFrame((state, delta) => {
if (!ikSolver) return;
const bone = ikSolver.mesh.skeleton.bones.find(
(b: any) => b.name === targetBone
);
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
if (!bone) return;
if (isPlaying) {
if (!isPaused && customCurvePoints && currentPath.length > 0) {
const curvePoints = customCurvePoints;
const speedAdjustedProgress =
progressRef.current + speed * armBot.speed;
const index = Math.floor(speedAdjustedProgress);
if (!isPaused && customCurvePoints && customCurvePoints.length > 0) {
const distances = segmentDistancesRef.current; // distances between each pair of points
const totalDistance = totalDistanceRef.current;
if (index >= curvePoints.length) {
// Reached the end of the curve
progressRef.current += delta * (speed * armBot.speed);
const coveredDistance = progressRef.current;
let index = 0;
let accumulatedDistance = 0;
// Find which segment we are currently in
while (index < distances.length && coveredDistance > accumulatedDistance + distances[index]) {
accumulatedDistance += distances[index];
index++;
}
if (index < distances.length) {
const startPoint = customCurvePoints[index];
const endPoint = customCurvePoints[index + 1];
const segmentDistance = distances[index];
const t = (coveredDistance - accumulatedDistance) / segmentDistance;
if (startPoint && endPoint) {
const position = startPoint.clone().lerp(endPoint, t);
bone.position.copy(position);
}
}
if (progressRef.current >= totalDistance) {
HandleCallback();
setCurrentPath([]);
setCustomCurvePoints([]);
curveRef.current = null;
progressRef.current = 0;
} else {
const point = curvePoints[index];
bone.position.copy(point);
progressRef.current = speedAdjustedProgress;
startTimeRef.current = null;
}
} else if (isPaused) {
logStatus(armBot.modelUuid, "Simulation Paused");
}
ikSolver.update();
} else if (!isPlaying && currentPath.length === 0) {
// Not playing anymore, reset to rest
ikSolver.update();
}
} else if ((!isPlaying && currentPath.length === 0) || isReset) {
bone.position.copy(restPosition);
ikSolver.update();
}
});
return (
<>
{customCurvePoints && currentPath && isPlaying && (
{customCurvePoints && customCurvePoints?.length >= 2 && currentPath && isPlaying && (
<mesh rotation={armBot.rotation} position={armBot.position}>
<Line
points={customCurvePoints.map(
(p) => [p.x, p.y, p.z] as [number, number, number]
)}
points={customCurvePoints.map((p) => [p.x, p.y, p.z] as [number, number, number])}
color="green"
lineWidth={5}
dashed={false}
/>
</mesh>
)}
<mesh
position={[
armBot.position[0],
armBot.position[1] + 1.5,
armBot.position[2],
]}
rotation={[-Math.PI / 2, 0, 0]}
visible={false}
>
<mesh position={[armBot.position[0], armBot.position[1] + 1.5, armBot.position[2]]} rotation={[-Math.PI / 2, 0, 0]}>
<ringGeometry args={[1.59, 1.61, 64]} />
<meshBasicMaterial color="green" side={THREE.DoubleSide} />
</mesh>
@@ -258,4 +239,4 @@ function RoboticArmAnimator({
);
}
export default RoboticArmAnimator;
export default RoboticArmAnimator;

View File

@@ -51,13 +51,12 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
pauseTimeRef.current = null;
}
const elapsedTime = performance.now() - startTime;
if (elapsedTime < 1500) {
if (elapsedTime < 1000) {
// Wait until 1500ms has passed
requestAnimationFrame(step);
return;
}
if (currentPhase === "picking") {
setArmBotActive(armBot.modelUuid, true);
setArmBotState(armBot.modelUuid, "running");
setCurrentPhase("start-to-end");
@@ -75,7 +74,6 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
}
logStatus(armBot.modelUuid, "Moving armBot from start point to end position.")
} else if (currentPhase === "dropping") {
setArmBotActive(armBot.modelUuid, true);
setArmBotState(armBot.modelUuid, "running");
setCurrentPhase("end-to-rest");
@@ -91,35 +89,26 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
}
logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
}
}
useEffect(() => {
isPausedRef.current = isPaused;
}, [isPaused]);
useEffect(() => {
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
if (targetMesh) {
targetMesh.visible = activeModule !== "simulation"
}
const targetBones = ikSolver?.mesh.skeleton.bones.find(
(b: any) => b.name === targetBone
);
if (isReset) {
if (isReset || !isPlaying) {
logStatus(armBot.modelUuid, "Simulation Play Reset Successfully")
removeCurrentAction(armBot.modelUuid)
setArmBotActive(armBot.modelUuid, true)
setArmBotState(armBot.modelUuid, "running")
setCurrentPhase("init-to-rest");
setArmBotActive(armBot.modelUuid, false)
setArmBotState(armBot.modelUuid, "idle")
setCurrentPhase("init");
isPausedRef.current = false
pauseTimeRef.current = null
isPausedRef.current = false
startTime = 0
const targetBones = ikSolver?.mesh.skeleton.bones.find(
(b: any) => b.name === targetBone
);
if (targetBones) {
let curve = createCurveBetweenTwoPoints(targetBones.position, targetBones.position)
let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition)
if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z]));
}
@@ -127,6 +116,16 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
setReset(false);
logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.")
}
}, [isReset, isPlaying])
useEffect(() => {
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
if (targetMesh) {
targetMesh.visible = activeModule !== "simulation"
}
const targetBones = ikSolver?.mesh.skeleton.bones.find(
(b: any) => b.name === targetBone
);
if (isPlaying) {
//Moving armBot from initial point to rest position.
@@ -136,7 +135,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
setArmBotState(armBot.modelUuid, "running")
setCurrentPhase("init-to-rest");
if (targetBones) {
let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition)
let curve = createCurveBetweenTwoPoints(targetBones.position, targetBones.position)
if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z]));
}
@@ -157,10 +156,9 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
setArmBotActive(armBot.modelUuid, true);
setArmBotState(armBot.modelUuid, "running");
setCurrentPhase("rest-to-start");
let actiondata = getActionByUuid(selectedProduct.productId, selectedAction.actionId)
const startPoint = armBot.point.actions[0].process.startPoint;
if (startPoint) {
let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]));
let curve = createCurveBetweenTwoPoints(targetBones.position, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]));
if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z]));
}
@@ -206,16 +204,19 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
// logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
}
} else {
logStatus(armBot.modelUuid, "Simulation Play Stopped")
logStatus(armBot.modelUuid, "Simulation Play Exited")
setArmBotActive(armBot.modelUuid, false)
setArmBotState(armBot.modelUuid, "idle")
setCurrentPhase("init");
setPath([])
isPausedRef.current = false
pauseTimeRef.current = null
isPausedRef.current = false
startTime = 0
removeCurrentAction(armBot.modelUuid)
}
}, [currentPhase, armBot, isPlaying, ikSolver, isReset])
}, [currentPhase, armBot, isPlaying, ikSolver])
function createCurveBetweenTwoPoints(p1: any, p2: any) {
@@ -258,6 +259,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
}
}
const logStatus = (id: string, status: string) => {
//
}

View File

@@ -70,7 +70,7 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKIns
setSelectedArm(OOI.Target_Bone);
// scene.add(helper);
scene.add(helper);
}, [cloned, gltf, setIkSolver]);

View File

@@ -23,7 +23,7 @@ function Simulation() {
}, [events])
useEffect(() => {
console.log('products: ', products);
// console.log('products: ', products);
}, [products])
return (

View File

@@ -1,18 +1,238 @@
import { useEffect } from 'react'
import { useProductStore } from '../../../store/simulation/useProductStore'
import { useProductStore } from '../../../store/simulation/useProductStore';
import { useActionHandler } from '../actions/useActionHandler';
function Simulator() {
const { products } = useProductStore();
useEffect(() => {
// console.log('products: ', products);
}, [products])
const executionOrder = determineExecutionOrder(products);
return (
<>
executionOrder.forEach(point => {
useActionHandler(point);
});
</>
)
function determineExecutionSequences(products: productsSchema): PointsScheme[][] {
// Create maps for all points
const pointMap = new Map<string, PointsScheme>();
const allPoints: PointsScheme[] = [];
// First pass: collect all points
products.forEach(product => {
product.eventDatas.forEach(event => {
if (event.type === 'transfer') {
event.points.forEach(point => {
pointMap.set(point.uuid, point);
allPoints.push(point);
});
} else if (event.type === 'vehicle' ||
event.type === 'machine' ||
event.type === 'storageUnit' ||
event.type === 'roboticArm') {
pointMap.set(event.point.uuid, event.point);
allPoints.push(event.point);
}
});
});
// Build complete dependency graph
const dependencyGraph = new Map<string, string[]>();
const reverseDependencyGraph = new Map<string, string[]>();
const triggeredPoints = new Set<string>();
allPoints.forEach(point => {
const triggers = extractTriggersFromPoint(point);
const dependencies: string[] = [];
triggers.forEach(trigger => {
const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid;
if (targetUuid && pointMap.has(targetUuid)) {
dependencies.push(targetUuid);
triggeredPoints.add(targetUuid);
if (!reverseDependencyGraph.has(targetUuid)) {
reverseDependencyGraph.set(targetUuid, []);
}
reverseDependencyGraph.get(targetUuid)!.push(point.uuid);
}
});
dependencyGraph.set(point.uuid, dependencies);
});
// Identify independent root points (points that trigger others but aren't triggered themselves)
const rootPoints = allPoints.filter(point => {
const hasOutgoingTriggers = extractTriggersFromPoint(point).some(
t => t.triggeredAsset?.triggeredPoint?.pointUuid
);
return hasOutgoingTriggers && !triggeredPoints.has(point.uuid);
});
// For each root point, build its complete trigger chain
const executionSequences: PointsScheme[][] = [];
function buildSequence(startUuid: string): PointsScheme[] {
const sequence: PointsScheme[] = [];
const visited = new Set<string>();
function traverse(uuid: string) {
if (visited.has(uuid)) return;
visited.add(uuid);
const point = pointMap.get(uuid);
if (point) {
sequence.push(point);
}
// Follow forward dependencies
const nextPoints = dependencyGraph.get(uuid) || [];
nextPoints.forEach(nextUuid => traverse(nextUuid));
}
traverse(startUuid);
return sequence;
}
// Build sequences for all root points
rootPoints.forEach(root => {
executionSequences.push(buildSequence(root.uuid));
});
// Handle any triggered points not reachable from roots (isolated chains)
const processedPoints = new Set(
executionSequences.flat().map(p => p.uuid)
);
allPoints.forEach(point => {
if (triggeredPoints.has(point.uuid) && !processedPoints.has(point.uuid)) {
executionSequences.push(buildSequence(point.uuid));
}
});
return executionSequences;
}
function determineExecutionOrder(products: productsSchema): PointsScheme[] {
// Create maps for all events and points
const eventMap = new Map<string, EventsSchema>();
const pointMap = new Map<string, PointsScheme>();
const allPoints: PointsScheme[] = [];
// First pass: collect all points
products.forEach(product => {
product.eventDatas.forEach(event => {
eventMap.set(event.modelUuid, event);
if (event.type === 'transfer') {
event.points.forEach(point => {
pointMap.set(point.uuid, point);
allPoints.push(point);
});
} else if (event.type === 'vehicle' ||
event.type === 'machine' ||
event.type === 'storageUnit') {
pointMap.set(event.point.uuid, event.point);
allPoints.push(event.point);
} else if (event.type === 'roboticArm') {
pointMap.set(event.point.uuid, event.point);
allPoints.push(event.point);
}
});
});
// Build dependency graphs
const graph = new Map<string, string[]>();
const reverseGraph = new Map<string, string[]>();
const allTriggeredPoints = new Set<string>();
allPoints.forEach(point => {
const triggers = extractTriggersFromPoint(point);
const dependencies: string[] = [];
triggers.forEach(trigger => {
const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid;
if (targetUuid && pointMap.has(targetUuid)) {
dependencies.push(targetUuid);
allTriggeredPoints.add(targetUuid);
if (!reverseGraph.has(targetUuid)) {
reverseGraph.set(targetUuid, []);
}
reverseGraph.get(targetUuid)!.push(point.uuid);
}
});
graph.set(point.uuid, dependencies);
});
// Identify root points (points that trigger others but aren't triggered themselves)
const rootPoints = allPoints
.filter(point => !allTriggeredPoints.has(point.uuid))
.filter(point => {
// Only include roots that actually have triggers pointing FROM them
const triggers = extractTriggersFromPoint(point);
return triggers.some(t => t.triggeredAsset?.triggeredPoint?.pointUuid);
});
// If no root points found but we have triggered points, find the earliest triggers
if (rootPoints.length === 0 && allTriggeredPoints.size > 0) {
// This handles cases where we have circular dependencies
// but still want to include the triggered points
const minTriggerCount = Math.min(
...Array.from(allTriggeredPoints)
.map(uuid => (graph.get(uuid) || []).length)
);
const potentialRoots = Array.from(allTriggeredPoints)
.filter(uuid => (graph.get(uuid) || []).length === minTriggerCount);
rootPoints.push(...potentialRoots.map(uuid => pointMap.get(uuid)!));
}
// Topological sort only for triggered points
const visited = new Set<string>();
const temp = new Set<string>();
const order: string[] = [];
let hasCycle = false;
function visit(node: string) {
if (temp.has(node)) {
hasCycle = true;
return;
}
if (visited.has(node)) return;
temp.add(node);
const dependencies = reverseGraph.get(node) || [];
for (const dep of dependencies) {
visit(dep);
}
temp.delete(node);
visited.add(node);
order.push(node);
}
// Start processing from root points
rootPoints.forEach(root => visit(root.uuid));
// Convert UUIDs back to points and filter out untriggered points
const triggeredPoints = order
.map(uuid => pointMap.get(uuid)!)
.filter(point => allTriggeredPoints.has(point.uuid) ||
rootPoints.some(root => root.uuid === point.uuid));
return triggeredPoints;
}
function extractTriggersFromPoint(point: PointsScheme): TriggerSchema[] {
if ('actions' in point) {
return point.actions.flatMap(action => action.triggers);
} else if ('action' in point) {
return point.action.triggers;
}
return [];
}
return <></>;
}
export default Simulator
export default Simulator;

View File

@@ -1,10 +1,19 @@
import { useRef, useState } from "react";
import * as THREE from "three";
import { ThreeEvent, useThree } from "@react-three/fiber";
import { useProductStore } from "../../../../store/simulation/useProductStore";
import {
useSelectedEventData,
useSelectedProduct,
} from "../../../../store/simulation/useSimulationStore";
type OnUpdateCallback = (object: THREE.Object3D) => void;
export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
const { getEventByModelUuid, updateAction, getActionByUuid } =
useProductStore();
const { selectedEventData } = useSelectedEventData();
const { selectedProduct } = useSelectedProduct();
const { camera, gl, controls } = useThree();
const activeObjRef = useRef<THREE.Object3D | null>(null);
const planeRef = useRef<THREE.Plane>(
@@ -34,7 +43,6 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
activeObjRef.current = obj;
initialPositionRef.current.copy(obj.position);
// Get world position
setObjectWorldPos(obj.getWorldPosition(objectWorldPos));
@@ -62,57 +70,84 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
const handlePointerMove = (e: PointerEvent) => {
if (!activeObjRef.current) return;
if (selectedEventData?.data.type === "roboticArm") {
const selectedArmBot = getEventByModelUuid(
selectedProduct.productId,
selectedEventData.data.modelUuid
);
if (!selectedArmBot) return;
// Check if Shift key is pressed
const isShiftKeyPressed = e.shiftKey;
// Check if Shift key is pressed
const isShiftKeyPressed = e.shiftKey;
// Get the mouse position relative to the canvas
const rect = gl.domElement.getBoundingClientRect();
pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
// Get the mouse position relative to the canvas
const rect = gl.domElement.getBoundingClientRect();
pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
// Update raycaster to point to the mouse position
raycaster.setFromCamera(pointer, camera);
// Update raycaster to point to the mouse position
raycaster.setFromCamera(pointer, camera);
// Create a vector to store intersection point
const intersection = new THREE.Vector3();
const intersects = raycaster.ray.intersectPlane(
planeRef.current,
intersection
);
if (!intersects) return;
// Create a vector to store intersection point
const intersection = new THREE.Vector3();
const intersects = raycaster.ray.intersectPlane(
planeRef.current,
intersection
);
if (!intersects) return;
// Add offset for dragging
intersection.add(offsetRef.current);
// Add offset for dragging
intersection.add(offsetRef.current);
// Get the parent's world matrix if exists
const parent = activeObjRef.current.parent;
const targetPosition = new THREE.Vector3();
// Get the parent's world matrix if exists
const parent = activeObjRef.current.parent;
const targetPosition = new THREE.Vector3();
// OnPointerDown
initialPositionRef.current.copy(objectWorldPos);
// OnPointerDown
initialPositionRef.current.copy(objectWorldPos);
// OnPointerMove
if (isShiftKeyPressed) {
const { x: initialX, y: initialY } = initialPositionRef.current;
const { x: objectX, z: objectZ } = objectWorldPos;
// OnPointerMove
if (isShiftKeyPressed) {
const { x: initialX, y: initialY } = initialPositionRef.current;
const { x: objectX, z: objectZ } = objectWorldPos;
const deltaX = intersection.x - initialX;
const deltaX = intersection.x - initialX;
targetPosition.set(objectX, initialY + deltaX, objectZ);
} else {
// For free movement
targetPosition.copy(intersection);
}
targetPosition.set(objectX, initialY + deltaX, objectZ);
} else {
// For free movement
targetPosition.copy(intersection);
// CONSTRAIN MOVEMENT HERE:
const centerX = selectedArmBot.position[0];
const centerZ = selectedArmBot.position[2];
const minDistance = 1.2; // 1.4 radius
const maxDistance = 2; // 2 radius
const deltaX = targetPosition.x - centerX;
const deltaZ = targetPosition.z - centerZ;
const distance = Math.sqrt(deltaX * deltaX + deltaZ * deltaZ);
if (distance < minDistance || distance > maxDistance) {
const angle = Math.atan2(deltaZ, deltaX);
const clampedDistance = Math.min(
Math.max(distance, minDistance),
maxDistance
);
targetPosition.x = centerX + Math.cos(angle) * clampedDistance;
targetPosition.z = centerZ + Math.sin(angle) * clampedDistance;
}
targetPosition.y = Math.min(Math.max(targetPosition.y, 0.6), 1.5);
// Convert world position to local if object is nested inside a parent
if (parent) {
parent.worldToLocal(targetPosition);
}
// Update object position
activeObjRef.current.position.copy(targetPosition);
}
// Convert world position to local if object is nested inside a parent
if (parent) {
parent.worldToLocal(targetPosition);
}
// Update object position
activeObjRef.current.position.copy(targetPosition);
};
const handlePointerUp = () => {

View File

@@ -9,8 +9,8 @@ function VehicleInstances() {
return (
<>
{vehicles.map((val: VehicleStatus) =>
<VehicleInstance agvDetail={val} key={val.modelUuid} />
{vehicles.map((vehicle: VehicleStatus) =>
<VehicleInstance agvDetail={vehicle} key={vehicle.modelUuid} />
)}
</>

View File

@@ -4,16 +4,39 @@ import { immer } from 'zustand/middleware/immer';
type MaterialsStore = {
materials: MaterialsSchema;
addMaterial: (material: MaterialSchema) => void;
removeMaterial: (materialId: string) => void;
updateMaterial: (materialId: string, updates: Partial<MaterialSchema>) => void;
addMaterial: (material: MaterialSchema) => MaterialSchema | undefined;
removeMaterial: (materialId: string) => MaterialSchema | undefined;
updateMaterial: (materialId: string, updates: Partial<MaterialSchema>) => MaterialSchema | undefined;
setStartTime: (materialId: string, startTime: string) => void;
setEndTime: (materialId: string, endTime: string) => void;
setCost: (materialId: string, cost: number) => void;
setWeight: (materialId: string, weight: number) => void;
setCurrentLocation: (
materialId: string,
location: {
modelUuid: string;
pointUuid: string;
actionUuid: string;
}
) => MaterialSchema | undefined;
setNextLocation: (
materialId: string,
location?: {
modelUuid: string;
pointUuid: string;
actionUuid: string;
} | null
) => MaterialSchema | undefined;
setStartTime: (materialId: string, startTime: string) => MaterialSchema | undefined;
setEndTime: (materialId: string, endTime: string) => MaterialSchema | undefined;
setCost: (materialId: string, cost: number) => MaterialSchema | undefined;
setWeight: (materialId: string, weight: number) => MaterialSchema | undefined;
setIsActive: (materialId: string, isActive: boolean) => MaterialSchema | undefined;
setIsVisible: (materialId: string, isVisible: boolean) => MaterialSchema | undefined;
setIsRendered: (materialId: string, isRendered: boolean) => MaterialSchema | undefined;
getMaterialById: (materialId: string) => MaterialSchema | undefined;
getMaterialsByPoint: (pointUuid: string) => MaterialSchema[];
getMaterialsByModel: (modelUuid: string) => MaterialSchema[];
};
export const useMaterialStore = create<MaterialsStore>()(
@@ -21,56 +44,161 @@ export const useMaterialStore = create<MaterialsStore>()(
materials: [],
addMaterial: (material) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
state.materials.push(material);
});
return updatedMaterial;
},
removeMaterial: (materialId) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
state.materials = state.materials.filter(m => m.materialId !== materialId);
const material = state.materials.find(m => m.materialId === materialId);
if (material) {
state.materials.filter(m => m.materialId !== material.materialId);
updatedMaterial = JSON.parse(JSON.stringify(material));
}
});
return updatedMaterial;
},
updateMaterial: (materialId, updates) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
if (material) {
Object.assign(material, updates);
updatedMaterial = JSON.parse(JSON.stringify(material));
}
});
return updatedMaterial;
},
setCurrentLocation: (materialId, location) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
if (material) {
material.current = location;
updatedMaterial = JSON.parse(JSON.stringify(material));
}
});
return updatedMaterial;
},
setNextLocation: (materialId, location) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
if (material) {
material.next = location || undefined;
updatedMaterial = JSON.parse(JSON.stringify(material));
}
});
return updatedMaterial;
},
setStartTime: (materialId, startTime) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
if (material) material.startTime = startTime;
if (material) {
material.startTime = startTime
updatedMaterial = JSON.parse(JSON.stringify(material));
};
});
return updatedMaterial;
},
setEndTime: (materialId, endTime) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
if (material) material.endTime = endTime;
if (material) {
material.endTime = endTime;
updatedMaterial = JSON.parse(JSON.stringify(material));
};
});
return updatedMaterial;
},
setCost: (materialId, cost) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
if (material) material.cost = cost;
if (material) {
material.cost = cost;
updatedMaterial = JSON.parse(JSON.stringify(material));
};
});
return updatedMaterial;
},
setWeight: (materialId, weight) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
if (material) material.weight = weight;
if (material) {
material.weight = weight;
updatedMaterial = JSON.parse(JSON.stringify(material));
};
});
return updatedMaterial;
},
setIsActive: (materialId, isActive) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
if (material) {
material.isActive = isActive;
updatedMaterial = JSON.parse(JSON.stringify(material));
};
});
return updatedMaterial;
},
setIsVisible: (materialId, isVisible) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
if (material) {
material.isVisible = isVisible;
updatedMaterial = JSON.parse(JSON.stringify(material));
};
});
return updatedMaterial;
},
setIsRendered: (materialId, isRendered) => {
let updatedMaterial: MaterialSchema | undefined;
set((state) => {
const material = state.materials.find(m => m.materialId === materialId);
if (material) {
material.isRendered = isRendered;
updatedMaterial = JSON.parse(JSON.stringify(material));
};
});
return updatedMaterial;
},
getMaterialById: (materialId) => {
return get().materials.find(m => m.materialId === materialId);
},
getMaterialsByPoint: (pointUuid) => {
return get().materials.filter(m =>
m.current?.pointUuid === pointUuid ||
m.next?.pointUuid === pointUuid
);
},
getMaterialsByModel: (modelUuid) => {
return get().materials.filter(m =>
m.current?.modelUuid === modelUuid ||
m.next?.modelUuid === modelUuid
);
},
}))
);
);

View File

@@ -63,6 +63,7 @@ type ProductsStore = {
getEventByModelUuid: (productId: string, modelUuid: string) => EventsSchema | undefined;
getPointByUuid: (productId: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | undefined;
getActionByUuid: (productId: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined;
getModelUuidByActionUuid: (productId: string, actionUuid: string) => (string) | undefined;
getTriggerByUuid: (productId: string, triggerUuid: string) => TriggerSchema | undefined;
getIsEventInProduct: (productId: string, modelUuid: string) => boolean;
};
@@ -573,6 +574,30 @@ export const useProductStore = create<ProductsStore>()(
return undefined;
},
getModelUuidByActionUuid: (productId, actionUuid) => {
const product = get().products.find(p => p.productId === productId);
if (!product) return undefined;
for (const event of product.eventDatas) {
if ('points' in event) {
for (const point of (event as ConveyorEventSchema).points) {
if (point.action?.actionUuid === actionUuid) {
return event.modelUuid;
}
}
} else if ('point' in event) {
const point = (event as any).point;
if ('action' in point && point.action?.actionUuid === actionUuid) {
return event.modelUuid;
} else if ('actions' in point) {
const action = point.actions.find((a: any) => a.actionUuid === actionUuid);
if (action) return event.modelUuid;
}
}
}
return undefined;
},
getTriggerByUuid: (productId, triggerUuid) => {
const product = get().products.find(p => p.productId === productId);
if (!product) return undefined;

View File

@@ -27,9 +27,9 @@ interface ConveyorPointSchema {
actionName: string;
actionType: "default" | "spawn" | "swap" | "delay" | "despawn";
material: string;
delay: number | "inherit";
spawnInterval: number | "inherit";
spawnCount: number | "inherit";
delay: number;
spawnInterval: number;
spawnCount: number;
triggers: TriggerSchema[];
};
}
@@ -180,13 +180,27 @@ interface StorageUnitStatus extends StorageEventSchema {
interface MaterialSchema {
materialId: string;
materialName: string;
materialName: stri9ng;
materialType: string;
isActive: boolean;
isVisible: boolean;
isRendered: boolean;
startTime?: string;
endTime?: string;
cost?: number;
weight?: number;
current: {
modelUuid: string;
pointUuid: string;
actionUuid: string;
};
next?: {
modelUuid: string;
pointUuid: string;
actionUuid: string;
};
}
type MaterialsSchema = MaterialSchema[];