Merge remote-tracking branch 'origin/v2' into v2-ui

This commit is contained in:
Vishnu 2025-04-23 17:16:14 +05:30
commit f340b052c1
32 changed files with 969 additions and 356 deletions

View File

@ -25,19 +25,8 @@ const avatarColors: string[] = [
export function getAvatarColor(index: number, name?: string): string { export function getAvatarColor(index: number, name?: string): string {
// Check if the color is already stored in localStorage // Check if the color is already stored in localStorage
const localStorageKey = "userAvatarColors"; const localStorageKey = "userAvatarColors";
// Helper function to check if local storage is available
function isLocalStorageAvailable(): boolean {
try {
const testKey = "__test__";
localStorage.setItem(testKey, "test");
localStorage.removeItem(testKey);
return true;
} catch (e) {
return false;
}
}
// Check if local storage is available // Check if local storage is available
if (isLocalStorageAvailable() && name) { if (name) {
let userColors = JSON.parse(localStorage.getItem(localStorageKey) || "{}"); let userColors = JSON.parse(localStorage.getItem(localStorageKey) || "{}");
// Check if the user already has an assigned color // Check if the user already has an assigned color

View File

@ -184,24 +184,9 @@ async function handleModelLoad(
); );
if (!data || !data.points) return; if (!data || !data.points) return;
console.log('data: ', data);
const createMarker = (point: THREE.Vector3) => {
const sphere = new THREE.SphereGeometry(0.1, 15);
const material = new THREE.MeshStandardMaterial();
const mesh = new THREE.Mesh(sphere, material);
mesh.position.copy(point);
return mesh;
};
if (data.points && data.points.length > 0) {
data.points.forEach((Point) => {
model.add(createMarker(Point));
});
}
if (selectedItem.type === "Conveyor") { if (selectedItem.type === "Conveyor") {
const event: ConveyorEventSchema = { const ConveyorEvent: ConveyorEventSchema = {
modelUuid: newFloorItem.modeluuid, modelUuid: newFloorItem.modeluuid,
modelName: newFloorItem.modelname, modelName: newFloorItem.modelname,
position: newFloorItem.position, position: newFloorItem.position,
@ -225,7 +210,85 @@ async function handleModelLoad(
} }
})) }))
} }
addEvent(event); addEvent(ConveyorEvent);
} else if (selectedItem.type === "Vehicle") {
const vehicleEvent: VehicleEventSchema = {
modelUuid: newFloorItem.modeluuid,
modelName: newFloorItem.modelname,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "vehicle",
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: "Vehicle Action",
actionType: "travel",
material: null,
unLoadDuration: 5,
loadCapacity: 10,
pickUpPoint: null,
unLoadPoint: null,
triggers: []
}
}
};
addEvent(vehicleEvent);
} else if (selectedItem.type === "ArmBot") {
const roboticArmEvent: RoboticArmEventSchema = {
modelUuid: newFloorItem.modeluuid,
modelName: newFloorItem.modelname,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "roboticArm",
speed: 1,
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [data.points[0].x, data.points[0].y, data.points[0].z],
rotation: [0, 0, 0],
actions: [
{
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Pick and Place",
actionType: "pickAndPlace",
process: {
startPoint: [0, 0, 0],
endPoint: [0, 0, 0]
},
triggers: []
}
]
}
};
addEvent(roboticArmEvent);
} else if (selectedItem.type === "Machine") {
const machineEvent: MachineEventSchema = {
modelUuid: newFloorItem.modeluuid,
modelName: newFloorItem.modelname,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "machine",
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: "Process Action",
actionType: "process",
processTime: 10,
swapMaterial: "material-id",
triggers: []
}
}
};
addEvent(machineEvent);
} }
} }

View File

