diff --git a/app/src/assets/gltf-glb/arm_ui_drop.glb b/app/src/assets/gltf-glb/arm_ui_drop.glb index 2a75e7e..ce3a012 100644 Binary files a/app/src/assets/gltf-glb/arm_ui_drop.glb and b/app/src/assets/gltf-glb/arm_ui_drop.glb differ diff --git a/app/src/assets/gltf-glb/arm_ui_pick.glb b/app/src/assets/gltf-glb/arm_ui_pick.glb index 979ade4..0ea6a23 100644 Binary files a/app/src/assets/gltf-glb/arm_ui_pick.glb and b/app/src/assets/gltf-glb/arm_ui_pick.glb differ diff --git a/app/src/assets/gltf-glb/rigged/ik_arm.glb b/app/src/assets/gltf-glb/rigged/ik_arm.glb new file mode 100644 index 0000000..22e26a8 Binary files /dev/null and b/app/src/assets/gltf-glb/rigged/ik_arm.glb differ diff --git a/app/src/assets/gltf-glb/rigged/ik_arm_1.glb b/app/src/assets/gltf-glb/rigged/ik_arm_1.glb new file mode 100644 index 0000000..326efad Binary files /dev/null and b/app/src/assets/gltf-glb/rigged/ik_arm_1.glb differ diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index 9787d00..cc2efb6 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -38,18 +38,12 @@ const SimulationPlayer: React.FC = () => { const { isReset, setReset } = useResetButtonStore(); const { subModule } = useSubModuleStore(); - useEffect(() => { - if (isReset) { - setTimeout(()=>{ - setReset(false); - },0) - } - }, [isReset]) - // Button functions const handleReset = () => { setReset(true); + setIsPaused(false); setSpeed(1); + setPlaySimulation(false); // local state reset }; const handlePlayStop = () => { setIsPaused(!isPaused); @@ -58,6 +52,7 @@ const SimulationPlayer: React.FC = () => { const handleExit = () => { setPlaySimulation(false); setIsPlaying(false); + setIsPaused(false); setActiveTool("cursor"); }; @@ -279,10 +274,11 @@ const SimulationPlayer: React.FC = () => { {index < intervals.length - 1 && (
= ((index + 1) / totalSegments) * 100 + className={`line ${ + progress >= ((index + 1) / totalSegments) * 100 ? "filled" : "" - }`} + }`} >
)} diff --git a/app/src/modules/simulation/actions/conveyor/actionHandler/useDefaultHandler.ts b/app/src/modules/simulation/actions/conveyor/actionHandler/useDefaultHandler.ts new file mode 100644 index 0000000..e69de29 diff --git a/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts b/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts index 20d82bf..8f639e6 100644 --- a/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts +++ b/app/src/modules/simulation/actions/conveyor/actionHandler/useSpawnHandler.ts @@ -4,28 +4,44 @@ import { useFrame } from "@react-three/fiber"; import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore"; import { useProductStore } from "../../../../../store/simulation/useProductStore"; import { useSelectedProduct } from "../../../../../store/simulation/useSimulationStore"; +import { usePlayButtonStore, useAnimationPlaySpeed, usePauseButtonStore, useResetButtonStore } from "../../../../../store/usePlayButtonStore"; -export function useSpawnHandler() { - const { addMaterial } = useMaterialStore(); - const { getModelUuidByActionUuid, getPointUuidByActionUuid } = useProductStore(); - const { selectedProduct } = useSelectedProduct(); - const lastSpawnTime = useRef(null); - const startTime = useRef(null); - const spawnCountRef = useRef(0); - const spawnParams = useRef<{ +interface SpawnInstance { + lastSpawnTime: number | null; + startTime: number; + spawnCount: number; + params: { material: string; intervalMs: number; totalCount: number; action: ConveyorAction; - } | null>(null); + }; + pauseStartTime: number; + remainingTime: number; + isPaused: boolean; +} - const clearCurrentSpawn = useCallback(() => { - lastSpawnTime.current = null; - startTime.current = null; - spawnCountRef.current = 0; - spawnParams.current = null; +export function useSpawnHandler() { + const { addMaterial } = useMaterialStore(); + const { getModelUuidByActionUuid, getPointUuidByActionUuid } = useProductStore(); + const { isPlaying } = usePlayButtonStore(); + const { isPaused } = usePauseButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const { isReset } = useResetButtonStore(); + const { selectedProduct } = useSelectedProduct(); + + const activeSpawns = useRef>(new Map()); + + const clearAllSpawns = useCallback(() => { + activeSpawns.current.clear(); }, []); + useEffect(() => { + if (isReset) { + clearAllSpawns(); + } + }, [isReset, clearAllSpawns]); + const spawnLogStatus = (materialUuid: string, status: string) => { // console.log(`${materialUuid}, ${status}`); } @@ -64,78 +80,121 @@ export function useSpawnHandler() { addMaterial(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(() => { - if (!spawnParams.current || !startTime.current) return; + if (!isPlaying || isPaused || isReset) return; const currentTime = performance.now(); - const { material, intervalMs, totalCount, action } = spawnParams.current; - const isFirstSpawn = lastSpawnTime.current === null; - const elapsed = currentTime - startTime.current; + const completedActions: string[] = []; - // First spawn - if (isFirstSpawn) { - if (elapsed >= intervalMs) { - const createdMaterial = createNewMaterial(material, action); - if (createdMaterial) { - spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`); + activeSpawns.current.forEach((spawn, actionUuid) => { + const { material, intervalMs, totalCount, action } = spawn.params; + const isFirstSpawn = spawn.lastSpawnTime === null; + + // First spawn + 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; - spawnCountRef.current = 1; + return; + } - if (totalCount <= 1) { - clearCurrentSpawn(); + // Subsequent spawns + 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 - if (lastSpawnTime.current !== null) { - 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(); - } - } - } + completedActions.forEach(actionUuid => { + activeSpawns.current.delete(actionUuid); + }); }); const handleSpawn = useCallback((action: ConveyorAction) => { 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; - clearCurrentSpawn(); + if (activeSpawns.current.has(actionUuid)) { + activeSpawns.current.delete(actionUuid); + } - spawnParams.current = { - material, - intervalMs, - totalCount: spawnCount, - action: action - }; - - startTime.current = performance.now(); - }, [clearCurrentSpawn]); + activeSpawns.current.set(actionUuid, { + lastSpawnTime: null, + startTime: performance.now(), + spawnCount: 0, + params: { + material, + intervalMs, + totalCount: spawnCount, + action: action + }, + pauseStartTime: 0, + remainingTime: 0, + isPaused: false + }); + }, []); useEffect(() => { return () => { - clearCurrentSpawn(); + clearAllSpawns(); }; - }, [clearCurrentSpawn]); + }, [clearAllSpawns]); return { handleSpawn, - clearCurrentSpawn + clearCurrentSpawn: clearAllSpawns }; } \ No newline at end of file diff --git a/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts b/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts index 744ff4c..f93ef4f 100644 --- a/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts +++ b/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts @@ -5,7 +5,8 @@ export function useConveyorActions() { const { handleSpawn, clearCurrentSpawn } = useSpawnHandler(); 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) => { diff --git a/app/src/modules/simulation/actions/useActionHandler.ts b/app/src/modules/simulation/actions/useActionHandler.ts index bf1a41c..28a5359 100644 --- a/app/src/modules/simulation/actions/useActionHandler.ts +++ b/app/src/modules/simulation/actions/useActionHandler.ts @@ -1,3 +1,4 @@ +import { usePlayButtonStore, useResetButtonStore } from "../../../store/usePlayButtonStore"; import { useConveyorActions } from "./conveyor/useConveyorActions"; import { useMachineActions } from "./machine/useMachineActions"; import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions"; @@ -6,6 +7,8 @@ import { useVehicleActions } from "./vehicle/useVehicleActions"; import { useCallback, useEffect } from "react"; export function useActionHandler() { + const { isReset } = useResetButtonStore(); + const { isPlaying } = usePlayButtonStore(); const { handleConveyorAction, cleanup: cleanupConveyor } = useConveyorActions(); const { handleVehicleAction, cleanup: cleanupVehicle } = useVehicleActions(); const { handleRoboticArmAction, cleanup: cleanupRoboticArm } = useRoboticArmActions(); @@ -52,7 +55,7 @@ export function useActionHandler() { return () => { cleanup(); }; - }, [cleanup]); + }, [cleanup, isReset, isPlaying]); return { handleAction, diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx index 29a6db7..84b6e7c 100644 --- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx +++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx @@ -1,276 +1,265 @@ import React, { useEffect, useRef, useState } from "react"; import * as THREE from "three"; import { useEventsStore } from "../../../../../store/simulation/useEventsStore"; -import useModuleStore, { - useSubModuleStore, -} from "../../../../../store/useModuleStore"; +import useModuleStore, { useSubModuleStore } from "../../../../../store/useModuleStore"; import { TransformControls } from "@react-three/drei"; import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; -import { - useSelectedEventSphere, - useSelectedEventData, -} from "../../../../../store/simulation/useSimulationStore"; +import { useSelectedEventSphere, useSelectedEventData, } from "../../../../../store/simulation/useSimulationStore"; import { useThree } from "@react-three/fiber"; +import { usePlayButtonStore } from "../../../../../store/usePlayButtonStore"; function PointsCreator() { - const { gl, raycaster, scene, pointer, camera } = useThree(); - const { subModule } = useSubModuleStore(); - const { events, updatePoint, getPointByUuid, getEventByModelUuid } = - useEventsStore(); - const { activeModule } = useModuleStore(); - const transformRef = useRef(null); - const [transformMode, setTransformMode] = useState< - "translate" | "rotate" | null - >(null); - const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); - const { - selectedEventSphere, - setSelectedEventSphere, - clearSelectedEventSphere, - } = useSelectedEventSphere(); - const { setSelectedEventData, clearSelectedEventData } = - useSelectedEventData(); + const { gl, raycaster, scene, pointer, camera } = useThree(); + const { subModule } = useSubModuleStore(); + const { events, updatePoint, getPointByUuid, getEventByModelUuid } = useEventsStore(); + const { activeModule } = useModuleStore(); + const transformRef = useRef(null); + const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); + const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); + const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere(); + const { setSelectedEventData, clearSelectedEventData } = useSelectedEventData(); + const { isPlaying } = usePlayButtonStore(); - useEffect(() => { - if (selectedEventSphere) { - const eventData = getEventByModelUuid( - selectedEventSphere.userData.modelUuid - ); + useEffect(() => { + if (selectedEventSphere) { + const eventData = getEventByModelUuid( + selectedEventSphere.userData.modelUuid + ); - if (eventData) { - setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid); - } else { - clearSelectedEventData(); - } - } else { - clearSelectedEventData(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedEventSphere]); - - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - const keyCombination = detectModifierKeys(e); - if (!selectedEventSphere) 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); - }, [selectedEventSphere]); - - const updatePointToState = (selectedEventSphere: THREE.Mesh) => { - let point = JSON.parse( - JSON.stringify( - getPointByUuid( - selectedEventSphere.userData.modelUuid, - selectedEventSphere.userData.pointUuid - ) - ) - ); - if (point) { - point.position = [ - selectedEventSphere.position.x, - selectedEventSphere.position.y, - selectedEventSphere.position.z, - ]; - updatePoint( - selectedEventSphere.userData.modelUuid, - selectedEventSphere.userData.pointUuid, - 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); + if (eventData) { + setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid); + } else { + clearSelectedEventData(); + } + } else { + clearSelectedEventData(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedEventSphere]); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + const keyCombination = detectModifierKeys(e); + if (!selectedEventSphere) 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); + }, [selectedEventSphere]); + + const updatePointToState = (selectedEventSphere: THREE.Mesh) => { + let point = JSON.parse( + JSON.stringify( + getPointByUuid( + selectedEventSphere.userData.modelUuid, + selectedEventSphere.userData.pointUuid + ) + ) + ); + if (point) { + point.position = [ + selectedEventSphere.position.x, + selectedEventSphere.position.y, + selectedEventSphere.position.z, + ]; + updatePoint( + selectedEventSphere.userData.modelUuid, + selectedEventSphere.userData.pointUuid, + point + ); } - } }; - const onMouseMove = () => { - if (isMouseDown) { - drag = true; - } - }; + useEffect(() => { + const canvasElement = gl.domElement; - if (subModule === "mechanics") { - canvasElement.addEventListener("mousedown", onMouseDown); - canvasElement.addEventListener("mouseup", onMouseUp); - canvasElement.addEventListener("mousemove", onMouseMove); - } + let drag = false; + let isMouseDown = false; - return () => { - canvasElement.removeEventListener("mousedown", onMouseDown); - canvasElement.removeEventListener("mouseup", onMouseUp); - canvasElement.removeEventListener("mousemove", onMouseMove); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [gl, subModule, selectedEventSphere]); + const onMouseDown = () => { + isMouseDown = true; + drag = false; + }; - return ( - <> - {activeModule === "simulation" && ( + 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 = () => { + 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 ( <> - - {events.map((event, index) => { - if (event.type === "transfer") { - return ( - - {event.points.map((point, j) => ( - (sphereRefs.current[point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - setSelectedEventSphere( - sphereRefs.current[point.uuid] - ); - }} - key={`${index}-${point.uuid}`} - position={new THREE.Vector3(...point.position)} - userData={{ - modelUuid: event.modelUuid, - pointUuid: point.uuid, - }} - > - - - - ))} - - ); - } else if (event.type === "vehicle") { - return ( - - (sphereRefs.current[event.point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - setSelectedEventSphere( - sphereRefs.current[event.point.uuid] - ); - }} - position={new THREE.Vector3(...event.point.position)} - userData={{ - modelUuid: event.modelUuid, - pointUuid: event.point.uuid, - }} - > - - - - - ); - } else if (event.type === "roboticArm") { - return ( - - (sphereRefs.current[event.point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - setSelectedEventSphere( - sphereRefs.current[event.point.uuid] - ); - }} - position={new THREE.Vector3(...event.point.position)} - userData={{ - modelUuid: event.modelUuid, - pointUuid: event.point.uuid, - }} - > - - - - - ); - } else if (event.type === "machine") { - return ( - - (sphereRefs.current[event.point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - setSelectedEventSphere( - sphereRefs.current[event.point.uuid] - ); - }} - position={new THREE.Vector3(...event.point.position)} - userData={{ - modelUuid: event.modelUuid, - pointUuid: event.point.uuid, - }} - > - - - - - ); - } else { - return null; - } - })} - - {selectedEventSphere && transformMode && ( - { - updatePointToState(selectedEventSphere); - }} - /> - )} + {activeModule === "simulation" && ( + <> + + {events.map((event, index) => { + if (event.type === "transfer") { + return ( + + {event.points.map((point, j) => ( + (sphereRefs.current[point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + setSelectedEventSphere( + sphereRefs.current[point.uuid] + ); + }} + key={`${index}-${point.uuid}`} + position={new THREE.Vector3(...point.position)} + userData={{ + modelUuid: event.modelUuid, + pointUuid: point.uuid, + }} + > + + + + ))} + + ); + } else if (event.type === "vehicle") { + return ( + + (sphereRefs.current[event.point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + setSelectedEventSphere( + sphereRefs.current[event.point.uuid] + ); + }} + position={new THREE.Vector3(...event.point.position)} + userData={{ + modelUuid: event.modelUuid, + pointUuid: event.point.uuid, + }} + > + + + + + ); + } else if (event.type === "roboticArm") { + return ( + + (sphereRefs.current[event.point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + setSelectedEventSphere( + sphereRefs.current[event.point.uuid] + ); + }} + position={new THREE.Vector3(...event.point.position)} + userData={{ + modelUuid: event.modelUuid, + pointUuid: event.point.uuid, + }} + > + + + + + ); + } else if (event.type === "machine") { + return ( + + (sphereRefs.current[event.point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + setSelectedEventSphere( + sphereRefs.current[event.point.uuid] + ); + }} + position={new THREE.Vector3(...event.point.position)} + userData={{ + modelUuid: event.modelUuid, + pointUuid: event.point.uuid, + }} + > + + + + + ); + } else { + return null; + } + })} + + {selectedEventSphere && transformMode && ( + { + updatePointToState(selectedEventSphere); + }} + /> + )} + + )} - )} - - ); + ); } export default PointsCreator; diff --git a/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx b/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx index 6ef2b0e..c4b4b44 100644 --- a/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx +++ b/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx @@ -34,16 +34,12 @@ const MachineAnimator = ({ currentPhase, handleCallBack, processingTime, machine useEffect(() => { isPlayingRef.current = isPlaying; }, [isPlaying]); - useEffect(() => { - isResetRef.current = isReset; - }, [isReset]); + useEffect(() => { - if (isReset || !isPlaying) { reset(); - setReset(false); startTimeRef.current = 0; isPausedRef.current = false; pauseTimeRef.current = 0; @@ -53,49 +49,53 @@ const MachineAnimator = ({ currentPhase, handleCallBack, processingTime, machine } }, [isReset, isPlaying]) + + useEffect(() => { if (currentPhase === 'processing' && !animationStarted.current && machineUuid) { animationStarted.current = true; startTimeRef.current = performance.now(); animationFrameId.current = requestAnimationFrame(step); } + }, [currentPhase]); 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); 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(); - } + } } + return null; } diff --git a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx index 714bcdb..b3d4498 100644 --- a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx +++ b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx @@ -10,10 +10,10 @@ function MachineInstance({ machineDetail }: any) { const { machines, addCurrentAction, setMachineState, setMachineActive } = useMachineStore(); const reset = () => { + setCurrentPhase("idle"); setMachineState(machineDetail.modelUuid, 'idle'); setMachineActive(machineDetail.modelUuid, false); isIncrememtable.current = true; - setCurrentPhase("idle"); } const increment = () => { if (isIncrememtable.current) { @@ -31,7 +31,7 @@ function MachineInstance({ machineDetail }: any) { if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && !machineDetail.currentAction) { setTimeout(() => { increment(); - }, 2000); + }, 5000); machineStatus(machineDetail.modelUuid, 'Machine is idle and waiting for next instruction.') } else if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && machineDetail.currentAction) { setCurrentPhase("processing"); @@ -39,8 +39,6 @@ function MachineInstance({ machineDetail }: any) { setMachineActive(machineDetail.modelUuid, true); machineStatus(machineDetail.modelUuid, "Machine started processing") } - } else { - reset(); } }, [currentPhase, isPlaying, machines]) @@ -60,6 +58,7 @@ function MachineInstance({ machineDetail }: any) { return ( <> + ) } diff --git a/app/src/modules/simulation/materials/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/materials/instances/animator/materialAnimator.tsx index 07c0ee0..c0a021d 100644 --- a/app/src/modules/simulation/materials/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/materials/instances/animator/materialAnimator.tsx @@ -1,26 +1,35 @@ import React, { useEffect, useState, useRef } from 'react'; import * as THREE from 'three'; import { useFrame, useThree } from '@react-three/fiber'; +import { usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; interface MaterialAnimatorProps { matRef: React.RefObject; material: MaterialSchema; - speed: number; // units per second + currentSpeed: number; onAnimationComplete?: () => void; } function MaterialAnimator({ matRef, material, - speed, + currentSpeed, onAnimationComplete }: MaterialAnimatorProps) { const { scene } = useThree(); const [targetPosition, setTargetPosition] = useState(null); const [isAnimating, setIsAnimating] = useState(false); - const animationStartTime = useRef(0); - const startPosition = useRef(new THREE.Vector3()); - const totalDistance = useRef(0); + const animationState = useRef({ + startTime: 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 obj = scene.getObjectByProperty('uuid', uuid); @@ -30,37 +39,58 @@ function MaterialAnimator({ return position; }; + // Handle target position changes and play state useEffect(() => { - if (!material.next?.pointUuid) { - setTargetPosition(null); + if (!isPlaying || !material.next?.pointUuid) { setIsAnimating(false); return; } const newTarget = getWorldPosition(material.next.pointUuid); if (newTarget && matRef.current) { - startPosition.current.copy(matRef.current.position); - totalDistance.current = startPosition.current.distanceTo(newTarget); - animationStartTime.current = performance.now(); + animationState.current.startPosition.copy(matRef.current.position); + animationState.current.totalDistance = animationState.current.startPosition.distanceTo(newTarget); + animationState.current.startTime = performance.now() - animationState.current.pausedTime; + animationState.current.pausedTime = 0; + animationState.current.isPaused = false; setTargetPosition(newTarget); 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(() => { - 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( - startPosition.current, + animationState.current.startPosition, targetPosition, progress ); - // Check if animation is complete if (progress >= 1) { matRef.current.position.copy(targetPosition); setIsAnimating(false); diff --git a/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx b/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx index 8e2706a..338a45c 100644 --- a/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx +++ b/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx @@ -5,12 +5,16 @@ import { useProductStore } from '../../../../../store/simulation/useProductStore import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; import { MaterialModel } from '../material/materialModel'; import { useThree } from '@react-three/fiber'; +import { useAnimationPlaySpeed } from '../../../../../store/usePlayButtonStore'; +import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; function MaterialInstance({ material }: { material: MaterialSchema }) { const matRef: any = useRef(); const { scene } = useThree(); - const { getModelUuidByPointUuid, getPointByUuid, getEventByModelUuid } = useProductStore(); + const { getModelUuidByPointUuid, getPointByUuid, getEventByModelUuid, getActionByUuid } = useProductStore(); const { selectedProduct } = useSelectedProduct(); + const { speed } = useAnimationPlaySpeed(); + const { triggerPointActions } = useTriggerHandler(); const getWorldPositionFromScene = (pointUuid: string): THREE.Vector3 | null => { const pointObj = scene.getObjectByProperty("uuid", pointUuid); @@ -21,32 +25,32 @@ function MaterialInstance({ material }: { material: MaterialSchema }) { return worldPosition; }; - const { position, rotation, speed } = useMemo(() => { + const { position, rotation, currentSpeed } = useMemo(() => { 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); 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); 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); 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 { position: new THREE.Vector3(...point.position), rotation: new THREE.Vector3(...point.rotation), - speed: speed || 1 + currentSpeed: currentSpeed || 1 }; }, [material, getPointByUuid]); @@ -82,16 +86,25 @@ function MaterialInstance({ material }: { material: MaterialSchema }) { // console.log('material: ', material); }, [material]) + const callTrigger = () => { + const action = getActionByUuid(selectedProduct.productId, material.current.actionUuid) + if (action) { + triggerPointActions(action); + } + } + return ( <> - + {material.isRendered && + + } { console.log('123');}} + currentSpeed={currentSpeed * speed} + onAnimationComplete={() => { callTrigger() }} /> ) diff --git a/app/src/modules/simulation/materials/instances/materialInstances.tsx b/app/src/modules/simulation/materials/instances/materialInstances.tsx index 1864f0f..199e604 100644 --- a/app/src/modules/simulation/materials/instances/materialInstances.tsx +++ b/app/src/modules/simulation/materials/instances/materialInstances.tsx @@ -6,7 +6,7 @@ function MaterialInstances() { const { materials } = useMaterialStore(); useEffect(() => { - // console.log('materials: ', materials); + console.log('materials: ', materials); }, [materials]) return ( diff --git a/app/src/modules/simulation/materials/materials.tsx b/app/src/modules/simulation/materials/materials.tsx index 432d815..06ed353 100644 --- a/app/src/modules/simulation/materials/materials.tsx +++ b/app/src/modules/simulation/materials/materials.tsx @@ -1,11 +1,27 @@ -import React from 'react' +import React, { useEffect } from 'react' import MaterialInstances from './instances/materialInstances' +import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore'; +import { useMaterialStore } from '../../../store/simulation/useMaterialStore'; function Materials() { + const { clearMaterials } = useMaterialStore(); + const { isPlaying } = usePlayButtonStore(); + const { isReset } = useResetButtonStore(); + + useEffect(() => { + if (isReset || !isPlaying) { + clearMaterials(); + } + }, [isReset, isPlaying]); + return ( <> - + {isPlaying && + + + + } ) diff --git a/app/src/modules/simulation/roboticArm/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/materialAnimator.tsx new file mode 100644 index 0000000..893f759 --- /dev/null +++ b/app/src/modules/simulation/roboticArm/instances/animator/materialAnimator.tsx @@ -0,0 +1,66 @@ +import { useFrame } from '@react-three/fiber'; +import React, { useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; +import { useLogger } from '../../../../../components/ui/log/LoggerContext'; + +type MaterialAnimatorProps = { + ikSolver: any; + armBot: any; + currentPhase: string; +}; + +export default function MaterialAnimator({ ikSolver, armBot, currentPhase }: MaterialAnimatorProps) { + const sphereRef = useRef(null); + const boneWorldPosition = new THREE.Vector3(); + const [isRendered, setIsRendered] = useState(false); + + useEffect(() => { + if (currentPhase === "start-to-end") { + setIsRendered(true); + } else { + setIsRendered(false); + } + }, [currentPhase]); + + useFrame(() => { + if (!ikSolver || !sphereRef.current) return; + + const boneTarget = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === "Bone004"); + const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === "Effector"); + + if (!boneTarget || !bone) return; + + if (currentPhase === "start-to-end") { + // Get world positions + const boneTargetWorldPos = new THREE.Vector3(); + const boneWorldPos = new THREE.Vector3(); + boneTarget.getWorldPosition(boneTargetWorldPos); + bone.getWorldPosition(boneWorldPos); + + // Calculate direction + const direction = new THREE.Vector3(); + direction.subVectors(boneWorldPos, boneTargetWorldPos).normalize(); + const downwardDirection = direction.clone().negate(); + const adjustedPosition = boneWorldPos.clone().addScaledVector(downwardDirection, -0.25); + + //set position + sphereRef.current.position.copy(adjustedPosition); + + // Set rotation + const worldQuaternion = new THREE.Quaternion(); + bone.getWorldQuaternion(worldQuaternion); + sphereRef.current.quaternion.copy(worldQuaternion); + } + }); + + return ( + <> + {isRendered && ( + + + + + )} + + ); +} diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index fd649c6..34f191b 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -15,7 +15,6 @@ function RoboticArmAnimator({ ikSolver, targetBone, armBot, - logStatus, path }: any) { const progressRef = useRef(0); @@ -30,9 +29,11 @@ function RoboticArmAnimator({ // Zustand stores const { isPlaying } = usePlayButtonStore(); const { isPaused } = usePauseButtonStore(); - const { isReset, setReset } = useResetButtonStore(); + const { isReset } = useResetButtonStore(); const { speed } = useAnimationPlaySpeed(); + const CIRCLE_RADIUS = 1.6 + // Update path state whenever `path` prop changes useEffect(() => { setCurrentPath(path); @@ -40,13 +41,13 @@ function RoboticArmAnimator({ // Handle circle points based on armBot position useEffect(() => { - const points = generateRingPoints(1.6, 64) + const points = generateRingPoints(CIRCLE_RADIUS, 64) setCirclePoints(points); }, [armBot.position]); //Handle Reset Animation useEffect(() => { - if (isReset ) { + if (isReset) { progressRef.current = 0; curveRef.current = null; setCurrentPath([]); @@ -98,7 +99,7 @@ function RoboticArmAnimator({ return distance < nearestDistance ? point : nearest; }, circlePoints[0]); }; - + // Handle nearest points and final path (including arc points) useEffect(() => { if (circlePoints.length > 0 && currentPath.length > 0) { @@ -109,14 +110,6 @@ function RoboticArmAnimator({ const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number]; const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number]; - // const findNearest = (target: [number, number, number]) => { - // return circlePoints.reduce((nearest, point) => { - // const distance = Math.hypot(target[0] - point[0], target[1] - point[1], target[2] - point[2]); - // const nearestDistance = Math.hypot(target[0] - nearest[0], target[1] - nearest[1], target[2] - nearest[2]); - // return distance < nearestDistance ? point : nearest; - // }, circlePoints[0]); - // }; - const nearestToStart = findNearest(raisedStart); const nearestToEnd = findNearest(raisedEnd); @@ -187,6 +180,7 @@ function RoboticArmAnimator({ if (!ikSolver) return; const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone); + if (!bone) return; if (isPlaying) { @@ -252,7 +246,7 @@ function RoboticArmAnimator({ )} - + diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index c67d6f6..21bbc7e 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -3,12 +3,11 @@ import IKInstance from '../ikInstance/ikInstance'; import RoboticArmAnimator from '../animator/roboticArmAnimator'; import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; -import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_4.glb"; +import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_1.glb"; import { useThree } from "@react-three/fiber"; import useModuleStore from '../../../../../store/useModuleStore'; import * as THREE from "three"; -import { useSelectedAction, useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; -import { useProductStore } from '../../../../../store/simulation/useProductStore'; +import MaterialAnimator from '../animator/materialAnimator'; function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { @@ -25,13 +24,11 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { let startTime: number; //zustand const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); - const { getActionByUuid } = useProductStore(); - const { selectedProduct } = useSelectedProduct(); const { activeModule } = useModuleStore(); const { isPlaying } = usePlayButtonStore(); const { isReset, setReset } = useResetButtonStore(); const { isPaused } = usePauseButtonStore(); - const { selectedAction } = useSelectedAction(); + function firstFrame() { startTime = performance.now(); @@ -114,7 +111,6 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.") } } - // setReset(false); } }, [isReset, isPlaying]) @@ -123,13 +119,10 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { if (targetMesh) { targetMesh.visible = activeModule !== "simulation" } - const targetBones = ikSolver?.mesh.skeleton.bones.find( - (b: any) => b.name === targetBone - ); + const targetBones = ikSolver?.mesh.skeleton.bones.find((b: any) => b.name === targetBone); if (isPlaying) { //Moving armBot from initial point to rest position. if (!armBot?.isActive && armBot?.state == "idle" && currentPhase == "init") { - setArmBotActive(armBot.modelUuid, true) setArmBotState(armBot.modelUuid, "running") setCurrentPhase("init-to-rest"); @@ -167,52 +160,10 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { } else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "picking" && armBot.currentAction) { requestAnimationFrame(firstFrame); - // setArmBotActive(armBot.modelUuid, true); - // setArmBotState(armBot.modelUuid, "running"); - // setCurrentPhase("start-to-end"); - // const startPoint = armBot.point.actions[0].process.startPoint; - // const endPoint = armBot.point.actions[0].process.endPoint; - // if (startPoint && endPoint) { - // let curve = createCurveBetweenTwoPoints( - // new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]), - // new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2])); - // if (curve) { - // setTimeout(() => { - // logStatus(armBot.modelUuid, "picking the object"); - // setPath(curve.points.map(point => [point.x, point.y, point.z])); - // }, 1500) - // } - // } - // logStatus(armBot.modelUuid, "Moving armBot from start point to end position.") } else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "dropping" && armBot.currentAction) { requestAnimationFrame(firstFrame); - // setArmBotActive(armBot.modelUuid, true); - // setArmBotState(armBot.modelUuid, "running"); - // setCurrentPhase("end-to-rest"); - // const endPoint = armBot.point.actions[0].process.endPoint; - // if (endPoint) { - // let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition); - // if (curve) { - // setTimeout(() => { - // logStatus(armBot.modelUuid, "dropping the object"); - // setPath(curve.points.map(point => [point.x, point.y, point.z])); - // }, 1500) - // } - // } - // logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.") } - } else { - // logStatus(armBot.modelUuid, "Simulation Play Exited") - // setArmBotActive(armBot.modelUuid, false) - // setArmBotState(armBot.modelUuid, "idle") - // setCurrentPhase("init"); - // setPath([]) - // isPausedRef.current = false - // pauseTimeRef.current = null - // isPausedRef.current = false - // startTime = 0 - // removeCurrentAction(armBot.modelUuid) } }, [currentPhase, armBot, isPlaying, ikSolver]) @@ -258,10 +209,6 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { } } const logStatus = (id: string, status: string) => { - - - // - } return ( @@ -269,6 +216,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { + ) } diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx index c090a92..be41a95 100644 --- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx @@ -57,6 +57,12 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKIns rotationMin: new THREE.Vector3(0, 0, 0), rotationMax: new THREE.Vector3(2, 0, 0), }, + // { + // index: 1, + // enabled: true, + // rotationMin: new THREE.Vector3(0, -Math.PI, 0), + // rotationMax: new THREE.Vector3(0, Math.PI, 0), + // }, { index: 1, enabled: true, limitation: new THREE.Vector3(0, 1, 0) }, { index: 0, enabled: false, limitation: new THREE.Vector3(0, 0, 0) }, ], diff --git a/app/src/modules/simulation/simulator/simulator.tsx b/app/src/modules/simulation/simulator/simulator.tsx index 320760f..c271286 100644 --- a/app/src/modules/simulation/simulator/simulator.tsx +++ b/app/src/modules/simulation/simulator/simulator.tsx @@ -1,21 +1,23 @@ import { useEffect } from 'react'; import { useProductStore } from '../../../store/simulation/useProductStore'; import { useActionHandler } from '../actions/useActionHandler'; +import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore'; function Simulator() { const { products } = useProductStore(); const { handleAction } = useActionHandler(); + const { isPlaying } = usePlayButtonStore(); + const { isReset } = useResetButtonStore(); useEffect(() => { + if (!isPlaying || isReset) return; + const executionOrder = determineExecutionOrder(products); - executionOrder.forEach(point => { - if ('actions' in point) { - handleAction(point.actions[0]); - } else { - handleAction(point.action); - } + executionOrder.map(point => { + const action = 'actions' in point ? point.actions[0] : point.action; + handleAction(action); }); - }, [products, handleAction]); + }, [products, handleAction, isPlaying, isReset]); function determineExecutionOrder(products: productsSchema): PointsScheme[] { // Create maps for all events and points diff --git a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx index f9fc30f..6afcc69 100644 --- a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx +++ b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx @@ -10,6 +10,7 @@ import { handleAddEventToProduct } from "../../events/points/functions/handleAdd import { QuadraticBezierLine } from "@react-three/drei"; import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; import { useDeleteTool } from "../../../../store/store"; +import { usePlayButtonStore } from "../../../../store/usePlayButtonStore"; interface ConnectionLine { id: string; @@ -29,6 +30,7 @@ function TriggerConnector() { const [helperlineColor, setHelperLineColor] = useState("red"); const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3; end: THREE.Vector3; mid: THREE.Vector3; } | null>(null); const { deleteTool } = useDeleteTool(); + const { isPlaying } = usePlayButtonStore(); const [firstSelectedPoint, setFirstSelectedPoint] = useState<{ productId: string; @@ -424,7 +426,7 @@ function TriggerConnector() { }; return ( - + {connections.map((connection) => { const startPoint = getWorldPositionFromScene(connection.startPointUuid); const endPoint = getWorldPositionFromScene(connection.endPointUuid); diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index e29f9eb..7541522 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -1,13 +1,128 @@ import { useCallback, useEffect, useRef } from 'react'; import { useActionHandler } from '../../actions/useActionHandler'; import { useProductStore } from '../../../../store/simulation/useProductStore'; +import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; +import { useMaterialStore } from '../../../../store/simulation/useMaterialStore'; export function useTriggerHandler() { - const { getActionByUuid } = useProductStore(); + const { getActionByUuid, getEventByTriggerUuid, getEventByModelUuid } = useProductStore(); 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) => { @@ -18,7 +133,7 @@ export function useTriggerHandler() { case 'onStart': break; case 'onComplete': - handleTrigger(trigger); + handleTrigger(trigger, action.actionUuid); break; case 'onStop': break; diff --git a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts index ab89507..dae90ee 100644 --- a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts +++ b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts @@ -138,7 +138,7 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { targetPosition.x = centerX + Math.cos(angle) * clampedDistance; targetPosition.z = centerZ + Math.sin(angle) * clampedDistance; } - targetPosition.y = Math.min(Math.max(targetPosition.y, 0.6), 1.5); + targetPosition.y = Math.min(Math.max(targetPosition.y, 0.6), 1.9); // Convert world position to local if object is nested inside a parent if (parent) { parent.worldToLocal(targetPosition); diff --git a/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx new file mode 100644 index 0000000..4b9eb1c --- /dev/null +++ b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx @@ -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(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 && ( + <> + + + + {agvDetail.currentLoad} + + + + )} + + ); +}; + + +export default MaterialAnimator; diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index 70c3e9d..460997a 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -199,7 +199,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai return ( <> {currentPath.length > 0 && ( - <> + {currentPath.map((point, index) => ( @@ -207,7 +207,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai ))} - + )} ); diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 412a4ba..d0e2afd 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -5,11 +5,13 @@ import { NavMeshQuery } from '@recast-navigation/core'; import { useNavMesh } from '../../../../../store/store'; import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; +import MaterialAnimator from '../animator/materialAnimator'; function VehicleInstance({ agvDetail }: any) { const { navMesh } = useNavMesh(); + const vehicleRef: any = useRef(); const { isPlaying } = usePlayButtonStore(); - const { vehicles, setVehicleActive, setVehicleState, incrementVehicleLoad } = useVehicleStore(); + const { vehicles, setVehicleActive, setVehicleState, incrementVehicleLoad, setMaterialType } = useVehicleStore(); const [currentPhase, setCurrentPhase] = useState('stationed'); const [path, setPath] = useState<[number, number, number][]>([]); let isIncrememtable = useRef(true); @@ -44,8 +46,8 @@ function VehicleInstance({ agvDetail }: any) { const increment = () => { if (isIncrememtable.current) { - incrementVehicleLoad(agvDetail.modelUuid, 10); + setMaterialType(agvDetail.modelUuid, 'Material 1') isIncrememtable.current = false; } } @@ -71,7 +73,7 @@ function VehicleInstance({ agvDetail }: any) { }, 5000); - if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) { + if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity && agvDetail.materialType) { const toDrop = computePath( agvDetail.point.action.pickUpPoint.position, agvDetail.point.action.unLoadPoint.position @@ -118,6 +120,7 @@ function VehicleInstance({ agvDetail }: any) { setVehicleState(agvDetail.modelUuid, 'idle'); setVehicleActive(agvDetail.modelUuid, false); setPath([]); + setMaterialType(agvDetail.modelUuid, null) vehicleStatus(agvDetail.modelUuid, 'Reached pickup point again, cycle complete'); } } @@ -132,6 +135,7 @@ function VehicleInstance({ agvDetail }: any) { agvDetail={agvDetail} reset={reset} /> + ); } diff --git a/app/src/store/simulation/useMaterialStore.ts b/app/src/store/simulation/useMaterialStore.ts index ba75460..ec63ff3 100644 --- a/app/src/store/simulation/useMaterialStore.ts +++ b/app/src/store/simulation/useMaterialStore.ts @@ -6,6 +6,7 @@ type MaterialsStore = { addMaterial: (material: MaterialSchema) => MaterialSchema | undefined; removeMaterial: (materialId: string) => MaterialSchema | undefined; + clearMaterials: () => void; updateMaterial: (materialId: string, updates: Partial) => MaterialSchema | undefined; setCurrentLocation: ( @@ -35,6 +36,7 @@ type MaterialsStore = { setIsRendered: (materialId: string, isRendered: boolean) => MaterialSchema | undefined; getMaterialById: (materialId: string) => MaterialSchema | undefined; + getMaterialByCurrentModelUuid: (currentModelUuid: string) => MaterialSchema | undefined; getMaterialsByPoint: (pointUuid: string) => MaterialSchema[]; getMaterialsByModel: (modelUuid: string) => MaterialSchema[]; }; @@ -63,6 +65,12 @@ export const useMaterialStore = create()( return updatedMaterial; }, + clearMaterials: () => { + set((state) => { + state.materials = []; + }); + }, + updateMaterial: (materialId, updates) => { let updatedMaterial: MaterialSchema | undefined; set((state) => { @@ -186,6 +194,10 @@ export const useMaterialStore = create()( getMaterialById: (materialId) => { return get().materials.find(m => m.materialId === materialId); }, + + getMaterialByCurrentModelUuid: (currentModelUuid) => { + return get().materials.find(m => m.current?.modelUuid === currentModelUuid); + }, getMaterialsByPoint: (pointUuid) => { return get().materials.filter(m => diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index bf99007..cdbc26c 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -61,6 +61,8 @@ type ProductsStore = { // Helper functions getProductById: (productId: string) => { productName: string; productId: string; eventDatas: 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; getActionByUuid: (productId: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined; getModelUuidByPointUuid: (productId: string, actionUuid: string) => (string) | undefined; @@ -540,6 +542,53 @@ export const useProductStore = create()( 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) => { const event = get().getEventByModelUuid(productId, modelUuid); if (!event) return undefined;