Merge branch 'v2' into simulation-armbot-v2

This commit is contained in:
2025-05-06 11:31:35 +05:30
20 changed files with 763 additions and 412 deletions

View File

@@ -38,18 +38,12 @@ const SimulationPlayer: React.FC = () => {
const { isReset, setReset } = useResetButtonStore(); const { isReset, setReset } = useResetButtonStore();
const { subModule } = useSubModuleStore(); const { subModule } = useSubModuleStore();
useEffect(() => {
if (isReset) {
setTimeout(()=>{
setReset(false);
},0)
}
}, [isReset])
// Button functions // Button functions
const handleReset = () => { const handleReset = () => {
setReset(true); setReset(true);
setIsPaused(false);
setSpeed(1); setSpeed(1);
setPlaySimulation(false); // local state reset
}; };
const handlePlayStop = () => { const handlePlayStop = () => {
setIsPaused(!isPaused); setIsPaused(!isPaused);
@@ -58,6 +52,7 @@ const SimulationPlayer: React.FC = () => {
const handleExit = () => { const handleExit = () => {
setPlaySimulation(false); setPlaySimulation(false);
setIsPlaying(false); setIsPlaying(false);
setIsPaused(false);
setActiveTool("cursor"); setActiveTool("cursor");
}; };
@@ -279,10 +274,11 @@ const SimulationPlayer: React.FC = () => {
</div> </div>
{index < intervals.length - 1 && ( {index < intervals.length - 1 && (
<div <div
className={`line ${progress >= ((index + 1) / totalSegments) * 100 className={`line ${
progress >= ((index + 1) / totalSegments) * 100
? "filled" ? "filled"
: "" : ""
}`} }`}
></div> ></div>
)} )}
</React.Fragment> </React.Fragment>

View File

@@ -4,28 +4,44 @@ import { useFrame } from "@react-three/fiber";
import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore"; import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore";
import { useProductStore } from "../../../../../store/simulation/useProductStore"; import { useProductStore } from "../../../../../store/simulation/useProductStore";
import { useSelectedProduct } from "../../../../../store/simulation/useSimulationStore"; import { useSelectedProduct } from "../../../../../store/simulation/useSimulationStore";
import { usePlayButtonStore, useAnimationPlaySpeed, usePauseButtonStore, useResetButtonStore } from "../../../../../store/usePlayButtonStore";
export function useSpawnHandler() { interface SpawnInstance {
const { addMaterial } = useMaterialStore(); lastSpawnTime: number | null;
const { getModelUuidByActionUuid, getPointUuidByActionUuid } = useProductStore(); startTime: number;
const { selectedProduct } = useSelectedProduct(); spawnCount: number;
const lastSpawnTime = useRef<number | null>(null); params: {
const startTime = useRef<number | null>(null);
const spawnCountRef = useRef<number>(0);
const spawnParams = useRef<{
material: string; material: string;
intervalMs: number; intervalMs: number;
totalCount: number; totalCount: number;
action: ConveyorAction; action: ConveyorAction;
} | null>(null); };
pauseStartTime: number;
remainingTime: number;
isPaused: boolean;
}
const clearCurrentSpawn = useCallback(() => { export function useSpawnHandler() {
lastSpawnTime.current = null; const { addMaterial } = useMaterialStore();
startTime.current = null; const { getModelUuidByActionUuid, getPointUuidByActionUuid } = useProductStore();
spawnCountRef.current = 0; const { isPlaying } = usePlayButtonStore();
spawnParams.current = null; const { isPaused } = usePauseButtonStore();
const { speed } = useAnimationPlaySpeed();
const { isReset } = useResetButtonStore();
const { selectedProduct } = useSelectedProduct();
const activeSpawns = useRef<Map<string, SpawnInstance>>(new Map());
const clearAllSpawns = useCallback(() => {
activeSpawns.current.clear();
}, []); }, []);
useEffect(() => {
if (isReset) {
clearAllSpawns();
}
}, [isReset, clearAllSpawns]);
const spawnLogStatus = (materialUuid: string, status: string) => { const spawnLogStatus = (materialUuid: string, status: string) => {
// console.log(`${materialUuid}, ${status}`); // console.log(`${materialUuid}, ${status}`);
} }
@@ -64,78 +80,121 @@ export function useSpawnHandler() {
addMaterial(newMaterial); addMaterial(newMaterial);
return newMaterial; return newMaterial;
}, [addMaterial]); }, [addMaterial, getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct.productId]);
useEffect(() => {
const currentTime = performance.now();
activeSpawns.current.forEach((spawn) => {
if (isPaused && !spawn.isPaused) {
if (spawn.lastSpawnTime === null) {
spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.startTime));
} else {
spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.lastSpawnTime));
}
spawn.pauseStartTime = currentTime;
spawn.isPaused = true;
} else if (!isPaused && spawn.isPaused) {
if (spawn.remainingTime > 0) {
if (spawn.lastSpawnTime === null) {
spawn.startTime = currentTime - (spawn.params.intervalMs - spawn.remainingTime);
} else {
spawn.lastSpawnTime = currentTime - (spawn.params.intervalMs - spawn.remainingTime);
}
}
spawn.isPaused = false;
spawn.pauseStartTime = 0;
spawn.remainingTime = 0;
}
});
}, [isPaused]);
useFrame(() => { useFrame(() => {
if (!spawnParams.current || !startTime.current) return; if (!isPlaying || isPaused || isReset) return;
const currentTime = performance.now(); const currentTime = performance.now();
const { material, intervalMs, totalCount, action } = spawnParams.current; const completedActions: string[] = [];
const isFirstSpawn = lastSpawnTime.current === null;
const elapsed = currentTime - startTime.current;
// First spawn activeSpawns.current.forEach((spawn, actionUuid) => {
if (isFirstSpawn) { const { material, intervalMs, totalCount, action } = spawn.params;
if (elapsed >= intervalMs) { const isFirstSpawn = spawn.lastSpawnTime === null;
const createdMaterial = createNewMaterial(material, action);
if (createdMaterial) { // First spawn
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`); if (isFirstSpawn) {
const elapsed = currentTime - spawn.startTime;
if (elapsed >= intervalMs) {
const createdMaterial = createNewMaterial(material, action);
if (createdMaterial) {
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
}
spawn.lastSpawnTime = currentTime;
spawn.spawnCount = 1;
if (totalCount <= 1) {
completedActions.push(actionUuid);
}
} }
lastSpawnTime.current = currentTime; return;
spawnCountRef.current = 1; }
if (totalCount <= 1) { // Subsequent spawns
clearCurrentSpawn(); if (spawn.lastSpawnTime !== null) {
const timeSinceLast = currentTime - spawn.lastSpawnTime;
if (timeSinceLast >= intervalMs) {
const count = spawn.spawnCount + 1;
const createdMaterial = createNewMaterial(material, action);
if (createdMaterial) {
spawnLogStatus(createdMaterial.materialId, `[${timeSinceLast.toFixed(2)}ms] Spawned ${material} (${count}/${totalCount})`);
}
spawn.lastSpawnTime = currentTime;
spawn.spawnCount = count;
if (count >= totalCount) {
completedActions.push(actionUuid);
}
} }
} }
return; });
}
// Subsequent spawns completedActions.forEach(actionUuid => {
if (lastSpawnTime.current !== null) { activeSpawns.current.delete(actionUuid);
const timeSinceLast = currentTime - lastSpawnTime.current; });
if (timeSinceLast >= intervalMs) {
const count = spawnCountRef.current + 1;
const createdMaterial = createNewMaterial(material, action);
if (createdMaterial) {
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (${count}/${totalCount})`);
}
lastSpawnTime.current = currentTime;
spawnCountRef.current = count;
if (count >= totalCount) {
clearCurrentSpawn();
}
}
}
}); });
const handleSpawn = useCallback((action: ConveyorAction) => { const handleSpawn = useCallback((action: ConveyorAction) => {
if (!action || action.actionType !== 'spawn') return; if (!action || action.actionType !== 'spawn') return;
const { material, spawnInterval = 0, spawnCount = 1 } = action; const { material, spawnInterval = 0, spawnCount = 1, actionUuid } = action;
const intervalMs = spawnInterval * 1000; const intervalMs = spawnInterval * 1000;
clearCurrentSpawn(); if (activeSpawns.current.has(actionUuid)) {
activeSpawns.current.delete(actionUuid);
}
spawnParams.current = { activeSpawns.current.set(actionUuid, {
material, lastSpawnTime: null,
intervalMs, startTime: performance.now(),
totalCount: spawnCount, spawnCount: 0,
action: action params: {
}; material,
intervalMs,
startTime.current = performance.now(); totalCount: spawnCount,
}, [clearCurrentSpawn]); action: action
},
pauseStartTime: 0,
remainingTime: 0,
isPaused: false
});
}, []);
useEffect(() => { useEffect(() => {
return () => { return () => {
clearCurrentSpawn(); clearAllSpawns();
}; };
}, [clearCurrentSpawn]); }, [clearAllSpawns]);
return { return {
handleSpawn, handleSpawn,
clearCurrentSpawn clearCurrentSpawn: clearAllSpawns
}; };
} }