@ -18,11 +18,9 @@ import useModuleStore from "../../../store/useModuleStore";
// import { retrieveGLTF } from "../../../utils/indexDB/idbUtils"; // import { retrieveGLTF } from "../../../utils/indexDB/idbUtils";
import { useEventsStore } from "../../../store/simulation/useEventsStore"; import { useEventsStore } from "../../../store/simulation/useEventsStore";
const assetManagerWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/assetManagerWorker.js", import.meta.url)); const assetManagerWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/assetManagerWorker.js", import.meta.url));
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));
const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject, floorGroup, tempLoader, isTempLoader, plane, }: any) => { const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject, floorGroup, tempLoader, isTempLoader, plane, }: any) => {
const state: Types.ThreeState = useThree(); const state: Types.ThreeState = useThree();
const { raycaster, controls }: any = state; const { raycaster, controls }: any = state;

View File

@ -5,240 +5,225 @@ import { useToolMode } from "../../../store/store";
import { Html } from "@react-three/drei"; import { Html } from "@react-three/drei";
const MeasurementTool = () => { const MeasurementTool = () => {
const { gl, raycaster, pointer, camera, scene } = useThree(); const { gl, raycaster, pointer, camera, scene } = useThree();
const { toolMode } = useToolMode(); const { toolMode } = useToolMode();
const [points, setPoints] = useState<THREE.Vector3[]>([]); const [points, setPoints] = useState<THREE.Vector3[]>([]);
const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>( const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>(
null null
); );
const groupRef = useRef<THREE.Group>(null); const groupRef = useRef<THREE.Group>(null);
const [startConePosition, setStartConePosition] = const [startConePosition, setStartConePosition] =
useState<THREE.Vector3 | null>(null); useState<THREE.Vector3 | null>(null);
const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>( const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>(
null null
); );
const [startConeQuaternion, setStartConeQuaternion] = useState( const [startConeQuaternion, setStartConeQuaternion] = useState(
new THREE.Quaternion() new THREE.Quaternion()
); );
const [endConeQuaternion, setEndConeQuaternion] = useState( const [endConeQuaternion, setEndConeQuaternion] = useState(
new THREE.Quaternion() new THREE.Quaternion()
); );
const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 }); const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 });
const MIN_RADIUS = 0.001, const MIN_RADIUS = 0.001, MAX_RADIUS = 0.1;
MAX_RADIUS = 0.1; const MIN_CONE_RADIUS = 0.01, MAX_CONE_RADIUS = 0.4;
const MIN_CONE_RADIUS = 0.01, const MIN_CONE_HEIGHT = 0.035, MAX_CONE_HEIGHT = 2.0;
MAX_CONE_RADIUS = 0.4;
const MIN_CONE_HEIGHT = 0.035,
MAX_CONE_HEIGHT = 2.0;
useEffect(() => { useEffect(() => {
const canvasElement = gl.domElement; const canvasElement = gl.domElement;
let drag = false; let drag = false;
let isLeftMouseDown = false; let isLeftMouseDown = false;
const onMouseDown = () => { const onMouseDown = () => {
isLeftMouseDown = true; isLeftMouseDown = true;
drag = false; drag = false;
}; };
const onMouseUp = (evt: any) => { const onMouseUp = (evt: any) => {
isLeftMouseDown = false; isLeftMouseDown = false;
if (evt.button === 0 && !drag) { if (evt.button === 0 && !drag) {
raycaster.setFromCamera(pointer, camera); raycaster.setFromCamera(pointer, camera);
const intersects = raycaster const intersects = raycaster
.intersectObjects(scene.children, true) .intersectObjects(scene.children, true)
.filter( .filter(
(intersect) => (intersect) =>
!intersect.object.name.includes("Roof") && !intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("MeasurementReference") && !intersect.object.name.includes("MeasurementReference") &&
!intersect.object.name.includes("agv-collider") && !intersect.object.name.includes("agv-collider") &&
!(intersect.object.type === "GridHelper") !(intersect.object.type === "GridHelper")
); );
if (intersects.length > 0) { if (intersects.length > 0) {
const intersectionPoint = intersects[0].point.clone(); const intersectionPoint = intersects[0].point.clone();
if (points.length < 2) { if (points.length < 2) {
setPoints([...points, intersectionPoint]); setPoints([...points, intersectionPoint]);
} else { } else {
setPoints([intersectionPoint]); setPoints([intersectionPoint]);
} }
}
}
};
const onMouseMove = () => {
if (isLeftMouseDown) drag = true;
};
const onContextMenu = (evt: any) => {
evt.preventDefault();
if (!drag) {
evt.preventDefault();
setPoints([]);
setTubeGeometry(null);
}
};
if (toolMode === "MeasurementScale") {
canvasElement.addEventListener("pointerdown", onMouseDown);
canvasElement.addEventListener("pointermove", onMouseMove);
canvasElement.addEventListener("pointerup", onMouseUp);
canvasElement.addEventListener("contextmenu", onContextMenu);
} else {
resetMeasurement();
setPoints([]);
} }
}
return () => {
canvasElement.removeEventListener("pointerdown", onMouseDown);
canvasElement.removeEventListener("pointermove", onMouseMove);
canvasElement.removeEventListener("pointerup", onMouseUp);
canvasElement.removeEventListener("contextmenu", onContextMenu);
};
}, [toolMode, camera, raycaster, pointer, scene, points]);
useFrame(() => {
if (points.length === 1) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster
.intersectObjects(scene.children, true)
.filter(
(intersect) =>
!intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("MeasurementReference") &&
!intersect.object.name.includes("agv-collider") &&
!(intersect.object.type === "GridHelper")
);
if (intersects.length > 0) {
updateMeasurement(points[0], intersects[0].point);
}
} else if (points.length === 2) {
updateMeasurement(points[0], points[1]);
} else {
resetMeasurement();
}
});
const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => {
const distance = start.distanceTo(end);
const radius = THREE.MathUtils.clamp(distance * 0.02, MIN_RADIUS, MAX_RADIUS);
const coneRadius = THREE.MathUtils.clamp(distance * 0.05, MIN_CONE_RADIUS, MAX_CONE_RADIUS);
const coneHeight = THREE.MathUtils.clamp(distance * 0.2, MIN_CONE_HEIGHT, MAX_CONE_HEIGHT);
setConeSize({ radius: coneRadius, height: coneHeight });
const direction = new THREE.Vector3().subVectors(end, start).normalize();
const offset = direction.clone().multiplyScalar(coneHeight * 0.5);
let tubeStart = start.clone().add(offset);
let tubeEnd = end.clone().sub(offset);
tubeStart.y = Math.max(tubeStart.y, 0);
tubeEnd.y = Math.max(tubeEnd.y, 0);
const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]);
setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false));
setStartConePosition(tubeStart);
setEndConePosition(tubeEnd);
setStartConeQuaternion(getArrowOrientation(start, end));
setEndConeQuaternion(getArrowOrientation(end, start));
}; };
const onMouseMove = () => { const resetMeasurement = () => {
if (isLeftMouseDown) drag = true;
};
const onContextMenu = (evt: any) => {
evt.preventDefault();
if (!drag) {
evt.preventDefault();
setPoints([]);
setTubeGeometry(null); setTubeGeometry(null);
} setStartConePosition(null);
setEndConePosition(null);
}; };
if (toolMode === "MeasurementScale") { const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => {
canvasElement.addEventListener("pointerdown", onMouseDown); const direction = new THREE.Vector3()
canvasElement.addEventListener("pointermove", onMouseMove); .subVectors(end, start)
canvasElement.addEventListener("pointerup", onMouseUp); .normalize()
canvasElement.addEventListener("contextmenu", onContextMenu); .negate();
} else { const quaternion = new THREE.Quaternion();
resetMeasurement(); quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
setPoints([]); return quaternion;
}
return () => {
canvasElement.removeEventListener("pointerdown", onMouseDown);
canvasElement.removeEventListener("pointermove", onMouseMove);
canvasElement.removeEventListener("pointerup", onMouseUp);
canvasElement.removeEventListener("contextmenu", onContextMenu);
}; };
}, [toolMode, camera, raycaster, pointer, scene, points]);
useFrame(() => { useEffect(() => {
if (points.length === 1) { if (points.length === 2) {
raycaster.setFromCamera(pointer, camera); // console.log(points[0].distanceTo(points[1]));
const intersects = raycaster }
.intersectObjects(scene.children, true) }, [points]);
.filter(
(intersect) =>
!intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("MeasurementReference") &&
!intersect.object.name.includes("agv-collider") &&
!(intersect.object.type === "GridHelper")
);
if (intersects.length > 0) { return (
updateMeasurement(points[0], intersects[0].point); <group ref={groupRef} name="MeasurementGroup">
} {startConePosition && (
} else if (points.length === 2) { <mesh
updateMeasurement(points[0], points[1]); name="MeasurementReference"
} else { position={startConePosition}
resetMeasurement(); quaternion={startConeQuaternion}
} >
}); <coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
<meshBasicMaterial color="yellow" />
</mesh>
)}
{endConePosition && (
<mesh
name="MeasurementReference"
position={endConePosition}
quaternion={endConeQuaternion}
>
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
<meshBasicMaterial color="yellow" />
</mesh>
)}
{tubeGeometry && (
<mesh name="MeasurementReference" geometry={tubeGeometry}>
<meshBasicMaterial color="yellow" />
</mesh>
)}
const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => { {startConePosition && endConePosition && (
const distance = start.distanceTo(end); <Html
scale={THREE.MathUtils.clamp(
const radius = THREE.MathUtils.clamp( startConePosition.distanceTo(endConePosition) * 0.25,
distance * 0.02, 0,
MIN_RADIUS, 10
MAX_RADIUS )}
position={[
(startConePosition.x + endConePosition.x) / 2,
(startConePosition.y + endConePosition.y) / 2,
(startConePosition.z + endConePosition.z) / 2,
]}
// class
wrapperClass="distance-text-wrapper"
className="distance-text"
// other
zIndexRange={[1, 0]}
prepend
sprite
>
<div>
{startConePosition.distanceTo(endConePosition).toFixed(2)} m
</div>
</Html>
)}
</group>
); );
const coneRadius = THREE.MathUtils.clamp(
distance * 0.05,
MIN_CONE_RADIUS,
MAX_CONE_RADIUS
);
const coneHeight = THREE.MathUtils.clamp(
distance * 0.2,
MIN_CONE_HEIGHT,
MAX_CONE_HEIGHT
);
setConeSize({ radius: coneRadius, height: coneHeight });
const direction = new THREE.Vector3().subVectors(end, start).normalize();
const offset = direction.clone().multiplyScalar(coneHeight * 0.5);
let tubeStart = start.clone().add(offset);
let tubeEnd = end.clone().sub(offset);
tubeStart.y = Math.max(tubeStart.y, 0);
tubeEnd.y = Math.max(tubeEnd.y, 0);
const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]);
setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false));
setStartConePosition(tubeStart);
setEndConePosition(tubeEnd);
setStartConeQuaternion(getArrowOrientation(start, end));
setEndConeQuaternion(getArrowOrientation(end, start));
};
const resetMeasurement = () => {
setTubeGeometry(null);
setStartConePosition(null);
setEndConePosition(null);
};
const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => {
const direction = new THREE.Vector3()
.subVectors(end, start)
.normalize()
.negate();
const quaternion = new THREE.Quaternion();
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
return quaternion;
};
useEffect(() => {
if (points.length === 2) {
console.log(points[0].distanceTo(points[1]));
}
}, [points]);
return (
<group ref={groupRef} name="MeasurementGroup">
{startConePosition && (
<mesh
name="MeasurementReference"
position={startConePosition}
quaternion={startConeQuaternion}
>
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
<meshBasicMaterial color="yellow" />
</mesh>
)}
{endConePosition && (
<mesh
name="MeasurementReference"
position={endConePosition}
quaternion={endConeQuaternion}
>
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
<meshBasicMaterial color="yellow" />
</mesh>
)}
{tubeGeometry && (
<mesh name="MeasurementReference" geometry={tubeGeometry}>
<meshBasicMaterial color="yellow" />
</mesh>
)}
{startConePosition && endConePosition && (
<Html
scale={THREE.MathUtils.clamp(
startConePosition.distanceTo(endConePosition) * 0.25,
0,
10
)}
position={[
(startConePosition.x + endConePosition.x) / 2,
(startConePosition.y + endConePosition.y) / 2,
(startConePosition.z + endConePosition.z) / 2,
]}
// class
wrapperClass="distance-text-wrapper"
className="distance-text"
// other
zIndexRange={[1, 0]}
prepend
sprite
>
<div>
{startConePosition.distanceTo(endConePosition).toFixed(2)} m
</div>
</Html>
)}
</group>
);
}; };
export default MeasurementTool; export default MeasurementTool;

