Merge remote-tracking branch 'origin/simulation-agv-v2' into v2

This commit is contained in:
Jerald-Golden-B 2025-05-03 10:12:02 +05:30
commit 9233bb97c8
8 changed files with 238 additions and 19 deletions

View File

@ -1,7 +1,7 @@
import * as THREE from "three"; 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 { 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 { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes"; import * as Types from "../../../../types/world/worldTypes";
@ -10,6 +10,7 @@ import { useEventsStore } from "../../../../store/simulation/useEventsStore";
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 { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; 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) { function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const { camera, controls, gl, scene, pointer, raycaster } = useThree();
@ -21,6 +22,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
const { floorItems, setFloorItems } = useFloorItems(); const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore(); const { socket } = useSocketStore();
const itemsData = useRef<Types.FloorItems>([]); const itemsData = useRef<Types.FloorItems>([]);
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("")
const email = localStorage.getItem('email') const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0]; const organization = (email!.split("@")[1]).split(".")[0];
@ -54,6 +56,15 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
const onPointerMove = () => { const onPointerMove = () => {
isMoving = true; 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) => { const onPointerUp = (event: PointerEvent) => {
if (!isMoving && movedObjects.length > 0 && event.button === 0) { if (!isMoving && movedObjects.length > 0 && event.button === 0) {
@ -75,18 +86,28 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
setMovedObjects([]); setMovedObjects([]);
itemsData.current = []; itemsData.current = [];
} }
setKeyEvent("")
}; };
const onKeyDown = (event: KeyboardEvent) => { const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event); const keyCombination = detectModifierKeys(event);
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return; 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 (keyCombination === "G") {
if (selectedAssets.length > 0) { if (selectedAssets.length > 0) {
moveAssets(); moveAssets();
itemsData.current = floorItems.filter((item: { modelUuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modelUuid)); itemsData.current = floorItems.filter((item: { modelUuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modelUuid));
} }
} }
if (keyCombination === "ESCAPE") { if (keyCombination === "ESCAPE") {
event.preventDefault(); event.preventDefault();
@ -109,6 +130,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown); canvasElement.addEventListener("keydown", onKeyDown);
canvasElement?.addEventListener("keyup", onKeyUp);
} }
return () => { return () => {
@ -116,12 +138,11 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown); 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; let moveSpeed = keyEvent === "Ctrl" || "Ctrl+Shift" ? 1 : 0.25;
const moveSpeed = 0.25;
const isGridSnap = false;
useFrame(() => { useFrame(() => {
if (movedObjects.length > 0) { if (movedObjects.length > 0) {
@ -132,10 +153,17 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
if (point) { if (point) {
let targetX = point.x; let targetX = point.x;
let targetZ = point.z; 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(); const position = new THREE.Vector3();
@ -280,6 +308,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
setMovedObjects([]); setMovedObjects([]);
setRotatedObjects([]); setRotatedObjects([]);
setSelectedAssets([]); setSelectedAssets([]);
setKeyEvent("")
} }
return null; return null;

View File

@ -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<boolean>(false);
const isPausedRef = useRef<boolean>(false);
const startTimeRef = useRef<number>(0);
const animationFrameId = useRef<number | null>(null);
const pauseTimeRef = useRef<number | null>(null);
const { isPaused } = usePauseButtonStore();
const { removeCurrentAction } = useMachineStore();
const { isReset, setReset } = useResetButtonStore();
const { isPlaying } = usePlayButtonStore();
const { speed } = useAnimationPlaySpeed();
const isPlayingRef = useRef<boolean>(false);
const isResetRef = useRef<boolean>(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;

View File

@ -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<string>('idle');
let isIncrememtable = useRef<boolean>(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 ( return (
<> <>
<MachineAnimator processingTime={machineDetail.point.action.processTime} handleCallBack={handleCallBack} currentPhase={currentPhase} machineUuid={machineDetail.modelUuid} machineStatus={machineStatus} reset={reset} />
</> </>
) )
} }

View File

@ -1,11 +1,14 @@
import React from 'react' import React from 'react'
import MachineInstance from './machineInstance/machineInstance' import MachineInstance from './machineInstance/machineInstance'
import { useMachineStore } from '../../../../store/simulation/useMachineStore';
function MachineInstances() { function MachineInstances() {
const { machines } = useMachineStore();
return ( return (
<> <>
{machines.map((val: MachineStatus) => (
<MachineInstance /> <MachineInstance key={val.modelUuid} machineDetail={val} />
))}
</> </>
) )

View File

@ -4,7 +4,7 @@ import { useMachineStore } from '../../../store/simulation/useMachineStore'
import { useSelectedProduct } from '../../../store/simulation/useSimulationStore'; import { useSelectedProduct } from '../../../store/simulation/useSimulationStore';
function Machine() { function Machine() {
const { addMachine, addCurrentAction, removeMachine } = useMachineStore(); const { addMachine, addCurrentAction, removeMachine, machines } = useMachineStore();
const { selectedProduct } = useSelectedProduct(); const { selectedProduct } = useSelectedProduct();
const machineSample: MachineEventSchema[] = [ const machineSample: MachineEventSchema[] = [
@ -38,6 +38,12 @@ function Machine() {
// addCurrentAction(machineSample[0].modelUuid, machineSample[0].point.action.actionUuid); // addCurrentAction(machineSample[0].modelUuid, machineSample[0].point.action.actionUuid);
}, []) }, [])
useEffect(() => {
// console.log('machines: ', machines);
}, [machines])
return ( return (
<> <>

View File

@ -25,9 +25,9 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
const completedRef = useRef<boolean>(false); const completedRef = useRef<boolean>(false);
const isPausedRef = useRef<boolean>(false); const isPausedRef = useRef<boolean>(false);
const pauseTimeRef = useRef<number | null>(null); const pauseTimeRef = useRef<number | null>(null);
const [progress, setProgress] = useState<number>(0);
const [restRotation, setRestingRotation] = useState<boolean>(true); const [restRotation, setRestingRotation] = useState<boolean>(true);
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
const [progress, setProgress] = useState<number>(0);
const { scene } = useThree(); const { scene } = useThree();
let startTime: number; let startTime: number;
let fixedInterval: number; let fixedInterval: number;
@ -66,6 +66,8 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
setReset(false); setReset(false);
setRestingRotation(true); setRestingRotation(true);
decrementVehicleLoad(agvDetail.modelUuid, 0); decrementVehicleLoad(agvDetail.modelUuid, 0);
isPausedRef.current = false;
pauseTimeRef.current = 0;
const object = scene.getObjectByProperty('uuid', agvUuid); const object = scene.getObjectByProperty('uuid', agvUuid);
if (object) { if (object) {
object.position.set(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]); object.position.set(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]);

View File

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

View File

@ -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
}
}