View File

@@ -5,7 +5,8 @@ export function useConveyorActions() {
const { handleSpawn, clearCurrentSpawn } = useSpawnHandler(); const { handleSpawn, clearCurrentSpawn } = useSpawnHandler();
const handleDefaultAction = useCallback((action: ConveyorAction) => { const handleDefaultAction = useCallback((action: ConveyorAction) => {
// console.log(`Default conveyor action ${action.actionUuid}`); console.log('action: ', action);
console.log(`Default conveyor action ${action.actionUuid}`);
}, []); }, []);
const handleSpawnAction = useCallback((action: ConveyorAction) => { const handleSpawnAction = useCallback((action: ConveyorAction) => {

View File

@@ -1,3 +1,4 @@
import { usePlayButtonStore, useResetButtonStore } from "../../../store/usePlayButtonStore";
import { useConveyorActions } from "./conveyor/useConveyorActions"; import { useConveyorActions } from "./conveyor/useConveyorActions";
import { useMachineActions } from "./machine/useMachineActions"; import { useMachineActions } from "./machine/useMachineActions";
import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions"; import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions";
@@ -6,6 +7,8 @@ import { useVehicleActions } from "./vehicle/useVehicleActions";
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
export function useActionHandler() { export function useActionHandler() {
const { isReset } = useResetButtonStore();
const { isPlaying } = usePlayButtonStore();
const { handleConveyorAction, cleanup: cleanupConveyor } = useConveyorActions(); const { handleConveyorAction, cleanup: cleanupConveyor } = useConveyorActions();
const { handleVehicleAction, cleanup: cleanupVehicle } = useVehicleActions(); const { handleVehicleAction, cleanup: cleanupVehicle } = useVehicleActions();
const { handleRoboticArmAction, cleanup: cleanupRoboticArm } = useRoboticArmActions(); const { handleRoboticArmAction, cleanup: cleanupRoboticArm } = useRoboticArmActions();
@@ -52,7 +55,7 @@ export function useActionHandler() {
return () => { return () => {
cleanup(); cleanup();
}; };
}, [cleanup]); }, [cleanup, isReset, isPlaying]);
return { return {
handleAction, handleAction,

View File

@@ -1,276 +1,265 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import * as THREE from "three"; import * as THREE from "three";
import { useEventsStore } from "../../../../../store/simulation/useEventsStore"; import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
import useModuleStore, { import useModuleStore, { useSubModuleStore } from "../../../../../store/useModuleStore";
useSubModuleStore,
} from "../../../../../store/useModuleStore";
import { TransformControls } from "@react-three/drei"; import { TransformControls } from "@react-three/drei";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { import { useSelectedEventSphere, useSelectedEventData, } from "../../../../../store/simulation/useSimulationStore";
useSelectedEventSphere,
useSelectedEventData,
} from "../../../../../store/simulation/useSimulationStore";
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import { usePlayButtonStore } from "../../../../../store/usePlayButtonStore";
function PointsCreator() { function PointsCreator() {
const { gl, raycaster, scene, pointer, camera } = useThree(); const { gl, raycaster, scene, pointer, camera } = useThree();
const { subModule } = useSubModuleStore(); const { subModule } = useSubModuleStore();
const { events, updatePoint, getPointByUuid, getEventByModelUuid } = const { events, updatePoint, getPointByUuid, getEventByModelUuid } = useEventsStore();
useEventsStore(); const { activeModule } = useModuleStore();
const { activeModule } = useModuleStore(); const transformRef = useRef<any>(null);
const transformRef = useRef<any>(null); const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
const [transformMode, setTransformMode] = useState< const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
"translate" | "rotate" | null const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere();
>(null); const { setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); const { isPlaying } = usePlayButtonStore();
const {
selectedEventSphere,
setSelectedEventSphere,
clearSelectedEventSphere,
} = useSelectedEventSphere();
const { setSelectedEventData, clearSelectedEventData } =
useSelectedEventData();
useEffect(() => { useEffect(() => {
if (selectedEventSphere) { if (selectedEventSphere) {
const eventData = getEventByModelUuid( const eventData = getEventByModelUuid(
selectedEventSphere.userData.modelUuid selectedEventSphere.userData.modelUuid
); );
if (eventData) { if (eventData) {
setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid); setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
} else { } else {
clearSelectedEventData(); clearSelectedEventData();
} }
} else { } else {
clearSelectedEventData(); clearSelectedEventData();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedEventSphere]); }, [selectedEventSphere]);
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
const keyCombination = detectModifierKeys(e); const keyCombination = detectModifierKeys(e);
if (!selectedEventSphere) return; if (!selectedEventSphere) return;
if (keyCombination === "G") { if (keyCombination === "G") {
setTransformMode((prev) => (prev === "translate" ? null : "translate")); setTransformMode((prev) => (prev === "translate" ? null : "translate"));
} }
if (keyCombination === "R") { if (keyCombination === "R") {
setTransformMode((prev) => (prev === "rotate" ? null : "rotate")); setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
} }
}; };
window.addEventListener("keydown", handleKeyDown); window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown);
}, [selectedEventSphere]); }, [selectedEventSphere]);
const updatePointToState = (selectedEventSphere: THREE.Mesh) => { const updatePointToState = (selectedEventSphere: THREE.Mesh) => {
let point = JSON.parse( let point = JSON.parse(
JSON.stringify( JSON.stringify(
getPointByUuid( getPointByUuid(
selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.modelUuid,
selectedEventSphere.userData.pointUuid selectedEventSphere.userData.pointUuid
) )
) )
); );
if (point) { if (point) {
point.position = [ point.position = [
selectedEventSphere.position.x, selectedEventSphere.position.x,
selectedEventSphere.position.y, selectedEventSphere.position.y,
selectedEventSphere.position.z, selectedEventSphere.position.z,
]; ];
updatePoint( updatePoint(
selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.modelUuid,
selectedEventSphere.userData.pointUuid, selectedEventSphere.userData.pointUuid,
point point
); );
}
};
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let isMouseDown = false;
const onMouseDown = () => {
isMouseDown = true;
drag = false;
};
const onMouseUp = () => {
if (selectedEventSphere && !drag) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster
.intersectObjects(scene.children, true)
.filter((intersect) => intersect.object.name === "Event-Sphere");
if (intersects.length === 0) {
clearSelectedEventSphere();
setTransformMode(null);
} }
}
}; };
const onMouseMove = () => { useEffect(() => {
if (isMouseDown) { const canvasElement = gl.domElement;
drag = true;
}
};
if (subModule === "mechanics") { let drag = false;
canvasElement.addEventListener("mousedown", onMouseDown); let isMouseDown = false;
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
}
return () => { const onMouseDown = () => {
canvasElement.removeEventListener("mousedown", onMouseDown); isMouseDown = true;
canvasElement.removeEventListener("mouseup", onMouseUp); drag = false;
canvasElement.removeEventListener("mousemove", onMouseMove); };
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [gl, subModule, selectedEventSphere]);
return ( const onMouseUp = () => {
<> if (selectedEventSphere && !drag) {
{activeModule === "simulation" && ( raycaster.setFromCamera(pointer, camera);
const intersects = raycaster
.intersectObjects(scene.children, true)
.filter((intersect) => intersect.object.name === "Event-Sphere");
if (intersects.length === 0) {
clearSelectedEventSphere();
setTransformMode(null);
}
}
};
const onMouseMove = () => {
if (isMouseDown) {
drag = true;
}
};
if (subModule === "mechanics") {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [gl, subModule, selectedEventSphere]);
return (
<> <>
<group name="EventPointsGroup"> {activeModule === "simulation" && (
{events.map((event, index) => { <>
if (event.type === "transfer") { <group name="EventPointsGroup" visible={!isPlaying}>
return ( {events.map((event, index) => {
<group if (event.type === "transfer") {
key={`${index}-${event.modelUuid}`} return (
position={event.position} <group
rotation={event.rotation} key={`${index}-${event.modelUuid}`}
> position={event.position}
{event.points.map((point, j) => ( rotation={event.rotation}
<mesh >
name="Event-Sphere" {event.points.map((point, j) => (
uuid={point.uuid} <mesh
ref={(el) => (sphereRefs.current[point.uuid] = el!)} name="Event-Sphere"
onClick={(e) => { uuid={point.uuid}
e.stopPropagation(); ref={(el) => (sphereRefs.current[point.uuid] = el!)}
setSelectedEventSphere( onClick={(e) => {
sphereRefs.current[point.uuid] e.stopPropagation();
); setSelectedEventSphere(
}} sphereRefs.current[point.uuid]
key={`${index}-${point.uuid}`} );
position={new THREE.Vector3(...point.position)} }}
userData={{ key={`${index}-${point.uuid}`}
modelUuid: event.modelUuid, position={new THREE.Vector3(...point.position)}
pointUuid: point.uuid, userData={{
}} modelUuid: event.modelUuid,
> pointUuid: point.uuid,
<sphereGeometry args={[0.1, 16, 16]} /> }}
<meshStandardMaterial color="orange" /> >
</mesh> <sphereGeometry args={[0.1, 16, 16]} />
))} <meshStandardMaterial color="orange" />
</group> </mesh>
); ))}
} else if (event.type === "vehicle") { </group>
return ( );
<group } else if (event.type === "vehicle") {
key={`${index}-${event.modelUuid}`} return (
position={event.position} <group
rotation={event.rotation} key={`${index}-${event.modelUuid}`}
> position={event.position}
<mesh rotation={event.rotation}
name="Event-Sphere" >
uuid={event.point.uuid} <mesh
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)} name="Event-Sphere"
onClick={(e) => { uuid={event.point.uuid}
e.stopPropagation(); ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
setSelectedEventSphere( onClick={(e) => {
sphereRefs.current[event.point.uuid] e.stopPropagation();
); setSelectedEventSphere(
}} sphereRefs.current[event.point.uuid]
position={new THREE.Vector3(...event.point.position)} );
userData={{ }}
modelUuid: event.modelUuid, position={new THREE.Vector3(...event.point.position)}
pointUuid: event.point.uuid, userData={{
}} modelUuid: event.modelUuid,
> pointUuid: event.point.uuid,
<sphereGeometry args={[0.1, 16, 16]} /> }}
<meshStandardMaterial color="blue" /> >
</mesh> <sphereGeometry args={[0.1, 16, 16]} />
</group> <meshStandardMaterial color="blue" />
); </mesh>
} else if (event.type === "roboticArm") { </group>
return ( );
<group } else if (event.type === "roboticArm") {
key={`${index}-${event.modelUuid}`} return (
position={event.position} <group
rotation={event.rotation} key={`${index}-${event.modelUuid}`}
> position={event.position}
<mesh rotation={event.rotation}
name="Event-Sphere" >
uuid={event.point.uuid} <mesh
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)} name="Event-Sphere"
onClick={(e) => { uuid={event.point.uuid}
e.stopPropagation(); ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
setSelectedEventSphere( onClick={(e) => {
sphereRefs.current[event.point.uuid] e.stopPropagation();
); setSelectedEventSphere(
}} sphereRefs.current[event.point.uuid]
position={new THREE.Vector3(...event.point.position)} );
userData={{ }}
modelUuid: event.modelUuid, position={new THREE.Vector3(...event.point.position)}
pointUuid: event.point.uuid, userData={{
}} modelUuid: event.modelUuid,
> pointUuid: event.point.uuid,
<sphereGeometry args={[0.1, 16, 16]} /> }}
<meshStandardMaterial color="green" /> >
</mesh> <sphereGeometry args={[0.1, 16, 16]} />
</group> <meshStandardMaterial color="green" />
); </mesh>
} else if (event.type === "machine") { </group>
return ( );
<group } else if (event.type === "machine") {
key={`${index}-${event.modelUuid}`} return (
position={event.position} <group
rotation={event.rotation} key={`${index}-${event.modelUuid}`}
> position={event.position}
<mesh rotation={event.rotation}
name="Event-Sphere" >
uuid={event.point.uuid} <mesh
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)} name="Event-Sphere"
onClick={(e) => { uuid={event.point.uuid}
e.stopPropagation(); ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
setSelectedEventSphere( onClick={(e) => {
sphereRefs.current[event.point.uuid] e.stopPropagation();
); setSelectedEventSphere(
}} sphereRefs.current[event.point.uuid]
position={new THREE.Vector3(...event.point.position)} );
userData={{ }}
modelUuid: event.modelUuid, position={new THREE.Vector3(...event.point.position)}
pointUuid: event.point.uuid, userData={{
}} modelUuid: event.modelUuid,
> pointUuid: event.point.uuid,
<sphereGeometry args={[0.1, 16, 16]} /> }}
<meshStandardMaterial color="purple" /> >
</mesh> <sphereGeometry args={[0.1, 16, 16]} />
</group> <meshStandardMaterial color="purple" />
); </mesh>
} else { </group>
return null; );
} } else {
})} return null;
</group> }
{selectedEventSphere && transformMode && ( })}
<TransformControls </group>
ref={transformRef} {selectedEventSphere && transformMode && (
object={selectedEventSphere} <TransformControls
mode={transformMode} ref={transformRef}
onMouseUp={(e) => { object={selectedEventSphere}
updatePointToState(selectedEventSphere); mode={transformMode}
}} onMouseUp={(e) => {
/> updatePointToState(selectedEventSphere);
)} }}
/>
)}
</>
)}
</> </>
)} );
</>
);
} }
export default PointsCreator; export default PointsCreator;