View File

@ -0,0 +1,14 @@
import React from 'react'
import ConveyorInstances from './instances/conveyorInstances'
function Conveyor() {
return (
<>
<ConveyorInstances />
</>
)
}
export default Conveyor

View File

@ -0,0 +1,10 @@
import React from 'react'
function ConveyorInstance() {
return (
<>
</>
)
}
export default ConveyorInstance

View File

@ -0,0 +1,14 @@
import React from 'react'
import ConveyorInstance from './conveyorInstance/conveyorInstance'
function ConveyorInstances() {
return (
<>
<ConveyorInstance />
</>
)
}
export default ConveyorInstances

View File

@ -0,0 +1,148 @@
import React, { useEffect, useRef, useState } from 'react';
import * as THREE from 'three';
import { useEventsStore } from '../../../../../store/simulation/useEventsStore';
import useModuleStore from '../../../../../store/useModuleStore';
import { TransformControls } from '@react-three/drei';
import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
function PointsCreator() {
const { events, updatePoint, getPointByUuid } = useEventsStore();
const { activeModule } = useModuleStore();
const transformRef = useRef<any>(null);
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
const [selectedPoint, setSelectedPoint] = useState<THREE.Mesh | null>(null);
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
useEffect(() => {
setTransformMode(null);
const handleKeyDown = (e: KeyboardEvent) => {
const keyCombination = detectModifierKeys(e);
if (!selectedPoint) return;
if (keyCombination === "G") {
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
}
if (keyCombination === "R") {
setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [selectedPoint]);
const updatePointToState = (selectedPoint: THREE.Mesh) => {
let point = JSON.parse(JSON.stringify(getPointByUuid(selectedPoint.userData.modelUuid, selectedPoint.userData.pointUuid)));
if (point) {
point.position = [selectedPoint.position.x, selectedPoint.position.y, selectedPoint.position.z];
updatePoint(selectedPoint.userData.modelUuid, selectedPoint.userData.pointUuid, point)
}
}
return (
<>
{activeModule === 'simulation' &&
<>
<group name='EventPointsGroup' >
{events.map((event, i) => {
if (event.type === 'transfer') {
return (
<group key={i} position={new THREE.Vector3(...event.position)}>
{event.points.map((point, j) => (
<mesh
uuid={point.uuid}
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedPoint(sphereRefs.current[point.uuid]);
}}
onPointerMissed={() => {
setSelectedPoint(null);
}}
key={`${i}-${j}`}
position={new THREE.Vector3(...point.position)}
userData={{ modelUuid: event.modelUuid, pointUuid: point.uuid }}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="orange" />
</mesh>
))}
</group>
);
} else if (event.type === 'vehicle') {
return (
<group key={i} position={new THREE.Vector3(...event.position)}>
<mesh
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedPoint(sphereRefs.current[event.point.uuid]);
}}
onPointerMissed={() => {
setSelectedPoint(null);
}}
position={new THREE.Vector3(...event.point.position)}
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="blue" />
</mesh>
</group>
);
} else if (event.type === 'roboticArm') {
return (
<group key={i} position={new THREE.Vector3(...event.position)}>
<mesh
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedPoint(sphereRefs.current[event.point.uuid]);
}}
onPointerMissed={() => {
setSelectedPoint(null);
}}
position={new THREE.Vector3(...event.point.position)}
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="green" />
</mesh>
</group>
);
} else if (event.type === 'machine') {
return (
<group key={i} position={new THREE.Vector3(...event.position)}>
<mesh
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedPoint(sphereRefs.current[event.point.uuid]);
}}
onPointerMissed={() => {
setSelectedPoint(null);
}}
position={new THREE.Vector3(...event.point.position)}
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="purple" />
</mesh>
</group>
);
} else {
return null;
}
})}
</group>
{(selectedPoint && transformMode) &&
<TransformControls ref={transformRef} object={selectedPoint} mode={transformMode} onMouseUp={(e) => { updatePointToState(selectedPoint) }} />
}
</>
}
</>
);
}
export default PointsCreator;

