diff --git a/app/src/modules/scene/controls/selectionControls/moveControls.tsx b/app/src/modules/scene/controls/selectionControls/moveControls.tsx index 3084952..5b33c14 100644 --- a/app/src/modules/scene/controls/selectionControls/moveControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/moveControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; -import { useEffect, useMemo, useRef } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSocketStore, useStartSimulation, useToggleView } from "../../../../store/store"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import { toast } from "react-toastify"; import * as Types from "../../../../types/world/worldTypes"; @@ -10,6 +10,7 @@ import { useEventsStore } from "../../../../store/simulation/useEventsStore"; import { useProductStore } from "../../../../store/simulation/useProductStore"; import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; +import { snapControls } from "../../../../utils/handleSnap"; function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); @@ -21,6 +22,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); const itemsData = useRef([]); + const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("") const email = localStorage.getItem('email') const organization = (email!.split("@")[1]).split(".")[0]; @@ -54,6 +56,15 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje const onPointerMove = () => { isMoving = true; }; + const onKeyUp = (event: KeyboardEvent) => { + // When any modifier is released, reset snap + const isModifierKey = + event.key === "Control" || event.key === "Shift"; + + if (isModifierKey) { + setKeyEvent(""); + } + }; const onPointerUp = (event: PointerEvent) => { if (!isMoving && movedObjects.length > 0 && event.button === 0) { @@ -75,18 +86,28 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje setMovedObjects([]); itemsData.current = []; } + setKeyEvent("") }; const onKeyDown = (event: KeyboardEvent) => { const keyCombination = detectModifierKeys(event); if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return; + + if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + // update state here + setKeyEvent(keyCombination) + } else { + setKeyEvent("") + } + if (keyCombination === "G") { if (selectedAssets.length > 0) { moveAssets(); itemsData.current = floorItems.filter((item: { modelUuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modelUuid)); } } + if (keyCombination === "ESCAPE") { event.preventDefault(); @@ -109,6 +130,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("keydown", onKeyDown); + canvasElement?.addEventListener("keyup", onKeyUp); } return () => { @@ -116,12 +138,11 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); + canvasElement?.removeEventListener("keyup", onKeyUp); }; - }, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects]); + }, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent]); - const gridSize = 0.25; - const moveSpeed = 0.25; - const isGridSnap = false; + let moveSpeed = keyEvent === "Ctrl" || "Ctrl+Shift" ? 1 : 0.25; useFrame(() => { if (movedObjects.length > 0) { @@ -132,10 +153,17 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje if (point) { let targetX = point.x; let targetZ = point.z; + if (keyEvent === "Ctrl") { + targetX = snapControls(targetX, "Ctrl"); + targetZ = snapControls(targetZ, "Ctrl"); + } else if (keyEvent === "Ctrl+Shift") { + targetX = snapControls(targetX, "Ctrl+Shift"); + targetZ = snapControls(targetZ, "Ctrl+Shift"); + } else if (keyEvent === "Shift") { + targetX = snapControls(targetX, "Shift"); + targetZ = snapControls(targetZ, "Shift"); + } else { - if (isGridSnap) { - targetX = Math.round(point.x / gridSize) * gridSize; - targetZ = Math.round(point.z / gridSize) * gridSize; } const position = new THREE.Vector3(); @@ -280,6 +308,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje setMovedObjects([]); setRotatedObjects([]); setSelectedAssets([]); + setKeyEvent("") } return null; diff --git a/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx b/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx new file mode 100644 index 0000000..6ef2b0e --- /dev/null +++ b/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx @@ -0,0 +1,102 @@ +import { useFrame } from '@react-three/fiber'; +import React, { useEffect, useRef } from 'react'; +import { useMachineStore } from '../../../../../store/simulation/useMachineStore'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; + + +interface MachineAnimatorProps { + currentPhase: string; + handleCallBack: () => void; + reset: () => void; + machineStatus: (modelId: string, status: string) => void; + processingTime: number; + machineUuid: string +} + +const MachineAnimator = ({ currentPhase, handleCallBack, processingTime, machineUuid, machineStatus, reset }: MachineAnimatorProps) => { + const animationStarted = useRef(false); + const isPausedRef = useRef(false); + const startTimeRef = useRef(0); + const animationFrameId = useRef(null); + const pauseTimeRef = useRef(null); + const { isPaused } = usePauseButtonStore(); + const { removeCurrentAction } = useMachineStore(); + const { isReset, setReset } = useResetButtonStore(); + const { isPlaying } = usePlayButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const isPlayingRef = useRef(false); + const isResetRef = useRef(false) + + + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); + 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; + animationFrameId.current = null; + animationStarted.current = false; + removeCurrentAction(machineUuid) + } + }, [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) { + 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; +} + +export default MachineAnimator; diff --git a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx index edb825f..714bcdb 100644 --- a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx +++ b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx @@ -1,8 +1,65 @@ -import React from 'react' +import React, { useEffect, useRef, useState } from 'react' +import { useMachineStore } from '../../../../../store/simulation/useMachineStore'; +import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import MachineAnimator from '../animator/machineAnimator'; + +function MachineInstance({ machineDetail }: any) { + const [currentPhase, setCurrentPhase] = useState('idle'); + let isIncrememtable = useRef(true); + const { isPlaying } = usePlayButtonStore(); + const { machines, addCurrentAction, setMachineState, setMachineActive } = useMachineStore(); + + const reset = () => { + setMachineState(machineDetail.modelUuid, 'idle'); + setMachineActive(machineDetail.modelUuid, false); + isIncrememtable.current = true; + setCurrentPhase("idle"); + } + const increment = () => { + if (isIncrememtable.current) { + addCurrentAction(machineDetail.modelUuid, "machine-action-2468-1357-8024") + isIncrememtable.current = false; + } + } + function machineStatus(modelId: string, status: string) { + // console.log(`${modelId} , ${status}`); + + } + + useEffect(() => { + if (isPlaying) { + if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && !machineDetail.currentAction) { + setTimeout(() => { + increment(); + }, 2000); + machineStatus(machineDetail.modelUuid, 'Machine is idle and waiting for next instruction.') + } else if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && machineDetail.currentAction) { + setCurrentPhase("processing"); + setMachineState(machineDetail.modelUuid, 'running'); + setMachineActive(machineDetail.modelUuid, true); + machineStatus(machineDetail.modelUuid, "Machine started processing") + } + } else { + reset(); + } + }, [currentPhase, isPlaying, machines]) + + function handleCallBack() { + if (currentPhase == "processing") { + setMachineState(machineDetail.modelUuid, 'idle'); + setMachineActive(machineDetail.modelUuid, false); + setCurrentPhase("idle") + isIncrememtable.current = true; + machineStatus(machineDetail.modelUuid, "Machine has completed the processing") + } + } + // console.log('currentPhase: ', currentPhase); + + -function MachineInstance() { return ( <> + ) } diff --git a/app/src/modules/simulation/machine/instances/machineInstances.tsx b/app/src/modules/simulation/machine/instances/machineInstances.tsx index b0c2c9f..8536cac 100644 --- a/app/src/modules/simulation/machine/instances/machineInstances.tsx +++ b/app/src/modules/simulation/machine/instances/machineInstances.tsx @@ -1,11 +1,14 @@ import React from 'react' import MachineInstance from './machineInstance/machineInstance' +import { useMachineStore } from '../../../../store/simulation/useMachineStore'; function MachineInstances() { + const { machines } = useMachineStore(); return ( <> - - + {machines.map((val: MachineStatus) => ( + + ))} ) diff --git a/app/src/modules/simulation/machine/machine.tsx b/app/src/modules/simulation/machine/machine.tsx index e9d2dea..3d24f61 100644 --- a/app/src/modules/simulation/machine/machine.tsx +++ b/app/src/modules/simulation/machine/machine.tsx @@ -4,7 +4,7 @@ import { useMachineStore } from '../../../store/simulation/useMachineStore' import { useSelectedProduct } from '../../../store/simulation/useSimulationStore'; function Machine() { - const { addMachine, addCurrentAction, removeMachine } = useMachineStore(); + const { addMachine, addCurrentAction, removeMachine, machines } = useMachineStore(); const { selectedProduct } = useSelectedProduct(); const machineSample: MachineEventSchema[] = [ @@ -38,6 +38,12 @@ function Machine() { // addCurrentAction(machineSample[0].modelUuid, machineSample[0].point.action.actionUuid); }, []) + + useEffect(() => { + + // console.log('machines: ', machines); + }, [machines]) + return ( <> diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index 9ca7355..aeadc4b 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -25,9 +25,9 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai const completedRef = useRef(false); const isPausedRef = useRef(false); const pauseTimeRef = useRef(null); + const [progress, setProgress] = useState(0); const [restRotation, setRestingRotation] = useState(true); const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); - const [progress, setProgress] = useState(0); const { scene } = useThree(); let startTime: number; let fixedInterval: number; @@ -66,6 +66,8 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai setReset(false); setRestingRotation(true); decrementVehicleLoad(agvDetail.modelUuid, 0); + isPausedRef.current = false; + pauseTimeRef.current = 0; const object = scene.getObjectByProperty('uuid', agvUuid); if (object) { object.position.set(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]); diff --git a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx index 91111cf..fcc840d 100644 --- a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx +++ b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx @@ -9,10 +9,8 @@ function VehicleInstances() { return ( <> - {vehicles.map((val: any, i: any) => - - - + {vehicles.map((val: VehicleStatus) => + )} diff --git a/app/src/utils/handleSnap.ts b/app/src/utils/handleSnap.ts new file mode 100644 index 0000000..bd6a74d --- /dev/null +++ b/app/src/utils/handleSnap.ts @@ -0,0 +1,22 @@ +export function snapControls(value: number, event: string): number { + const CTRL_DISTANCE = 1; // Snap to whole numbers when Ctrl is pressed + const SHIFT_DISTANCE = 0.01; // Snap to half-step increments when Shift is pressed + const CTRL_SHIFT_DISTANCE = 0.1; // Snap to fine increments when both Ctrl and Shift are pressed + + switch (event) { + case "Ctrl": + return Math.round(value / CTRL_DISTANCE) * CTRL_DISTANCE; + + case "Shift": + return Math.round(value / SHIFT_DISTANCE) * SHIFT_DISTANCE; + + case "Ctrl+Shift": + const base = Math.floor(value / CTRL_DISTANCE) * CTRL_DISTANCE; + const offset = + Math.round((value - base) / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE; + return base + offset; + + default: + return value; // No snapping if no modifier key is pressed + } +}