View File

@@ -34,16 +34,12 @@ const MachineAnimator = ({ currentPhase, handleCallBack, processingTime, machine
useEffect(() => { useEffect(() => {
isPlayingRef.current = isPlaying; isPlayingRef.current = isPlaying;
}, [isPlaying]); }, [isPlaying]);
useEffect(() => {
isResetRef.current = isReset;
}, [isReset]);
useEffect(() => { useEffect(() => {
if (isReset || !isPlaying) { if (isReset || !isPlaying) {
reset(); reset();
setReset(false);
startTimeRef.current = 0; startTimeRef.current = 0;
isPausedRef.current = false; isPausedRef.current = false;
pauseTimeRef.current = 0; pauseTimeRef.current = 0;
@@ -53,49 +49,53 @@ const MachineAnimator = ({ currentPhase, handleCallBack, processingTime, machine
} }
}, [isReset, isPlaying]) }, [isReset, isPlaying])
useEffect(() => { useEffect(() => {
if (currentPhase === 'processing' && !animationStarted.current && machineUuid) { if (currentPhase === 'processing' && !animationStarted.current && machineUuid) {
animationStarted.current = true; animationStarted.current = true;
startTimeRef.current = performance.now(); startTimeRef.current = performance.now();
animationFrameId.current = requestAnimationFrame(step); animationFrameId.current = requestAnimationFrame(step);
} }
}, [currentPhase]); }, [currentPhase]);
function step(time: number) { function step(time: number) {
if (!isPausedRef.current || !isResetRef.current) {
if (animationFrameId.current) { if (isPausedRef.current) {
if (!pauseTimeRef.current) {
pauseTimeRef.current = performance.now();
}
animationFrameId.current = requestAnimationFrame(step);
return;
}
if (pauseTimeRef.current) {
const pauseDuration = performance.now() - pauseTimeRef.current;
startTimeRef.current += pauseDuration;
pauseTimeRef.current = null;
}
const elapsed = time - startTimeRef.current;
const processedTime = (processingTime * 1000) / speed;
if (elapsed < processedTime) {
machineStatus(machineUuid, "Machine is currently processing the task");
animationFrameId.current = requestAnimationFrame(step);
} else {
animationStarted.current = false;
if (animationFrameId.current !== null) {
removeCurrentAction(machineUuid);
cancelAnimationFrame(animationFrameId.current); cancelAnimationFrame(animationFrameId.current);
animationFrameId.current = null; animationFrameId.current = null;
}
if (isPausedRef.current) {
if (!pauseTimeRef.current) {
pauseTimeRef.current = performance.now();
}
animationFrameId.current = requestAnimationFrame(step);
return;
}
if (pauseTimeRef.current) {
const pauseDuration = performance.now() - pauseTimeRef.current;
startTimeRef.current += pauseDuration;
pauseTimeRef.current = null;
}
const elapsed = time - startTimeRef.current;
const processedTime = processingTime * 1000;
if (elapsed < processedTime) {
machineStatus(machineUuid, "Machine is currently processing the task");
animationFrameId.current = requestAnimationFrame(step);
} else {
removeCurrentAction(machineUuid);
animationStarted.current = false;
handleCallBack(); handleCallBack();
} }
} }
} }
return null; return null;
} }

View File

@@ -10,10 +10,10 @@ function MachineInstance({ machineDetail }: any) {
const { machines, addCurrentAction, setMachineState, setMachineActive } = useMachineStore(); const { machines, addCurrentAction, setMachineState, setMachineActive } = useMachineStore();
const reset = () => { const reset = () => {
setCurrentPhase("idle");
setMachineState(machineDetail.modelUuid, 'idle'); setMachineState(machineDetail.modelUuid, 'idle');
setMachineActive(machineDetail.modelUuid, false); setMachineActive(machineDetail.modelUuid, false);
isIncrememtable.current = true; isIncrememtable.current = true;
setCurrentPhase("idle");
} }
const increment = () => { const increment = () => {
if (isIncrememtable.current) { if (isIncrememtable.current) {
@@ -31,7 +31,7 @@ function MachineInstance({ machineDetail }: any) {
if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && !machineDetail.currentAction) { if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && !machineDetail.currentAction) {
setTimeout(() => { setTimeout(() => {
increment(); increment();
}, 2000); }, 5000);
machineStatus(machineDetail.modelUuid, 'Machine is idle and waiting for next instruction.') machineStatus(machineDetail.modelUuid, 'Machine is idle and waiting for next instruction.')
} else if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && machineDetail.currentAction) { } else if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && machineDetail.currentAction) {
setCurrentPhase("processing"); setCurrentPhase("processing");
@@ -39,8 +39,6 @@ function MachineInstance({ machineDetail }: any) {
setMachineActive(machineDetail.modelUuid, true); setMachineActive(machineDetail.modelUuid, true);
machineStatus(machineDetail.modelUuid, "Machine started processing") machineStatus(machineDetail.modelUuid, "Machine started processing")
} }
} else {
reset();
} }
}, [currentPhase, isPlaying, machines]) }, [currentPhase, isPlaying, machines])
@@ -60,6 +58,7 @@ function MachineInstance({ machineDetail }: any) {
return ( return (
<> <>
<MachineAnimator processingTime={machineDetail.point.action.processTime} handleCallBack={handleCallBack} currentPhase={currentPhase} machineUuid={machineDetail.modelUuid} machineStatus={machineStatus} reset={reset} /> <MachineAnimator processingTime={machineDetail.point.action.processTime} handleCallBack={handleCallBack} currentPhase={currentPhase} machineUuid={machineDetail.modelUuid} machineStatus={machineStatus} reset={reset} />
</> </>
) )
} }