View File

@ -0,0 +1,12 @@
import React from 'react'
import PointsCreator from './creator/pointsCreator'
function Points() {
return (
<>
<PointsCreator />
</>
)
}
export default Points

View File

@ -0,0 +1,9 @@
import React from 'react'
function MaterialAnimator() {
return (
<></>
)
}
export default MaterialAnimator

View File

@ -0,0 +1,9 @@
import React from 'react'
function MaterialInstance() {
return (
<></>
)
}
export default MaterialInstance

View File

@ -0,0 +1,17 @@
import React from 'react'
import MaterialInstance from './instance/materialInstance'
import MaterialAnimator from './animator/materialAnimator'
function MaterialInstances() {
return (
<>
<MaterialInstance />
<MaterialAnimator />
</>
)
}
export default MaterialInstances

View File

@ -0,0 +1,14 @@
import React from 'react'
import MaterialInstances from './instances/materialInstances'
function Materials() {
return (
<>
<MaterialInstances />
</>
)
}
export default Materials

View File

@ -0,0 +1,9 @@
import React from 'react'
function RoboticArmAnimator() {
return (
<></>
)
}
export default RoboticArmAnimator;

View File

@ -0,0 +1,17 @@
import React from 'react'
import IKInstance from '../ikInstance/ikInstance';
import RoboticArmAnimator from '../animator/roboticArmAnimator';
function RoboticArmInstance() {
return (
<>
<IKInstance />
<RoboticArmAnimator />
</>
)
}
export default RoboticArmInstance;

View File

@ -0,0 +1,9 @@
import React from 'react'
function IKInstance() {
return (
<></>
)
}
export default IKInstance;

View File

@ -0,0 +1,14 @@
import React from 'react'
import IKInstance from './ikInstance/ikInstance';
function IkInstances() {
return (
<>
<IKInstance />
</>
)
}
export default IkInstances;

View File

@ -0,0 +1,14 @@
import React from 'react'
import RoboticArmInstance from './armInstance/roboticArmInstance';
function RoboticArmInstances() {
return (
<>
<RoboticArmInstance />
</>
)
}
export default RoboticArmInstances;

View File

@ -0,0 +1,15 @@
import React from 'react'
import RoboticArmInstances from './instances/roboticArmInstances';
import IkInstances from './instances/ikInstances';
function RoboticArm() {
return (
<>
<RoboticArmInstances />
</>
)
}
export default RoboticArm;

View File

@ -1,21 +1,34 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useEventsStore } from '../../store/simulation/useEventsStore'; import { useEventsStore } from '../../store/simulation/useEventsStore';
import { useProductStore } from '../../store/simulation/useProductStore'; import { useProductStore } from '../../store/simulation/useProductStore';
import Vehicles from './vehicle/vehicles';
import Points from './events/points/points';
import Conveyor from './conveyor/conveyor';
import RoboticArm from './roboticArm/roboticArm';
function Simulation() { function Simulation() {
const { events } = useEventsStore(); const { events } = useEventsStore();
const { products } = useProductStore(); const { products } = useProductStore();
useEffect(() => { useEffect(() => {
console.log('events: ', events); // console.log('events: ', events);
}, [events]) }, [events])
useEffect(() => { useEffect(() => {
console.log('products: ', products); // console.log('products: ', products);
}, [products]) }, [products])
return ( return (
<> <>
<Points />
<Vehicles />
<RoboticArm />
<Conveyor />
</> </>
) )
} }

View File

@ -1,9 +1,62 @@
import React from 'react' import { useEffect, useState } from 'react'
import { useFrame, useThree } from '@react-three/fiber';
function VehicleAnimator() { interface VehicleAnimatorProps {
return ( path: [number, number, number][];
<div>VehicleAnimator</div> handleCallBack: () => void;
) currentPhase: string;
agvUuid: number
}
function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid }: VehicleAnimatorProps) {
const [progress, setProgress] = useState<number>(0)
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
const { scene } = useThree();
useEffect(() => {
if (currentPhase === 'stationed-pickup' && path.length > 0) {
setCurrentPath(path);
}
}, [currentPhase, path])
useFrame((_, delta) => {
if (!path || path.length < 2) return;
const object = scene.getObjectByProperty("uuid", agvUuid)
if (!object) return;
setProgress(prev => {
const next = prev + delta * 0.1; // speed
return next >= 1 ? 1 : next;
});
const totalSegments = path.length - 1;
const segmentIndex = Math.floor(progress * totalSegments);
const t = progress * totalSegments - segmentIndex;
const start = path[segmentIndex];
const end = path[segmentIndex + 1] || start;
// Directly set position without creating a new Vector3
object.position.x = start[0] + (end[0] - start[0]) * t;
object.position.y = start[1] + (end[1] - start[1]) * t;
object.position.z = start[2] + (end[2] - start[2]) * t;
});
// useFrame(() => {
// if (currentPath.length === 0) return;
// const object = scene.getObjectByProperty("uuid", agvUuid);
// if (!object) return;
// })
return (
<>
</>
)
} }
export default VehicleAnimator export default VehicleAnimator

View File