View File

@@ -1,26 +1,35 @@
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import * as THREE from 'three'; import * as THREE from 'three';
import { useFrame, useThree } from '@react-three/fiber'; import { useFrame, useThree } from '@react-three/fiber';
import { usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
interface MaterialAnimatorProps { interface MaterialAnimatorProps {
matRef: React.RefObject<THREE.Mesh>; matRef: React.RefObject<THREE.Mesh>;
material: MaterialSchema; material: MaterialSchema;
speed: number; // units per second currentSpeed: number;
onAnimationComplete?: () => void; onAnimationComplete?: () => void;
} }
function MaterialAnimator({ function MaterialAnimator({
matRef, matRef,
material, material,
speed, currentSpeed,
onAnimationComplete onAnimationComplete
}: MaterialAnimatorProps) { }: MaterialAnimatorProps) {
const { scene } = useThree(); const { scene } = useThree();
const [targetPosition, setTargetPosition] = useState<THREE.Vector3 | null>(null); const [targetPosition, setTargetPosition] = useState<THREE.Vector3 | null>(null);
const [isAnimating, setIsAnimating] = useState(false); const [isAnimating, setIsAnimating] = useState(false);
const animationStartTime = useRef<number>(0); const animationState = useRef({
const startPosition = useRef<THREE.Vector3>(new THREE.Vector3()); startTime: 0,
const totalDistance = useRef<number>(0); startPosition: new THREE.Vector3(),
totalDistance: 0,
pausedTime: 0,
isPaused: false,
lastFrameTime: 0
});
const { isPlaying } = usePlayButtonStore();
const { isPaused } = usePauseButtonStore();
const getWorldPosition = (uuid: string): THREE.Vector3 | null => { const getWorldPosition = (uuid: string): THREE.Vector3 | null => {
const obj = scene.getObjectByProperty('uuid', uuid); const obj = scene.getObjectByProperty('uuid', uuid);
@@ -30,37 +39,58 @@ function MaterialAnimator({
return position; return position;
}; };
// Handle target position changes and play state
useEffect(() => { useEffect(() => {
if (!material.next?.pointUuid) { if (!isPlaying || !material.next?.pointUuid) {
setTargetPosition(null);
setIsAnimating(false); setIsAnimating(false);
return; return;
} }
const newTarget = getWorldPosition(material.next.pointUuid); const newTarget = getWorldPosition(material.next.pointUuid);
if (newTarget && matRef.current) { if (newTarget && matRef.current) {
startPosition.current.copy(matRef.current.position); animationState.current.startPosition.copy(matRef.current.position);
totalDistance.current = startPosition.current.distanceTo(newTarget); animationState.current.totalDistance = animationState.current.startPosition.distanceTo(newTarget);
animationStartTime.current = performance.now(); animationState.current.startTime = performance.now() - animationState.current.pausedTime;
animationState.current.pausedTime = 0;
animationState.current.isPaused = false;
setTargetPosition(newTarget); setTargetPosition(newTarget);
setIsAnimating(true); setIsAnimating(true);
} }
}, [material.next?.pointUuid]); }, [material.next?.pointUuid, isPlaying]);
// Handle pause/unpause
useEffect(() => {
if (isPaused) {
animationState.current.isPaused = true;
setIsAnimating(false);
// Record the time when paused
animationState.current.pausedTime = performance.now() - animationState.current.startTime;
} else {
animationState.current.isPaused = false;
if (isPlaying && targetPosition && !isAnimating) {
// Resume from where we left off
animationState.current.startTime = performance.now() - animationState.current.pausedTime;
setIsAnimating(true);
}
}
}, [isPaused]);
useFrame(() => { useFrame(() => {
if (!matRef.current || !targetPosition || !isAnimating) return; if (!matRef.current || !targetPosition || !isAnimating || animationState.current.isPaused || !isPlaying) {
return;
}
const currentTime = performance.now();
// Calculate elapsed time since animation start, minus any paused time
const elapsed = (currentTime - animationState.current.startTime) / 1000;
const progress = Math.min(1, (currentSpeed * elapsed) / animationState.current.totalDistance);
// Calculate exact position based on constant speed
const elapsed = (performance.now() - animationStartTime.current) / 1000;
const progress = Math.min(1, (speed * elapsed) / totalDistance.current);
matRef.current.position.lerpVectors( matRef.current.position.lerpVectors(
startPosition.current, animationState.current.startPosition,
targetPosition, targetPosition,
progress progress
); );
// Check if animation is complete
if (progress >= 1) { if (progress >= 1) {
matRef.current.position.copy(targetPosition); matRef.current.position.copy(targetPosition);
setIsAnimating(false); setIsAnimating(false);

View File

@@ -5,12 +5,16 @@ import { useProductStore } from '../../../../../store/simulation/useProductStore
import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore';
import { MaterialModel } from '../material/materialModel'; import { MaterialModel } from '../material/materialModel';
import { useThree } from '@react-three/fiber'; import { useThree } from '@react-three/fiber';
import { useAnimationPlaySpeed } from '../../../../../store/usePlayButtonStore';
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
function MaterialInstance({ material }: { material: MaterialSchema }) { function MaterialInstance({ material }: { material: MaterialSchema }) {
const matRef: any = useRef(); const matRef: any = useRef();
const { scene } = useThree(); const { scene } = useThree();
const { getModelUuidByPointUuid, getPointByUuid, getEventByModelUuid } = useProductStore(); const { getModelUuidByPointUuid, getPointByUuid, getEventByModelUuid, getActionByUuid } = useProductStore();
const { selectedProduct } = useSelectedProduct(); const { selectedProduct } = useSelectedProduct();
const { speed } = useAnimationPlaySpeed();
const { triggerPointActions } = useTriggerHandler();
const getWorldPositionFromScene = (pointUuid: string): THREE.Vector3 | null => { const getWorldPositionFromScene = (pointUuid: string): THREE.Vector3 | null => {
const pointObj = scene.getObjectByProperty("uuid", pointUuid); const pointObj = scene.getObjectByProperty("uuid", pointUuid);
@@ -21,32 +25,32 @@ function MaterialInstance({ material }: { material: MaterialSchema }) {
return worldPosition; return worldPosition;
}; };
const { position, rotation, speed } = useMemo(() => { const { position, rotation, currentSpeed } = useMemo(() => {
if (!material.current?.pointUuid) { if (!material.current?.pointUuid) {
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), speed: 1 }; return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), currentSpeed: 1 };
} }
const modelUuid = getModelUuidByPointUuid(selectedProduct.productId, material.current.pointUuid); const modelUuid = getModelUuidByPointUuid(selectedProduct.productId, material.current.pointUuid);
if (!modelUuid) { if (!modelUuid) {
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), speed: 1 }; return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), currentSpeed: 1 };
} }
const speed = getCurrentSpeed(selectedProduct.productId, modelUuid); const currentSpeed = getCurrentSpeed(selectedProduct.productId, modelUuid);
const point = getPointByUuid(selectedProduct.productId, modelUuid, material.current.pointUuid); const point = getPointByUuid(selectedProduct.productId, modelUuid, material.current.pointUuid);
if (!point) { if (!point) {
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), speed: 1 }; return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), currentSpeed: 1 };
} }
const position = getWorldPositionFromScene(point.uuid); const position = getWorldPositionFromScene(point.uuid);
if (position) { if (position) {
return { position: position, rotation: new THREE.Vector3(0, 0, 0), speed: 1 }; return { position: position, rotation: new THREE.Vector3(0, 0, 0), currentSpeed: 1 };
} }
return { return {
position: new THREE.Vector3(...point.position), position: new THREE.Vector3(...point.position),
rotation: new THREE.Vector3(...point.rotation), rotation: new THREE.Vector3(...point.rotation),
speed: speed || 1 currentSpeed: currentSpeed || 1
}; };
}, [material, getPointByUuid]); }, [material, getPointByUuid]);
@@ -82,16 +86,25 @@ function MaterialInstance({ material }: { material: MaterialSchema }) {
// console.log('material: ', material); // console.log('material: ', material);
}, [material]) }, [material])
const callTrigger = () => {
const action = getActionByUuid(selectedProduct.productId, material.current.actionUuid)
if (action) {
triggerPointActions(action);
}
}
return ( return (
<> <>
<MaterialModel matRef={matRef} materialType={material.materialType} position={position} /> {material.isRendered &&
<MaterialModel matRef={matRef} materialType={material.materialType} visible={material.isVisible} position={position} />
}
<MaterialAnimator <MaterialAnimator
matRef={matRef} matRef={matRef}
material={material} material={material}
speed={speed} currentSpeed={currentSpeed * speed}
onAnimationComplete={() => { console.log('123');}} onAnimationComplete={() => { callTrigger() }}
/> />
</> </>
) )