@ -1,11 +1,63 @@
import React from 'react' import React, { useCallback, useEffect, useState } from 'react'
import VehicleAnimator from '../animator/vehicleAnimator' import VehicleAnimator from '../animator/vehicleAnimator'
import * as THREE from "three";
import { NavMeshQuery } from '@recast-navigation/core';
import { useNavMesh } from '../../../../../store/store';
import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore';
function VehicleInstance({ agvDetails }: any) {
const { navMesh } = useNavMesh();
const { isPlaying } = usePlayButtonStore();
const { setVehicleActive, setVehicleState } = useVehicleStore();
const [currentPhase, setCurrentPhase] = useState<(string)>("stationed");
const [path, setPath] = useState<[number, number, number][]>([]);
const computePath = useCallback((start: any, end: any) => {
try {
const navMeshQuery = new NavMeshQuery(navMesh);
const { path: segmentPath } = navMeshQuery.computePath(start, end);
return (
segmentPath?.map(
({ x, y, z }) => [x, y + 0.1, z] as [number, number, number]
) || []
);
} catch {
return [];
}
}, [navMesh]);
useEffect(() => {
if (isPlaying) {
if (!agvDetails.isActive && agvDetails.state == "idle" && currentPhase == "stationed") {
const toPickupPath = computePath(new THREE.Vector3(agvDetails.position[0], agvDetails.position[1], agvDetails.position[2]), agvDetails.point.action.pickUpPoint);
setPath(toPickupPath)
setVehicleActive(agvDetails.modelUuid, true)
setVehicleState(agvDetails.modelUuid, "running")
setCurrentPhase("stationed-pickup")
//
}
}
}, [agvDetails, currentPhase, path, isPlaying])
function handleCallBack() {
if (currentPhase === "stationed-pickup") {
setVehicleActive(agvDetails.modelUuid, false)
setVehicleState(agvDetails.modelUuid, "idle")
setCurrentPhase("picking")
setPath([])
}
}
function VehicleInstance() {
return ( return (
<> <>
<VehicleAnimator /> <VehicleAnimator path={path} handleCallBack={handleCallBack} currentPhase={currentPhase} agvUuid={agvDetails?.modelUuid} />
</> </>
) )

View File

@ -1,11 +1,16 @@
import React from 'react' import React from 'react'
import VehicleInstance from './instance/vehicleInstance' import VehicleInstance from './instance/vehicleInstance'
import { useVehicleStore } from '../../../../store/simulation/useVehicleStore';
function VehicleInstances() { function VehicleInstances() {
const { vehicles } = useVehicleStore();
return ( return (
<> <>
<VehicleInstance /> {vehicles.map((val: any, i: any) =>
<VehicleInstance agvDetails={val} key={i} />
)}
</> </>
) )

View File

@ -1,14 +0,0 @@
import React from 'react'
import VehicleInstances from './instances/vehicleInstances';
function Vehicle() {
return (
<>
<VehicleInstances />
</>
)
}
export default Vehicle

View File

@ -0,0 +1,121 @@
import React, { useEffect } from 'react'
import VehicleInstances from './instances/vehicleInstances';
import { useVehicleStore } from '../../../store/simulation/useVehicleStore';
function Vehicles() {
const { vehicles, addVehicle } = useVehicleStore();
const vehicleStatusSample: VehicleEventSchema[] = [
{
modelUuid: "veh-123",
modelName: "Autonomous Truck A1",
position: [10, 0, 5],
rotation: [0, 0, 0],
state: "idle",
type: "vehicle",
speed: 2.5,
point: {
uuid: "point-789",
position: [0, 1, 0],
rotation: [0, 0, 0],
action: {
actionUuid: "action-456",
actionName: "Deliver to Zone A",
actionType: "travel",
material: "crate",
unLoadDuration: 15,
loadCapacity: 5,
pickUpPoint: { x: 5, y: 0, z: 3 },
unLoadPoint: { x: 20, y: 0, z: 10 },
triggers: [
{
triggerUuid: "trig-001",
triggerName: "Start Travel",
triggerType: "onComplete",
delay: 0,
triggeredAsset: {
triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
}
},
{
triggerUuid: "trig-002",
triggerName: "Complete Travel",
triggerType: "onComplete",
delay: 2,
triggeredAsset: null
}
]
}
}
},
{
modelUuid: "veh-123",
modelName: "Autonomous Truck A1",
position: [10, 0, 5],
rotation: [0, 0, 0],
state: "idle",
type: "vehicle",
speed: 2.5,
point: {
uuid: "point-789",
position: [0, 1, 0],
rotation: [0, 0, 0],
action: {
actionUuid: "action-456",
actionName: "Deliver to Zone A",
actionType: "travel",
material: "crate",
unLoadDuration: 15,
loadCapacity: 5,
pickUpPoint: { x: 5, y: 0, z: 3 },
unLoadPoint: { x: 20, y: 0, z: 10 },
triggers: [
{
triggerUuid: "trig-001",
triggerName: "Start Travel",
triggerType: "onStart",
delay: 0,
triggeredAsset: {
triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
}
},
{
triggerUuid: "trig-002",
triggerName: "Complete Travel",
triggerType: "onComplete",
delay: 2,
triggeredAsset: null
}
]
}
}
}
];
useEffect(() => {
addVehicle('123', vehicleStatusSample[0]);
addVehicle('123', vehicleStatusSample[1]);
}, [])
useEffect(() => {
// console.log('vehicles: ', vehicles);
}, [vehicles])
return (
<>
<VehicleInstances />
</>
)
}
export default Vehicles;

View File

@ -44,7 +44,7 @@ export default function Dropped3dWidgets() {
const rotationStartRef = useRef<[number, number, number]>([0, 0, 0]); const rotationStartRef = useRef<[number, number, number]>([0, 0, 0]);
const mouseStartRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 }); const mouseStartRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
const { setSelectedChartId } = useWidgetStore(); const { setSelectedChartId } = useWidgetStore();
const { measurements, duration} = useChartStore(); const { measurements, duration } = useChartStore();
let [floorPlanesVertical, setFloorPlanesVertical] = useState( let [floorPlanesVertical, setFloorPlanesVertical] = useState(
new THREE.Plane(new THREE.Vector3(0, 1, 0)) new THREE.Plane(new THREE.Vector3(0, 1, 0))
); );
@ -117,7 +117,6 @@ export default function Dropped3dWidgets() {
!intersect.object.name.includes("Roof") && !intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("agv-collider") && !intersect.object.name.includes("agv-collider") &&
!intersect.object.name.includes("MeasurementReference") && !intersect.object.name.includes("MeasurementReference") &&
!intersect.object.userData.isPathObject &&
!(intersect.object.type === "GridHelper") !(intersect.object.type === "GridHelper")
); );
@ -154,7 +153,6 @@ export default function Dropped3dWidgets() {
!intersect.object.name.includes("Roof") && !intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("agv-collider") && !intersect.object.name.includes("agv-collider") &&
!intersect.object.name.includes("MeasurementReference") && !intersect.object.name.includes("MeasurementReference") &&
!intersect.object.userData.isPathObject &&
!(intersect.object.type === "GridHelper") !(intersect.object.type === "GridHelper")
); );
// Update widget's position in memory // Update widget's position in memory
@ -258,7 +256,7 @@ export default function Dropped3dWidgets() {
widgetToDuplicate.position[2] + 0.5, widgetToDuplicate.position[2] + 0.5,
], ],
rotation: widgetToDuplicate.rotation || [0, 0, 0], rotation: widgetToDuplicate.rotation || [0, 0, 0],
Data:{ Data: {
measurements: measurements, measurements: measurements,
duration: duration duration: duration
}, },

View File

@ -1,17 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
interface ArmBotStatus extends RoboticArmEventSchema {
productId: string;
isActive: boolean;
idleTime: number;
activeTime: number;
currentAction?: {
actionUuid: string;
actionName: string;
};
}
interface ArmBotStore { interface ArmBotStore {
armBots: ArmBotStatus[]; armBots: ArmBotStatus[];
@ -22,9 +11,11 @@ interface ArmBotStore {
updates: Partial<Omit<ArmBotStatus, 'modelUuid' | 'productId'>> updates: Partial<Omit<ArmBotStatus, 'modelUuid' | 'productId'>>
) => void; ) => void;
startAction: (modelUuid: string, actionUuid: string) => void; addCurrentAction: (modelUuid: string, actionUuid: string) => void;
completeAction: (modelUuid: string) => void; removeCurrentAction: (modelUuid: string) => void;
cancelAction: (modelUuid: string) => void;
addAction: (modelUuid: string, action: RoboticArmPointSchema['actions'][number]) => void;
removeAction: (modelUuid: string, actionUuid: string) => void;
setArmBotActive: (modelUuid: string, isActive: boolean) => void; setArmBotActive: (modelUuid: string, isActive: boolean) => void;
@ -71,7 +62,7 @@ export const useArmBotStore = create<ArmBotStore>()(
}); });
}, },
startAction: (modelUuid, actionUuid) => { addCurrentAction: (modelUuid, actionUuid) => {
set((state) => { set((state) => {
const armBot = state.armBots.find(a => a.modelUuid === modelUuid); const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
if (armBot) { if (armBot) {
@ -87,22 +78,30 @@ export const useArmBotStore = create<ArmBotStore>()(
}); });
}, },
completeAction: (modelUuid) => { removeCurrentAction: (modelUuid) => {
set((state) => { set((state) => {
const armBot = state.armBots.find(a => a.modelUuid === modelUuid); const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
if (armBot && armBot.currentAction) { if (armBot) {
armBot.currentAction = undefined; armBot.currentAction = undefined;
armBot.isActive = false; armBot.isActive = false;
} }
}); });
}, },
cancelAction: (modelUuid) => { addAction: (modelUuid, action) => {
set((state) => { set((state) => {
const armBot = state.armBots.find(a => a.modelUuid === modelUuid); const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
if (armBot) { if (armBot) {
armBot.currentAction = undefined; armBot.point.actions.push(action);
armBot.isActive = false; }
});
},
removeAction: (modelUuid, actionUuid) => {
set((state) => {
const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
if (armBot) {
armBot.point.actions = armBot.point.actions.filter(a => a.actionUuid !== actionUuid);
} }
}); });
}, },

View File

@ -1,13 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
interface ConveyorStatus extends ConveyorEventSchema {
productId: string;
isActive: boolean;
idleTime: number;
activeTime: number;
}
interface ConveyorStore { interface ConveyorStore {
conveyors: ConveyorStatus[]; conveyors: ConveyorStatus[];

View File

@ -1,13 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
interface MachineStatus extends MachineEventSchema {
productId: string;
isActive: boolean;
idleTime: number;
activeTime: number;
}
interface MachineStore { interface MachineStore {
machines: MachineStatus[]; machines: MachineStatus[];

View File

@ -1,14 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
interface StorageUnitStatus extends StorageEventSchema {
productId: string;
isActive: boolean;
idleTime: number;
activeTime: number;
currentLoad: number;
}
interface StorageUnitStore { interface StorageUnitStore {
storageUnits: StorageUnitStatus[]; storageUnits: StorageUnitStatus[];
@ -25,7 +17,7 @@ interface StorageUnitStore {
setStorageUnitState: (modelUuid: string, newState: StorageUnitStatus['state']) => void; setStorageUnitState: (modelUuid: string, newState: StorageUnitStatus['state']) => void;
// Load updates // Load updates
updateStorageUnitLoad: (modelUuid: string, load: number) => void; updateStorageUnitLoad: (modelUuid: string, incrementBy: number) => void;
// Time tracking // Time tracking
incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
@ -95,11 +87,11 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
}, },
// Load updates // Load updates
updateStorageUnitLoad: (modelUuid, load) => { updateStorageUnitLoad: (modelUuid, incrementBy) => {
set((state) => { set((state) => {
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid); const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
if (unit) { if (unit) {
unit.currentLoad = load; unit.currentLoad += incrementBy;
} }
}); });
}, },

View File

@ -21,7 +21,8 @@ interface VehiclesStore {
) => void; ) => void;
setVehicleActive: (modelUuid: string, isActive: boolean) => void; setVehicleActive: (modelUuid: string, isActive: boolean) => void;
updateVehicleLoad: (modelUuid: string, load: number) => void; incrementVehicleLoad: (modelUuid: string, incrementBy: number) => void;
decrementVehicleLoad: (modelUuid: string, decrementBy: number) => void;
setVehicleState: (modelUuid: string, newState: VehicleStatus['state']) => void; setVehicleState: (modelUuid: string, newState: VehicleStatus['state']) => void;
incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
incrementIdleTime: (modelUuid: string, incrementBy: number) => void; incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
@ -30,7 +31,6 @@ interface VehiclesStore {
getVehiclesByProduct: (productId: string) => VehicleStatus[]; getVehiclesByProduct: (productId: string) => VehicleStatus[];
getVehiclesByState: (state: string) => VehicleStatus[]; getVehiclesByState: (state: string) => VehicleStatus[];
getActiveVehicles: () => VehicleStatus[]; getActiveVehicles: () => VehicleStatus[];
getIdleVehicles: () => VehicleStatus[];
} }
export const useVehicleStore = create<VehiclesStore>()( export const useVehicleStore = create<VehiclesStore>()(
@ -75,11 +75,20 @@ export const useVehicleStore = create<VehiclesStore>()(
}); });
}, },
updateVehicleLoad: (modelUuid, load) => { incrementVehicleLoad: (modelUuid, incrementBy) => {
set((state) => { set((state) => {
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
if (vehicle) { if (vehicle) {
vehicle.currentLoad = load; vehicle.currentLoad += incrementBy;
}
});
},
decrementVehicleLoad: (modelUuid, decrementBy) => {
set((state) => {
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
if (vehicle) {
vehicle.currentLoad = decrementBy;
} }
}); });
}, },
@ -125,10 +134,6 @@ export const useVehicleStore = create<VehiclesStore>()(
getActiveVehicles: () => { getActiveVehicles: () => {
return get().vehicles.filter(v => v.isActive); return get().vehicles.filter(v => v.isActive);
},
getIdleVehicles: () => {
return get().vehicles.filter(v => !v.isActive && v.currentLoad > 0);
} }
})) }))
); );