View File

@@ -6,7 +6,7 @@ function MaterialInstances() {
const { materials } = useMaterialStore(); const { materials } = useMaterialStore();
useEffect(() => { useEffect(() => {
// console.log('materials: ', materials); console.log('materials: ', materials);
}, [materials]) }, [materials])
return ( return (

View File

@@ -1,11 +1,27 @@
import React from 'react' import React, { useEffect } from 'react'
import MaterialInstances from './instances/materialInstances' import MaterialInstances from './instances/materialInstances'
import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore';
import { useMaterialStore } from '../../../store/simulation/useMaterialStore';
function Materials() { function Materials() {
const { clearMaterials } = useMaterialStore();
const { isPlaying } = usePlayButtonStore();
const { isReset } = useResetButtonStore();
useEffect(() => {
if (isReset || !isPlaying) {
clearMaterials();
}
}, [isReset, isPlaying]);
return ( return (
<> <>
<MaterialInstances /> {isPlaying &&
<MaterialInstances />
}
</> </>
) )

View File

@@ -1,21 +1,23 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useProductStore } from '../../../store/simulation/useProductStore'; import { useProductStore } from '../../../store/simulation/useProductStore';
import { useActionHandler } from '../actions/useActionHandler'; import { useActionHandler } from '../actions/useActionHandler';
import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore';
function Simulator() { function Simulator() {
const { products } = useProductStore(); const { products } = useProductStore();
const { handleAction } = useActionHandler(); const { handleAction } = useActionHandler();
const { isPlaying } = usePlayButtonStore();
const { isReset } = useResetButtonStore();
useEffect(() => { useEffect(() => {
if (!isPlaying || isReset) return;
const executionOrder = determineExecutionOrder(products); const executionOrder = determineExecutionOrder(products);
executionOrder.forEach(point => { executionOrder.map(point => {
if ('actions' in point) { const action = 'actions' in point ? point.actions[0] : point.action;
handleAction(point.actions[0]); handleAction(action);
} else {
handleAction(point.action);
}
}); });
}, [products, handleAction]); }, [products, handleAction, isPlaying, isReset]);
function determineExecutionOrder(products: productsSchema): PointsScheme[] { function determineExecutionOrder(products: productsSchema): PointsScheme[] {
// Create maps for all events and points // Create maps for all events and points

View File

@@ -10,6 +10,7 @@ import { handleAddEventToProduct } from "../../events/points/functions/handleAdd
import { QuadraticBezierLine } from "@react-three/drei"; import { QuadraticBezierLine } from "@react-three/drei";
import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi";
import { useDeleteTool } from "../../../../store/store"; import { useDeleteTool } from "../../../../store/store";
import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";
interface ConnectionLine { interface ConnectionLine {
id: string; id: string;
@@ -29,6 +30,7 @@ function TriggerConnector() {
const [helperlineColor, setHelperLineColor] = useState<string>("red"); const [helperlineColor, setHelperLineColor] = useState<string>("red");
const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3; end: THREE.Vector3; mid: THREE.Vector3; } | null>(null); const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3; end: THREE.Vector3; mid: THREE.Vector3; } | null>(null);
const { deleteTool } = useDeleteTool(); const { deleteTool } = useDeleteTool();
const { isPlaying } = usePlayButtonStore();
const [firstSelectedPoint, setFirstSelectedPoint] = useState<{ const [firstSelectedPoint, setFirstSelectedPoint] = useState<{
productId: string; productId: string;
@@ -424,7 +426,7 @@ function TriggerConnector() {
}; };
return ( return (
<group name="simulationConnectionGroup" > <group name="simulationConnectionGroup" visible={!isPlaying}>
{connections.map((connection) => { {connections.map((connection) => {
const startPoint = getWorldPositionFromScene(connection.startPointUuid); const startPoint = getWorldPositionFromScene(connection.startPointUuid);
const endPoint = getWorldPositionFromScene(connection.endPointUuid); const endPoint = getWorldPositionFromScene(connection.endPointUuid);

View File

@@ -1,13 +1,128 @@
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react';
import { useActionHandler } from '../../actions/useActionHandler'; import { useActionHandler } from '../../actions/useActionHandler';
import { useProductStore } from '../../../../store/simulation/useProductStore'; import { useProductStore } from '../../../../store/simulation/useProductStore';
import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore';
import { useMaterialStore } from '../../../../store/simulation/useMaterialStore';
export function useTriggerHandler() { export function useTriggerHandler() {
const { getActionByUuid } = useProductStore(); const { getActionByUuid, getEventByTriggerUuid, getEventByModelUuid } = useProductStore();
const { handleAction } = useActionHandler(); const { handleAction } = useActionHandler();
const { getMaterialByCurrentModelUuid, setCurrentLocation, setNextLocation } = useMaterialStore();
const { selectedProduct } = useSelectedProduct();
const handleTrigger = (trigger: TriggerSchema) => { const handleTrigger = (trigger: TriggerSchema, actionUuid: string) => {
// const fromEvent = getEventByTriggerUuid(selectedProduct.productId, trigger.triggerUuid);
// console.log('fromEvent: ', fromEvent);
// const toEvent = getEventByModelUuid(selectedProduct.productId, trigger.triggeredAsset?.triggeredModel.modelUuid || '');
// console.log('toEvent: ', toEvent);
// if (fromEvent?.type === 'transfer') {
// if (toEvent?.type === 'transfer') {
// // console.log('toEvent: ', toEvent.type);
// // Transfer to Transfer
// const action = getActionByUuid(selectedProduct.productId, trigger.triggeredAsset?.triggeredAction?.actionUuid || '');
// if (action && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
// const material = getMaterialByCurrentModelUuid(fromEvent.modelUuid);
// if (material) {
// if (material.next &&
// action.triggers[0].triggeredAsset?.triggeredAction?.actionUuid &&
// action.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid) {
// setCurrentLocation(material.materialId, material.next);
// setNextLocation(material.materialId, {
// modelUuid: toEvent.modelUuid,
// pointUuid: action.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid,
// actionUuid: action.triggers[0].triggeredAsset?.triggeredAction?.actionUuid
// });
// }
// handleAction(action);
// }
// }
// } else if (toEvent?.type === 'vehicle') {
// // Transfer to Vehicle
// } else if (toEvent?.type === 'machine') {
// // Transfer to Machine
// } else if (toEvent?.type === 'roboticArm') {
// // Transfer to Robotic Arm
// } else if (toEvent?.type === 'storageUnit') {
// // Transfer to Storage Unit
// }
// } else if (fromEvent?.type === 'vehicle') {
// if (toEvent?.type === 'transfer') {
// // Vehicle to Transfer
// } else if (toEvent?.type === 'vehicle') {
// // Vehicle to Vehicle
// } else if (toEvent?.type === 'machine') {
// // Vehicle to Machine
// } else if (toEvent?.type === 'roboticArm') {
// // Vehicle to Robotic Arm
// } else if (toEvent?.type === 'storageUnit') {
// // Vehicle to Storage Unit
// }
// } else if (fromEvent?.type === 'machine') {
// if (toEvent?.type === 'transfer') {
// // Machine to Transfer
// } else if (toEvent?.type === 'vehicle') {
// // Machine to Vehicle
// } else if (toEvent?.type === 'machine') {
// // Machine to Machine
// } else if (toEvent?.type === 'roboticArm') {
// // Machine to Robotic Arm
// } else if (toEvent?.type === 'storageUnit') {
// // Machine to Storage Unit
// }
// } else if (fromEvent?.type === 'roboticArm') {
// if (toEvent?.type === 'transfer') {
// // Robotic Arm to Transfer
// } else if (toEvent?.type === 'vehicle') {
// // Robotic Arm to Vehicle
// } else if (toEvent?.type === 'machine') {
// // Robotic Arm to Machine
// } else if (toEvent?.type === 'roboticArm') {
// // Robotic Arm to Robotic Arm
// } else if (toEvent?.type === 'storageUnit') {
// // Robotic Arm to Storage Unit
// }
// } else if (fromEvent?.type === 'storageUnit') {
// if (toEvent?.type === 'transfer') {
// // Storage Unit to Transfer
// } else if (toEvent?.type === 'vehicle') {
// // Storage Unit to Vehicle
// } else if (toEvent?.type === 'machine') {
// // Storage Unit to Machine
// } else if (toEvent?.type === 'roboticArm') {
// // Storage Unit to Robotic Arm
// } else if (toEvent?.type === 'storageUnit') {
// // Storage Unit to Storage Unit
// }
// }
} }
const triggerPointActions = useCallback((action: Action) => { const triggerPointActions = useCallback((action: Action) => {
@@ -18,7 +133,7 @@ export function useTriggerHandler() {
case 'onStart': case 'onStart':
break; break;
case 'onComplete': case 'onComplete':
handleTrigger(trigger); handleTrigger(trigger, action.actionUuid);
break; break;
case 'onStop': case 'onStop':
break; break;

View File

@@ -0,0 +1,61 @@
import { useEffect, useRef, useState } from 'react';
import { useThree, useFrame } from '@react-three/fiber';
import * as THREE from 'three';
import { MaterialModel } from '../../../materials/instances/material/materialModel';
import { Html } from '@react-three/drei';
type MaterialAnimatorProps = {
agvDetail: VehicleStatus;
};
const MaterialAnimator = ({ agvDetail }: MaterialAnimatorProps) => {
const meshRef = useRef<any>(null!);
const [hasLoad, setHasLoad] = useState(false);
const { scene } = useThree();
const offset = new THREE.Vector3(0, 0.85, 0);
const [htmlPosition, setHtmlPosition] = useState<[number, number, number]>([0, 0, 0]);
const [htmlRotation, setHtmlRotation] = useState<[number, number, number]>([0, 0, 0]);
useEffect(() => {
setHasLoad(agvDetail.currentLoad > 0);
}, [agvDetail.currentLoad]);
useFrame(() => {
if (!hasLoad || !meshRef.current) return;
const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D;
if (agvModel) {
const worldPosition = offset.clone().applyMatrix4(agvModel.matrixWorld);
meshRef.current.position.copy(worldPosition);
setHtmlPosition([worldPosition.x, worldPosition.y, worldPosition.z]);
meshRef.current.rotation.copy(agvModel.rotation);
setHtmlRotation([agvModel.rotation.x, agvModel.rotation.y, agvModel.rotation.z]);
}
});
return (
<>
{hasLoad && (
<>
<MaterialModel
matRef={meshRef}
materialType={agvDetail.materialType || 'Default material'}
/>
<Html
position={htmlPosition}
rotation={htmlRotation}
style={{ backgroundColor: "pink", padding: "4px", borderRadius: "4px" }}
>
{agvDetail.currentLoad}
</Html>
</>
)}
</>
);
};
export default MaterialAnimator;

View File

@@ -199,7 +199,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
return ( return (
<> <>
{currentPath.length > 0 && ( {currentPath.length > 0 && (
<> <group visible={false}>
<Line points={currentPath} color="blue" lineWidth={3} /> <Line points={currentPath} color="blue" lineWidth={3} />
{currentPath.map((point, index) => ( {currentPath.map((point, index) => (
<mesh key={index} position={point}> <mesh key={index} position={point}>
@@ -207,7 +207,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
<meshStandardMaterial color="red" /> <meshStandardMaterial color="red" />
</mesh> </mesh>
))} ))}
</> </group>
)} )}
</> </>
); );

View File

@@ -5,11 +5,13 @@ import { NavMeshQuery } from '@recast-navigation/core';
import { useNavMesh } from '../../../../../store/store'; import { useNavMesh } from '../../../../../store/store';
import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore';
import MaterialAnimator from '../animator/materialAnimator';
function VehicleInstance({ agvDetail }: any) { function VehicleInstance({ agvDetail }: any) {
const { navMesh } = useNavMesh(); const { navMesh } = useNavMesh();
const vehicleRef: any = useRef();
const { isPlaying } = usePlayButtonStore(); const { isPlaying } = usePlayButtonStore();
const { vehicles, setVehicleActive, setVehicleState, incrementVehicleLoad } = useVehicleStore(); const { vehicles, setVehicleActive, setVehicleState, incrementVehicleLoad, setMaterialType } = useVehicleStore();
const [currentPhase, setCurrentPhase] = useState<string>('stationed'); const [currentPhase, setCurrentPhase] = useState<string>('stationed');
const [path, setPath] = useState<[number, number, number][]>([]); const [path, setPath] = useState<[number, number, number][]>([]);
let isIncrememtable = useRef<boolean>(true); let isIncrememtable = useRef<boolean>(true);
@@ -44,8 +46,8 @@ function VehicleInstance({ agvDetail }: any) {
const increment = () => { const increment = () => {
if (isIncrememtable.current) { if (isIncrememtable.current) {
incrementVehicleLoad(agvDetail.modelUuid, 10); incrementVehicleLoad(agvDetail.modelUuid, 10);
setMaterialType(agvDetail.modelUuid, 'Material 1')
isIncrememtable.current = false; isIncrememtable.current = false;
} }
} }
@@ -71,7 +73,7 @@ function VehicleInstance({ agvDetail }: any) {
}, 5000); }, 5000);
if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) { if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity && agvDetail.materialType) {
const toDrop = computePath( const toDrop = computePath(
agvDetail.point.action.pickUpPoint.position, agvDetail.point.action.pickUpPoint.position,
agvDetail.point.action.unLoadPoint.position agvDetail.point.action.unLoadPoint.position
@@ -118,6 +120,7 @@ function VehicleInstance({ agvDetail }: any) {
setVehicleState(agvDetail.modelUuid, 'idle'); setVehicleState(agvDetail.modelUuid, 'idle');
setVehicleActive(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, false);
setPath([]); setPath([]);
setMaterialType(agvDetail.modelUuid, null)
vehicleStatus(agvDetail.modelUuid, 'Reached pickup point again, cycle complete'); vehicleStatus(agvDetail.modelUuid, 'Reached pickup point again, cycle complete');
} }
} }
@@ -132,6 +135,7 @@ function VehicleInstance({ agvDetail }: any) {
agvDetail={agvDetail} agvDetail={agvDetail}
reset={reset} reset={reset}
/> />
<MaterialAnimator agvDetail={agvDetail} />
</> </>
); );
} }

View File

@@ -6,6 +6,7 @@ type MaterialsStore = {
addMaterial: (material: MaterialSchema) => MaterialSchema | undefined; addMaterial: (material: MaterialSchema) => MaterialSchema | undefined;
removeMaterial: (materialId: string) => MaterialSchema | undefined; removeMaterial: (materialId: string) => MaterialSchema | undefined;
clearMaterials: () => void;
updateMaterial: (materialId: string, updates: Partial<MaterialSchema>) => MaterialSchema | undefined; updateMaterial: (materialId: string, updates: Partial<MaterialSchema>) => MaterialSchema | undefined;
setCurrentLocation: ( setCurrentLocation: (
@@ -35,6 +36,7 @@ type MaterialsStore = {
setIsRendered: (materialId: string, isRendered: boolean) => MaterialSchema | undefined; setIsRendered: (materialId: string, isRendered: boolean) => MaterialSchema | undefined;
getMaterialById: (materialId: string) => MaterialSchema | undefined; getMaterialById: (materialId: string) => MaterialSchema | undefined;
getMaterialByCurrentModelUuid: (currentModelUuid: string) => MaterialSchema | undefined;
getMaterialsByPoint: (pointUuid: string) => MaterialSchema[]; getMaterialsByPoint: (pointUuid: string) => MaterialSchema[];
getMaterialsByModel: (modelUuid: string) => MaterialSchema[]; getMaterialsByModel: (modelUuid: string) => MaterialSchema[];
}; };
@@ -63,6 +65,12 @@ export const useMaterialStore = create<MaterialsStore>()(
return updatedMaterial; return updatedMaterial;
}, },
clearMaterials: () => {
set((state) => {
state.materials = [];
});
},
updateMaterial: (materialId, updates) => { updateMaterial: (materialId, updates) => {
let updatedMaterial: MaterialSchema | undefined; let updatedMaterial: MaterialSchema | undefined;
set((state) => { set((state) => {
@@ -186,6 +194,10 @@ export const useMaterialStore = create<MaterialsStore>()(
getMaterialById: (materialId) => { getMaterialById: (materialId) => {
return get().materials.find(m => m.materialId === materialId); return get().materials.find(m => m.materialId === materialId);
}, },
getMaterialByCurrentModelUuid: (currentModelUuid) => {
return get().materials.find(m => m.current?.modelUuid === currentModelUuid);
},
getMaterialsByPoint: (pointUuid) => { getMaterialsByPoint: (pointUuid) => {
return get().materials.filter(m => return get().materials.filter(m =>

View File

@@ -61,6 +61,8 @@ type ProductsStore = {
// Helper functions // Helper functions
getProductById: (productId: string) => { productName: string; productId: string; eventDatas: EventsSchema[] } | undefined; getProductById: (productId: string) => { productName: string; productId: string; eventDatas: EventsSchema[] } | undefined;
getEventByModelUuid: (productId: string, modelUuid: string) => EventsSchema | undefined; getEventByModelUuid: (productId: string, modelUuid: string) => EventsSchema | undefined;
getEventByTriggerUuid: (productId: string, triggerUuid: string) => EventsSchema | undefined;
getEventByPointUuid: (productId: string, pointUuid: string) => EventsSchema | undefined;
getPointByUuid: (productId: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | 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; getActionByUuid: (productId: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined;
getModelUuidByPointUuid: (productId: string, actionUuid: string) => (string) | undefined; getModelUuidByPointUuid: (productId: string, actionUuid: string) => (string) | undefined;
@@ -540,6 +542,53 @@ export const useProductStore = create<ProductsStore>()(
return product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); return product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid);
}, },
getEventByTriggerUuid: (productId, triggerUuid) => {
const product = get().getProductById(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?.triggers?.some(t => t.triggerUuid === triggerUuid)) {
return event;
}
}
} else if ('point' in event) {
const point = (event as any).point;
if ('action' in point) {
if (point.action?.triggers?.some((t: any) => t.triggerUuid === triggerUuid)) {
return event;
}
} else if ('actions' in point) {
for (const action of point.actions) {
if (action.triggers?.some((t: any) => t.triggerUuid === triggerUuid)) {
return event;
}
}
}
}
}
return undefined;
},
getEventByPointUuid: (productId, pointUuid) => {
const product = get().getProductById(productId);
if (!product) return undefined;
for (const event of product.eventDatas) {
if ('points' in event) {
if ((event as ConveyorEventSchema).points.some(p => p.uuid === pointUuid)) {
return event;
}
} else if ('point' in event) {
if ((event as any).point?.uuid === pointUuid) {
return event;
}
}
}
return undefined;
},
getPointByUuid: (productId, modelUuid, pointUuid) => { getPointByUuid: (productId, modelUuid, pointUuid) => {
const event = get().getEventByModelUuid(productId, modelUuid); const event = get().getEventByModelUuid(productId, modelUuid);
if (!event) return undefined; if (!event) return undefined;