View File

@ -42,7 +42,7 @@ interface VehiclePointSchema {
actionUuid: string; actionUuid: string;
actionName: string; actionName: string;
actionType: "travel"; actionType: "travel";
material: string; material: string | null;
unLoadDuration: number; unLoadDuration: number;
loadCapacity: number; loadCapacity: number;
pickUpPoint: { x: number; y: number, z: number } | null; pickUpPoint: { x: number; y: number, z: number } | null;
@ -59,7 +59,7 @@ interface RoboticArmPointSchema {
actionUuid: string; actionUuid: string;
actionName: string; actionName: string;
actionType: "pickAndPlace"; actionType: "pickAndPlace";
process: { startPoint: string; endPoint: string }; process: { startPoint: [number, number, number]; endPoint: [number, number, number] };
triggers: TriggerSchema[]; triggers: TriggerSchema[];
}[]; }[];
} }
@ -119,10 +119,53 @@ interface StorageEventSchema extends AssetEventSchema {
point: StoragePointSchema; point: StoragePointSchema;
} }
type EventsSchema = ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema | []; type EventsSchema = ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema;
type productsSchema = { type productsSchema = {
productName: string; productName: string;
productId: string; productId: string;
eventsData: EventsSchema[]; eventsData: EventsSchema[];
}[] }[]
interface ConveyorStatus extends ConveyorEventSchema {
productId: string;
isActive: boolean;
idleTime: number;
activeTime: number;
}
interface MachineStatus extends MachineEventSchema {
productId: string;
isActive: boolean;
idleTime: number;
activeTime: number;
}
interface ArmBotStatus extends RoboticArmEventSchema {
productId: string;
isActive: boolean;
idleTime: number;
activeTime: number;
currentAction?: {
actionUuid: string;
actionName: string;
};
}
interface VehicleStatus extends VehicleEventSchema {
productId: string;
isActive: boolean;
idleTime: number;
activeTime: number;
currentLoad: number;
distanceTraveled: number;
}
interface StorageUnitStatus extends StorageEventSchema {
productId: string;
isActive: boolean;
idleTime: number;
activeTime: number;
currentLoad: number;
}