Merge remote-tracking branch 'origin/simulation' into simulation-animation
This commit is contained in:
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
import { useSimulationStates } from "../../../store/store";
|
||||
import * as SimulationTypes from '../../../types/simulation';
|
||||
import * as SimulationTypes from '../../../types/simulationTypes';
|
||||
import { ArmbotInstances } from "./ArmBotInstances";
|
||||
|
||||
interface ArmBotState {
|
||||
@@ -12,26 +12,47 @@ interface ArmBotState {
|
||||
status: string;
|
||||
material: string;
|
||||
triggerId: string;
|
||||
connections: any
|
||||
connections: {
|
||||
source: { modelUUID: string; pointUUID: string };
|
||||
targets: { modelUUID: string; pointUUID: string }[];
|
||||
};
|
||||
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
const ArmBot: React.FC = () => {
|
||||
interface StaticMachineState {
|
||||
uuid: string;
|
||||
status: string;
|
||||
actions: { uuid: string; name: string; buffer: number; material: string; };
|
||||
machineTriggerId: string;
|
||||
connectedArmBot: string;
|
||||
}
|
||||
|
||||
interface ArmBotProps {
|
||||
armBots: ArmBotState[];
|
||||
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
|
||||
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
|
||||
}
|
||||
|
||||
const ArmBot = ({ armBots, setArmBots, setStaticMachines }: ArmBotProps) => {
|
||||
const { activeModule } = useModuleStore();
|
||||
const { scene } = useThree();
|
||||
const { simulationStates } = useSimulationStates();
|
||||
const [armBots, setArmBots] = useState<ArmBotState[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = simulationStates.filter((s): s is SimulationTypes.ArmBotEventsSchema => s.type === "ArmBot");
|
||||
const initialStates: ArmBotState[] = filtered.map(bot => ({
|
||||
uuid: bot.modeluuid,
|
||||
position: bot.position,
|
||||
rotation: bot.rotation,
|
||||
status: "idle",
|
||||
material: "default",
|
||||
triggerId: '',
|
||||
connections: bot.points.connections
|
||||
}));
|
||||
const initialStates: ArmBotState[] = filtered
|
||||
.filter(bot => bot.points.connections.targets.length > 0)
|
||||
.map(bot => ({
|
||||
uuid: bot.modeluuid,
|
||||
position: bot.position,
|
||||
rotation: bot.rotation,
|
||||
status: "idle",
|
||||
material: "default",
|
||||
triggerId: '',
|
||||
actions: bot.points.actions,
|
||||
connections: bot.points.connections
|
||||
}));
|
||||
setArmBots(initialStates);
|
||||
}, [simulationStates]);
|
||||
|
||||
@@ -53,6 +74,7 @@ const ArmBot: React.FC = () => {
|
||||
index={i}
|
||||
armBot={bot}
|
||||
setArmBots={setArmBots}
|
||||
setStaticMachines={setStaticMachines}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import IkInstances from "./IkInstances";
|
||||
import armModel from "../../../assets/gltf-glb/rigged/ik_arm_4.glb";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { Vector3 } from "three";
|
||||
|
||||
interface Process {
|
||||
triggerId: string;
|
||||
startPoint?: Vector3;
|
||||
endPoint?: Vector3;
|
||||
speed: number;
|
||||
}
|
||||
|
||||
interface ArmBotState {
|
||||
uuid: string;
|
||||
@@ -8,18 +18,74 @@ interface ArmBotState {
|
||||
status: string;
|
||||
material: string;
|
||||
triggerId: string;
|
||||
connections: any
|
||||
connections: {
|
||||
source: { modelUUID: string; pointUUID: string };
|
||||
targets: { modelUUID: string; pointUUID: string }[];
|
||||
};
|
||||
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
interface StaticMachineState {
|
||||
uuid: string;
|
||||
status: string;
|
||||
actions: { uuid: string; name: string; buffer: number; material: string; };
|
||||
machineTriggerId: string;
|
||||
connectedArmBot: string;
|
||||
}
|
||||
|
||||
interface ArmbotInstancesProps {
|
||||
index: number;
|
||||
armBot: ArmBotState;
|
||||
setArmBots: (armBots: ArmBotState[]) => void;
|
||||
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
|
||||
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
|
||||
}
|
||||
|
||||
export const ArmbotInstances: React.FC<ArmbotInstancesProps> = ({ index, armBot, setArmBots }) => {
|
||||
export const ArmbotInstances: React.FC<ArmbotInstancesProps> = ({ index, armBot, setArmBots, setStaticMachines }) => {
|
||||
const { scene } = useThree();
|
||||
const [processes, setProcesses] = useState<Process[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (armBot.actions.processes.length > 0) {
|
||||
const mappedProcesses = armBot.actions.processes.map((process) => {
|
||||
return {
|
||||
triggerId: process.triggerId,
|
||||
startPoint: scene.getObjectByProperty('uuid', process.startPoint)?.getWorldPosition(new Vector3()),
|
||||
endPoint: scene.getObjectByProperty('uuid', process.endPoint)?.getWorldPosition(new Vector3()),
|
||||
speed: armBot.actions.speed
|
||||
};
|
||||
});
|
||||
setProcesses(mappedProcesses);
|
||||
} else {
|
||||
setProcesses([]);
|
||||
}
|
||||
}, [armBot, scene]);
|
||||
|
||||
const updateArmBotStatus = (status: string) => {
|
||||
setArmBots((prevArmBots) => {
|
||||
return prevArmBots.map(bot => {
|
||||
if (bot.uuid === armBot.uuid) {
|
||||
return { ...bot, status, triggerId: status === 'idle' ? '' : armBot.triggerId };
|
||||
}
|
||||
return bot;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<IkInstances key={index} modelUrl={armModel} position={armBot.position} rotation={armBot.rotation} />
|
||||
<IkInstances
|
||||
key={index}
|
||||
uuid={armBot.uuid}
|
||||
selectedTrigger={armBot.triggerId}
|
||||
modelUrl={armModel}
|
||||
position={armBot.position}
|
||||
rotation={armBot.rotation}
|
||||
processes={processes}
|
||||
armBot={armBot}
|
||||
setArmBots={setArmBots}
|
||||
setStaticMachines={setStaticMachines}
|
||||
updateArmBotStatus={updateArmBotStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,96 +1,338 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState, useRef } from "react";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
import { useSimulationStates } from "../../../store/store";
|
||||
|
||||
|
||||
interface StaticMachineState {
|
||||
uuid: string;
|
||||
status: string;
|
||||
actions: { uuid: string; name: string; buffer: number; material: string; };
|
||||
machineTriggerId: string;
|
||||
connectedArmBot: string;
|
||||
}
|
||||
|
||||
interface ArmBotState {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
status: string;
|
||||
material: string;
|
||||
triggerId: string;
|
||||
connections: {
|
||||
source: { modelUUID: string; pointUUID: string };
|
||||
targets: { modelUUID: string; pointUUID: string }[];
|
||||
};
|
||||
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
type IKAnimationControllerProps = {
|
||||
ikSolver: any;
|
||||
process: {
|
||||
triggerId: string;
|
||||
startPoint: THREE.Vector3;
|
||||
endPoint: THREE.Vector3;
|
||||
speed: number;
|
||||
}[];
|
||||
selectedTrigger: string;
|
||||
targetBoneName: string;
|
||||
uuid: string;
|
||||
logStatus: (status: string) => void;
|
||||
groupRef: React.RefObject<THREE.Group>;
|
||||
armBot: ArmBotState;
|
||||
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
|
||||
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
|
||||
updateArmBotStatus: (status: string) => void;
|
||||
}
|
||||
|
||||
const IKAnimationController = ({
|
||||
ikSolver,
|
||||
process,
|
||||
selectedTrigger,
|
||||
targetBoneName,
|
||||
}: {
|
||||
ikSolver: any;
|
||||
process: {
|
||||
trigger: string;
|
||||
start: THREE.Vector3;
|
||||
end: THREE.Vector3;
|
||||
speed: number;
|
||||
}[];
|
||||
selectedTrigger: string;
|
||||
targetBoneName: string;
|
||||
}) => {
|
||||
uuid,
|
||||
logStatus,
|
||||
groupRef,
|
||||
armBot,
|
||||
setArmBots,
|
||||
setStaticMachines,
|
||||
updateArmBotStatus
|
||||
}: IKAnimationControllerProps) => {
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [initialProgress, setInitialProgress] = useState(0);
|
||||
const [needsInitialMovement, setNeedsInitialMovement] = useState(true);
|
||||
const [isInitializing, setIsInitializing] = useState(true);
|
||||
const restSpeed = 0.1;
|
||||
const restPosition = new THREE.Vector3(0, 2, 1.6);
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { simulationStates } = useSimulationStates();
|
||||
|
||||
// Track previous states for comparison
|
||||
const prevStateRef = useRef({
|
||||
isInitializing: true,
|
||||
needsInitialMovement: true,
|
||||
selectedTrigger: "",
|
||||
progress: 0
|
||||
});
|
||||
|
||||
// Track previous status for comparison
|
||||
const prevStatusRef = useRef("");
|
||||
|
||||
const initialCurveRef = useRef<THREE.CatmullRomCurve3 | null>(null);
|
||||
const initialStartPositionRef = useRef<THREE.Vector3 | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setProgress(0);
|
||||
}, [selectedTrigger]);
|
||||
|
||||
const processedCurves = useMemo(() => {
|
||||
const restPosition = new THREE.Vector3(0.2, 2.3, 1.6);
|
||||
return process.map((p) => {
|
||||
const mid = new THREE.Vector3(
|
||||
(p.start.x + p.end.x) / 1,
|
||||
Math.max(p.start.y, p.end.y) + 0.8,
|
||||
(p.start.z + p.end.z) / 0.9
|
||||
useEffect(() => {
|
||||
if (ikSolver) {
|
||||
const targetBone = ikSolver.mesh.skeleton.bones.find(
|
||||
(b: any) => b.name === targetBoneName
|
||||
);
|
||||
const points = [
|
||||
restPosition.clone(),
|
||||
p.start.clone(),
|
||||
mid.clone(),
|
||||
p.end.clone(),
|
||||
restPosition.clone(),
|
||||
];
|
||||
const curve = new THREE.CatmullRomCurve3(points);
|
||||
const restToStartDist = points[0].distanceTo(points[1]);
|
||||
const startToEndDist = points[1].distanceTo(points[3]);
|
||||
const endToRestDist = points[3].distanceTo(points[4]);
|
||||
if (targetBone) {
|
||||
initialStartPositionRef.current = targetBone.position.clone();
|
||||
calculateInitialCurve(targetBone.position);
|
||||
logStatus(`[Arm ${uuid}] Initializing IK system, starting position: ${targetBone.position.toArray()}`);
|
||||
}
|
||||
}
|
||||
}, [ikSolver]);
|
||||
|
||||
const totalDist = restToStartDist + startToEndDist + endToRestDist;
|
||||
const restToStartRange = [0, restToStartDist / totalDist];
|
||||
const startToEndRange = [
|
||||
restToStartRange[1],
|
||||
restToStartRange[1] + startToEndDist / totalDist,
|
||||
];
|
||||
const endToRestRange = [startToEndRange[1], 1];
|
||||
// Log state changes
|
||||
useEffect(() => {
|
||||
const prev = prevStateRef.current;
|
||||
|
||||
return {
|
||||
trigger: p.trigger,
|
||||
curve,
|
||||
speed: p.speed,
|
||||
restToStartRange,
|
||||
startToEndRange,
|
||||
endToRestRange,
|
||||
};
|
||||
});
|
||||
}, [process]);
|
||||
if (prev.isInitializing !== isInitializing) {
|
||||
if (!isInitializing) {
|
||||
logStatus(`[Arm ${uuid}] Completed initialization, now at rest position`);
|
||||
}
|
||||
}
|
||||
|
||||
if (prev.needsInitialMovement !== needsInitialMovement && !needsInitialMovement) {
|
||||
logStatus(`[Arm ${uuid}] Reached rest position, ready for animation`);
|
||||
|
||||
}
|
||||
|
||||
if (prev.selectedTrigger !== selectedTrigger) {
|
||||
logStatus(`[Arm ${uuid}] Processing new trigger: ${selectedTrigger}`);
|
||||
|
||||
const currentProcess = process.find(p => p.triggerId === prev.selectedTrigger);
|
||||
if (currentProcess) {
|
||||
const triggerId = currentProcess.triggerId;
|
||||
|
||||
const endPoint = armBot.actions.processes.find((process) => process.triggerId === triggerId)?.endPoint;
|
||||
|
||||
// Search simulationStates for a StaticMachine or Conveyor that has a point matching this endPointId
|
||||
const matchedMachine = simulationStates.find((state) => {
|
||||
if (state.type === "Conveyor") {
|
||||
// For Conveyor, points is an array
|
||||
return (state).points.some(
|
||||
(point) => point.uuid === endPoint
|
||||
);
|
||||
} else if (state.type === "StaticMachine") {
|
||||
// For StaticMachine, points is an object
|
||||
return state.points.uuid === endPoint;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (matchedMachine) {
|
||||
// Log if the end point is a conveyor
|
||||
if (matchedMachine.type === "Conveyor") {
|
||||
logStatus(`[Arm ${uuid}] Reached end point which is a conveyor (${matchedMachine.modelName})`);
|
||||
} else {
|
||||
logStatus(`[Arm ${uuid}] Reached end point which is a static machine (${matchedMachine.modelName})`);
|
||||
}
|
||||
|
||||
if (matchedMachine.type === "StaticMachine") {
|
||||
setStaticMachines((machines) => {
|
||||
return machines.map((machine) => {
|
||||
if (machine.uuid === matchedMachine.modeluuid) {
|
||||
return { ...machine, status: "running" };
|
||||
} else {
|
||||
return machine;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (matchedMachine.type === "Conveyor") {
|
||||
setArmBots((prev) =>
|
||||
prev.map((arm) => {
|
||||
if (arm.uuid === uuid) {
|
||||
return {
|
||||
...arm,
|
||||
isActive: false
|
||||
};
|
||||
}
|
||||
else {
|
||||
return arm;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update previous state
|
||||
prevStateRef.current = {
|
||||
isInitializing,
|
||||
needsInitialMovement,
|
||||
selectedTrigger,
|
||||
progress
|
||||
};
|
||||
}, [isInitializing, needsInitialMovement, selectedTrigger, progress]);
|
||||
|
||||
const calculateInitialCurve = (startPosition: THREE.Vector3) => {
|
||||
const direction = new THREE.Vector3().subVectors(restPosition, startPosition);
|
||||
const distance = direction.length();
|
||||
direction.normalize();
|
||||
|
||||
const perpendicular = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
|
||||
const midHeight = 0.5;
|
||||
const tiltAmount = 1;
|
||||
const mid = new THREE.Vector3()
|
||||
.addVectors(startPosition, restPosition)
|
||||
.multiplyScalar(0.5)
|
||||
.add(perpendicular.clone().multiplyScalar(distance * 0.3 * tiltAmount))
|
||||
.add(new THREE.Vector3(0, midHeight, 0));
|
||||
|
||||
initialCurveRef.current = new THREE.CatmullRomCurve3([
|
||||
startPosition,
|
||||
new THREE.Vector3().lerpVectors(startPosition, mid, 0.33),
|
||||
mid,
|
||||
new THREE.Vector3().lerpVectors(mid, restPosition, 0.66),
|
||||
restPosition
|
||||
]);
|
||||
};
|
||||
|
||||
const processedCurves = useMemo(() => {
|
||||
if (isPlaying)
|
||||
return process.map((p) => {
|
||||
const tempLift = 0.5;
|
||||
const localStart = groupRef.current?.worldToLocal(p.startPoint.clone().add(new THREE.Vector3(0, tempLift, 0)));
|
||||
const localEnd = groupRef.current?.worldToLocal(p.endPoint.clone().add(new THREE.Vector3(0, tempLift, 0)));
|
||||
|
||||
if (localStart && localEnd) {
|
||||
|
||||
const mid = new THREE.Vector3(
|
||||
(localStart.x + localEnd.x) / 1,
|
||||
Math.max(localStart.y, localEnd.y) + 0.8,
|
||||
(localStart.z + localEnd.z) / 0.9
|
||||
);
|
||||
|
||||
const points = [
|
||||
restPosition.clone(),
|
||||
localStart.clone(),
|
||||
mid.clone(),
|
||||
localEnd.clone(),
|
||||
restPosition.clone(),
|
||||
];
|
||||
const curve = new THREE.CatmullRomCurve3(points);
|
||||
const restToStartDist = points[0].distanceTo(points[1]);
|
||||
const startToEndDist = points[1].distanceTo(points[3]);
|
||||
const endToRestDist = points[3].distanceTo(points[4]);
|
||||
|
||||
const totalDist = restToStartDist + startToEndDist + endToRestDist;
|
||||
const restToStartRange = [0, restToStartDist / totalDist];
|
||||
const startToEndRange = [
|
||||
restToStartRange[1],
|
||||
restToStartRange[1] + startToEndDist / totalDist,
|
||||
];
|
||||
const endToRestRange = [startToEndRange[1], 1];
|
||||
|
||||
return {
|
||||
trigger: p.triggerId,
|
||||
curve,
|
||||
speed: p.speed,
|
||||
restToStartRange,
|
||||
startToEndRange,
|
||||
endToRestRange,
|
||||
};
|
||||
}
|
||||
});
|
||||
}, [process, groupRef, isPlaying]);
|
||||
|
||||
const activeCurve = useMemo(() => {
|
||||
return processedCurves.find((c) => c.trigger === selectedTrigger);
|
||||
}, [processedCurves, selectedTrigger]);
|
||||
if (isPlaying && processedCurves)
|
||||
return processedCurves.find((c) => c?.trigger === selectedTrigger);
|
||||
}, [processedCurves, selectedTrigger, isPlaying]);
|
||||
|
||||
// Initial movement to rest position
|
||||
useFrame((_, delta) => {
|
||||
if (!ikSolver || !activeCurve) return;
|
||||
if (!ikSolver || !needsInitialMovement || !isInitializing || !initialCurveRef.current) return;
|
||||
|
||||
const { curve, speed, startToEndRange } = activeCurve;
|
||||
const targetBone = ikSolver.mesh.skeleton.bones.find(
|
||||
(b: any) => b.name === targetBoneName
|
||||
);
|
||||
if (!targetBone) return;
|
||||
|
||||
setInitialProgress((prev) => {
|
||||
const next = prev + delta * 0.5;
|
||||
if (next >= 1) {
|
||||
targetBone.position.copy(restPosition);
|
||||
setNeedsInitialMovement(false);
|
||||
setIsInitializing(false);
|
||||
return 1;
|
||||
}
|
||||
targetBone.position.copy(initialCurveRef.current!.getPoint(next));
|
||||
return next;
|
||||
});
|
||||
|
||||
ikSolver.update();
|
||||
});
|
||||
|
||||
// Main animation loop
|
||||
useFrame((_, delta) => {
|
||||
if (!ikSolver || !activeCurve || isInitializing || !isPlaying) return;
|
||||
|
||||
const { curve, speed, restToStartRange, startToEndRange, endToRestRange } = activeCurve;
|
||||
const targetBone = ikSolver.mesh.skeleton.bones.find(
|
||||
(b: any) => b.name === targetBoneName
|
||||
);
|
||||
if (!targetBone) return;
|
||||
|
||||
let currentSpeed = restSpeed;
|
||||
if (progress >= startToEndRange[0] && progress < startToEndRange[1]) {
|
||||
let currentStatus = "idle"; // Default status
|
||||
|
||||
// Determine current phase and status
|
||||
if (progress < restToStartRange[1]) {
|
||||
currentSpeed = restSpeed;
|
||||
currentStatus = "moving"; // Moving to start point
|
||||
} else if (progress >= startToEndRange[0] && progress < startToEndRange[1]) {
|
||||
currentSpeed = speed;
|
||||
currentStatus = "moving"; // Moving between points
|
||||
} else if (progress >= endToRestRange[0] && progress < 1) {
|
||||
currentSpeed = restSpeed;
|
||||
currentStatus = "moving"; // Returning to rest
|
||||
} else if (progress >= 1) {
|
||||
currentStatus = "idle"; // Completed cycle
|
||||
}
|
||||
|
||||
setProgress((prev) => {
|
||||
const next = prev + delta * currentSpeed;
|
||||
if (next >= 1) {
|
||||
targetBone.position.copy(curve.getPoint(1));
|
||||
return 1;
|
||||
}
|
||||
targetBone.position.copy(curve.getPoint(next));
|
||||
return next;
|
||||
});
|
||||
// Update status when it changes
|
||||
if (prevStatusRef.current !== currentStatus) {
|
||||
updateArmBotStatus(currentStatus);
|
||||
prevStatusRef.current = currentStatus;
|
||||
}
|
||||
|
||||
// Only update progress if we're not already at the end
|
||||
if (progress < 1) {
|
||||
setProgress((prev) => {
|
||||
const next = prev + delta * currentSpeed;
|
||||
return Math.min(next, 1); // Cap at 1
|
||||
});
|
||||
}
|
||||
|
||||
// Update bone position based on progress
|
||||
if (progress < 1) {
|
||||
targetBone.position.copy(curve.getPoint(progress));
|
||||
} else {
|
||||
targetBone.position.copy(curve.getPoint(1));
|
||||
}
|
||||
|
||||
ikSolver.update();
|
||||
});
|
||||
@@ -98,4 +340,4 @@ const IKAnimationController = ({
|
||||
return null;
|
||||
};
|
||||
|
||||
export default IKAnimationController;
|
||||
export default IKAnimationController;
|
||||
@@ -1,43 +1,70 @@
|
||||
import * as THREE from "three";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useLoader } from "@react-three/fiber";
|
||||
import { useFrame, useLoader } from "@react-three/fiber";
|
||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { clone } from "three/examples/jsm/utils/SkeletonUtils";
|
||||
import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver";
|
||||
import IKAnimationController from "./IKAnimationController";
|
||||
import { TransformControls } from "@react-three/drei";
|
||||
|
||||
const IkInstances = ({ modelUrl, position, rotation }: { modelUrl: string; position: [number, number, number]; rotation: [number, number, number]; }) => {
|
||||
interface StaticMachineState {
|
||||
uuid: string;
|
||||
status: string;
|
||||
actions: { uuid: string; name: string; buffer: number; material: string; };
|
||||
machineTriggerId: string;
|
||||
connectedArmBot: string;
|
||||
}
|
||||
|
||||
interface ArmBotState {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
status: string;
|
||||
material: string;
|
||||
triggerId: string;
|
||||
connections: {
|
||||
source: { modelUUID: string; pointUUID: string };
|
||||
targets: { modelUUID: string; pointUUID: string }[];
|
||||
};
|
||||
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
const IkInstances = ({
|
||||
uuid,
|
||||
selectedTrigger,
|
||||
modelUrl,
|
||||
processes,
|
||||
position,
|
||||
rotation,
|
||||
armBot,
|
||||
setArmBots,
|
||||
setStaticMachines,
|
||||
updateArmBotStatus
|
||||
}: {
|
||||
uuid: string;
|
||||
selectedTrigger: string;
|
||||
modelUrl: string;
|
||||
processes: any;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
armBot: ArmBotState;
|
||||
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
|
||||
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
|
||||
updateArmBotStatus: (status: string) => void;
|
||||
}) => {
|
||||
const [ikSolver, setIkSolver] = useState<any>(null);
|
||||
const [selectedTrigger, setSelectedTrigger] = useState<string>("idle");
|
||||
const gltf = useLoader(GLTFLoader, modelUrl, (loader) => {
|
||||
const draco = new DRACOLoader();
|
||||
draco.setDecoderPath(
|
||||
"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/"
|
||||
);
|
||||
draco.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/");
|
||||
loader.setDRACOLoader(draco);
|
||||
});
|
||||
const cloned = useMemo(() => clone(gltf.scene), [gltf]);
|
||||
const groupRef = useRef<any>(null);
|
||||
const [selectedArm, setSelectedArm] = useState<THREE.Group>();
|
||||
const targetBoneName = "Target";
|
||||
const skinnedMeshName = "link_0";
|
||||
|
||||
const process = useMemo(() => [
|
||||
{
|
||||
trigger: "Trigger1",
|
||||
start: new THREE.Vector3(-0.75, 1.5, -2.2),
|
||||
end: new THREE.Vector3(0, 1.2, 2.2),
|
||||
speed: 0.25,
|
||||
},
|
||||
{
|
||||
trigger: "Trigger2",
|
||||
start: new THREE.Vector3(0, 1.2, 2.2),
|
||||
end: new THREE.Vector3(0.75, 1.5, -2.2),
|
||||
speed: 0.22,
|
||||
}
|
||||
], []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!gltf) return;
|
||||
const OOI: any = {};
|
||||
@@ -85,42 +112,37 @@ const IkInstances = ({ modelUrl, position, rotation }: { modelUrl: string; posit
|
||||
|
||||
}, [gltf]);
|
||||
|
||||
useEffect(() => {
|
||||
const triggers = ['Trigger1', 'Trigger2'];
|
||||
let index = 0;
|
||||
|
||||
const cycleTriggers = setInterval(() => {
|
||||
setSelectedTrigger(triggers[index]);
|
||||
index = (index + 1) % triggers.length;
|
||||
}, 10000);
|
||||
|
||||
return () => clearInterval(cycleTriggers);
|
||||
}, []);
|
||||
const logStatus = (status: string) => {
|
||||
// console.log(status);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<group
|
||||
ref={groupRef}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedArm(groupRef.current?.getObjectByName(targetBoneName))
|
||||
}}
|
||||
position={position}
|
||||
rotation={rotation}
|
||||
>
|
||||
<primitive
|
||||
object={cloned}
|
||||
position={position}
|
||||
rotation={rotation}
|
||||
scale={[1, 1, 1]}
|
||||
name={`arm-bot`}
|
||||
/>
|
||||
</group>
|
||||
<IKAnimationController
|
||||
ikSolver={ikSolver}
|
||||
process={process}
|
||||
process={processes}
|
||||
selectedTrigger={selectedTrigger}
|
||||
targetBoneName={targetBoneName}
|
||||
uuid={uuid}
|
||||
logStatus={logStatus}
|
||||
groupRef={groupRef}
|
||||
armBot={armBot}
|
||||
setArmBots={setArmBots}
|
||||
setStaticMachines={setStaticMachines}
|
||||
updateArmBotStatus={updateArmBotStatus}
|
||||
/>
|
||||
{/* {selectedArm && <TransformControls object={selectedArm} />} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import * as THREE from "three";
|
||||
import * as SimulationTypes from "../../../types/simulation";
|
||||
import * as SimulationTypes from "../../../types/simulationTypes";
|
||||
import { useRef, useState, useEffect, useMemo } from "react";
|
||||
import { Sphere, TransformControls } from "@react-three/drei";
|
||||
import {
|
||||
@@ -66,45 +66,6 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec
|
||||
});
|
||||
});
|
||||
|
||||
const updateSimulationPaths = () => {
|
||||
if (!selectedActionSphere) return;
|
||||
|
||||
const updatedPaths = simulationStates.map((path) => {
|
||||
if (path.type === "Conveyor") {
|
||||
return {
|
||||
...path,
|
||||
points: path.points.map((point) =>
|
||||
point.uuid === selectedActionSphere.points.uuid
|
||||
? {
|
||||
...point,
|
||||
position: [
|
||||
selectedActionSphere.points.position.x,
|
||||
selectedActionSphere.points.position.y,
|
||||
selectedActionSphere.points.position.z,
|
||||
],
|
||||
rotation: [
|
||||
selectedActionSphere.points.rotation.x,
|
||||
selectedActionSphere.points.rotation.y,
|
||||
selectedActionSphere.points.rotation.z,
|
||||
],
|
||||
}
|
||||
: point
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}) as SimulationTypes.ConveyorEventsSchema[];
|
||||
|
||||
const updatedPath = updatedPaths.find(
|
||||
(path) => path.type === "Conveyor" && path.points.some((point) => point.uuid === selectedActionSphere.points.uuid)
|
||||
);
|
||||
|
||||
// console.log("Updated Path:", updatedPath);
|
||||
|
||||
setSimulationStates(updatedPaths);
|
||||
};
|
||||
|
||||
useFrame(() => {
|
||||
if (eyeDropMode) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
@@ -445,7 +406,6 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec
|
||||
ref={transformRef}
|
||||
object={selectedActionSphere.points}
|
||||
mode={transformMode}
|
||||
onMouseUp={updateSimulationPaths}
|
||||
/>
|
||||
)}
|
||||
</group>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useRef, useEffect, useMemo } from "react";
|
||||
import React, { useRef, useEffect, useMemo, useCallback } from "react";
|
||||
import { useLoader, useFrame } from "@react-three/fiber";
|
||||
import { GLTFLoader } from "three-stdlib";
|
||||
import * as THREE from "three";
|
||||
@@ -10,11 +10,27 @@ import ProcessObject from "./processObject";
|
||||
import { ProcessData } from "./types";
|
||||
|
||||
|
||||
interface ArmBotState {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
status: string;
|
||||
material: string;
|
||||
triggerId: string;
|
||||
connections: {
|
||||
source: { modelUUID: string; pointUUID: string };
|
||||
targets: { modelUUID: string; pointUUID: string }[];
|
||||
};
|
||||
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
|
||||
isActive?: boolean;
|
||||
}
|
||||
interface ProcessContainerProps {
|
||||
processes: ProcessData[];
|
||||
setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
|
||||
agvRef: any;
|
||||
MaterialRef: any;
|
||||
armBots: ArmBotState[];
|
||||
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
|
||||
}
|
||||
|
||||
const ProcessAnimator: React.FC<ProcessContainerProps> = ({
|
||||
@@ -22,6 +38,8 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
|
||||
setProcesses,
|
||||
agvRef,
|
||||
MaterialRef,
|
||||
armBots,
|
||||
setArmBots
|
||||
}) => {
|
||||
const gltf = useLoader(GLTFLoader, crate) as GLTF;
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
@@ -41,7 +59,7 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
|
||||
getPointDataForAnimationIndex,
|
||||
processes: processedProcesses,
|
||||
checkAndCountTriggers,
|
||||
} = useProcessAnimation(processes, setProcesses, agvRef);
|
||||
} = useProcessAnimation(processes, setProcesses, agvRef, armBots, setArmBots);
|
||||
|
||||
const baseMaterials = useMemo(
|
||||
() => ({
|
||||
@@ -96,6 +114,29 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
|
||||
|
||||
// In processAnimator.tsx - only the relevant spawn logic part that needs fixes
|
||||
|
||||
|
||||
// Add this function to ProcessAnimator component
|
||||
const isConnectedToActiveArmBot = useCallback(
|
||||
(processId: any) => {
|
||||
// Check if any active armbot is connected to this process
|
||||
return armBots.some((armbot) => {
|
||||
if (!armbot.isActive) return false;
|
||||
|
||||
// Check if this armbot is connected to the process
|
||||
return armbot.connections?.targets?.some((connection: any) => {
|
||||
// Find the process that owns this modelUUID
|
||||
const connectedProcess = processes.find((p) =>
|
||||
p.paths?.some((path) => path.modeluuid === connection.modelUUID)
|
||||
);
|
||||
return connectedProcess?.id === processId;
|
||||
});
|
||||
});
|
||||
},
|
||||
[armBots, processes]
|
||||
);
|
||||
|
||||
// In processAnimator.tsx - only the relevant spawn logic part that needs fixes
|
||||
|
||||
useFrame(() => {
|
||||
// Spawn logic frame
|
||||
const currentTime =
|
||||
@@ -113,6 +154,14 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (isConnectedToActiveArmBot(process.id)) {
|
||||
newStates[process.id] = {
|
||||
...processState,
|
||||
nextSpawnTime: Infinity, // Prevent future spawns
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const spawnPoint = findSpawnPoint(process);
|
||||
if (!spawnPoint || !spawnPoint.actions) return;
|
||||
|
||||
@@ -180,6 +229,29 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
|
||||
const processState = newStates[process.id];
|
||||
if (!processState) return;
|
||||
|
||||
if (isConnectedToActiveArmBot(process.id)) {
|
||||
newStates[process.id] = {
|
||||
...processState,
|
||||
spawnedObjects: Object.entries(processState.spawnedObjects).reduce(
|
||||
(acc, [id, obj]) => ({
|
||||
...acc,
|
||||
[id]: {
|
||||
...obj,
|
||||
state: {
|
||||
...obj.state,
|
||||
isAnimating: false, // Stop animation
|
||||
isDelaying: false, // Clear delays
|
||||
delayComplete: false, // Reset delays
|
||||
progress: 0, // Reset progress
|
||||
},
|
||||
},
|
||||
}),
|
||||
{}
|
||||
),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (processState.isProcessDelaying) {
|
||||
const effectiveDelayTime =
|
||||
processState.processDelayDuration / speedRef.current;
|
||||
@@ -444,7 +516,6 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
|
||||
return newStates;
|
||||
});
|
||||
});
|
||||
|
||||
if (!processedProcesses || processedProcesses.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,28 @@ import React, { useState } from "react";
|
||||
import ProcessCreator from "./processCreator";
|
||||
import ProcessAnimator from "./processAnimator";
|
||||
|
||||
interface ArmBotState {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
status: string;
|
||||
material: string;
|
||||
triggerId: string;
|
||||
connections: {
|
||||
source: { modelUUID: string; pointUUID: string };
|
||||
targets: { modelUUID: string; pointUUID: string }[];
|
||||
};
|
||||
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
interface ProcessContainerProps {
|
||||
processes: any[];
|
||||
setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
|
||||
agvRef: any;
|
||||
MaterialRef: any;
|
||||
armBots: ArmBotState[];
|
||||
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
|
||||
}
|
||||
|
||||
const ProcessContainer: React.FC<ProcessContainerProps> = ({
|
||||
@@ -14,6 +31,8 @@ const ProcessContainer: React.FC<ProcessContainerProps> = ({
|
||||
setProcesses,
|
||||
agvRef,
|
||||
MaterialRef,
|
||||
armBots,
|
||||
setArmBots
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
@@ -23,6 +42,8 @@ const ProcessContainer: React.FC<ProcessContainerProps> = ({
|
||||
setProcesses={setProcesses}
|
||||
agvRef={agvRef}
|
||||
MaterialRef={MaterialRef}
|
||||
armBots={armBots}
|
||||
setArmBots={setArmBots}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
ArmBotEventsSchema,
|
||||
ConveyorEventsSchema,
|
||||
VehicleEventsSchema,
|
||||
} from "../../../types/simulation";
|
||||
} from "../../../types/simulationTypes";
|
||||
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
|
||||
// Type definitions
|
||||
|
||||
@@ -39,7 +39,7 @@ export interface ProcessPath {
|
||||
pathRotation: number[];
|
||||
speed: number;
|
||||
type: "Conveyor" | "Vehicle" | "ArmBot";
|
||||
isActive: boolean;
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
export interface ProcessData {
|
||||
|
||||
@@ -45,10 +45,27 @@ interface PlayAgvState {
|
||||
setPlayAgv: (data: any) => void;
|
||||
}
|
||||
|
||||
interface ArmBotState {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
status: string;
|
||||
material: string;
|
||||
triggerId: string;
|
||||
connections: {
|
||||
source: { modelUUID: string; pointUUID: string };
|
||||
targets: { modelUUID: string; pointUUID: string }[];
|
||||
};
|
||||
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export const useProcessAnimation = (
|
||||
processes: ProcessData[],
|
||||
setProcesses: React.Dispatch<React.SetStateAction<any[]>>,
|
||||
agvRef: any
|
||||
agvRef: any,
|
||||
armBots: ArmBotState[],
|
||||
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>
|
||||
) => {
|
||||
// State and refs initialization
|
||||
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
||||
@@ -58,18 +75,13 @@ export const useProcessAnimation = (
|
||||
const clockRef = useRef<THREE.Clock>(new THREE.Clock());
|
||||
const pauseTimeRef = useRef<number>(0);
|
||||
const elapsedBeforePauseRef = useRef<number>(0);
|
||||
const animationStatesRef = useRef<
|
||||
Record<string, EnhancedProcessAnimationState>
|
||||
>({});
|
||||
const animationStatesRef = useRef<Record<string, EnhancedProcessAnimationState>>({});
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
const prevIsPlaying = useRef<boolean | null>(null);
|
||||
const [internalResetFlag, setInternalResetFlag] = useState(false);
|
||||
const [animationStates, setAnimationStates] = useState<
|
||||
Record<string, EnhancedProcessAnimationState>
|
||||
>({});
|
||||
const [animationStates, setAnimationStates] = useState<Record<string, EnhancedProcessAnimationState>>({});
|
||||
const speedRef = useRef<number>(speed);
|
||||
const armBotRef = useRef<any[]>([]);
|
||||
|
||||
const { PlayAgv, setPlayAgv } = usePlayAgv();
|
||||
|
||||
// Effect hooks
|
||||
useEffect(() => {
|
||||
@@ -111,8 +123,7 @@ export const useProcessAnimation = (
|
||||
if (isPaused) {
|
||||
pauseTimeRef.current = clockRef.current.getElapsedTime();
|
||||
} else if (pauseTimeRef.current > 0) {
|
||||
const pausedDuration =
|
||||
clockRef.current.getElapsedTime() - pauseTimeRef.current;
|
||||
const pausedDuration = clockRef.current.getElapsedTime() - pauseTimeRef.current;
|
||||
elapsedBeforePauseRef.current += pausedDuration;
|
||||
}
|
||||
}, [isPaused]);
|
||||
@@ -161,8 +172,9 @@ export const useProcessAnimation = (
|
||||
// Initialize AGVs for each process first
|
||||
processes.forEach((process) => {
|
||||
// Find all vehicle paths for this process
|
||||
const vehiclePaths =
|
||||
process.paths?.filter((path) => path.type === "Vehicle") || [];
|
||||
const vehiclePaths = process.paths?.filter(
|
||||
(path) => path.type === "Vehicle"
|
||||
) || [];
|
||||
|
||||
// Initialize AGVs for each vehicle path
|
||||
vehiclePaths.forEach((vehiclePath) => {
|
||||
@@ -187,8 +199,8 @@ export const useProcessAnimation = (
|
||||
maxHitCount: maxHitCount || 0,
|
||||
isActive: false,
|
||||
hitCount: 0,
|
||||
status: "stationed",
|
||||
lastUpdated: 0,
|
||||
status: 'stationed',
|
||||
lastUpdated: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -449,6 +461,8 @@ export const useProcessAnimation = (
|
||||
[handleMaterialSwap]
|
||||
);
|
||||
|
||||
const deferredArmBotUpdates = useRef<{ uuid: string; triggerId: string }[]>([]);
|
||||
|
||||
// Trigger counting system
|
||||
const checkAndCountTriggers = useCallback(
|
||||
(
|
||||
@@ -468,9 +482,7 @@ export const useProcessAnimation = (
|
||||
const point = getPointDataForAnimationIndex(process, currentPointIndex);
|
||||
if (!point?.triggers) return prev;
|
||||
|
||||
const onHitTriggers = point.triggers.filter(
|
||||
(t: Trigger) => t.type === "On-Hit" && t.isUsed
|
||||
);
|
||||
const onHitTriggers = point.triggers.filter((t: Trigger) => t.type === "On-Hit" && t.isUsed);
|
||||
|
||||
if (onHitTriggers.length === 0) return prev;
|
||||
|
||||
@@ -478,58 +490,47 @@ export const useProcessAnimation = (
|
||||
const newTriggerLogs = [...processState.triggerLogs];
|
||||
let shouldLog = false;
|
||||
|
||||
// Find all vehicle paths for this process
|
||||
const vehiclePaths = process.paths.filter(
|
||||
(path) => path.type === "Vehicle"
|
||||
);
|
||||
const vehiclePaths = process.paths.filter((path) => path.type === "Vehicle");
|
||||
const armBotPaths = process.paths.filter((path) => path.type === "ArmBot");
|
||||
|
||||
// Find all ArmBot paths for this process
|
||||
const armBotPaths = process.paths.filter(
|
||||
(path) => path.type === "ArmBot"
|
||||
);
|
||||
|
||||
// Check if any vehicle is active for this process
|
||||
const activeVehicles = vehiclePaths.filter((path) => {
|
||||
const vehicleId = path.modeluuid;
|
||||
const vehicleEntry = agvRef.current.find(
|
||||
(v: any) => v.vehicleId === vehicleId && v.processId === processId
|
||||
);
|
||||
const vehicleEntry = agvRef.current.find((v: any) => v.vehicleId === vehicleId && v.processId === processId);
|
||||
return vehicleEntry?.isActive;
|
||||
});
|
||||
|
||||
// Check if any ArmBot is active for this process
|
||||
const activeArmBots = armBotPaths.filter((path) => {
|
||||
const armBotId = path.modeluuid;
|
||||
const armBotEntry = armBotRef.current.find(
|
||||
(a: any) => a.armBotId === armBotId && a.processId === processId
|
||||
);
|
||||
return armBotEntry?.isActive;
|
||||
});
|
||||
// const activeArmBots = armBotPaths.filter((path) => {
|
||||
// const armBotId = path.modeluuid;
|
||||
// const armBotEntry = armBots.find((a: any) => a.uuid === armBotId);
|
||||
// return armBotEntry;
|
||||
// });
|
||||
|
||||
// Only count triggers if no vehicles and no ArmBots are active for this process
|
||||
if (activeVehicles.length === 0 && activeArmBots.length === 0) {
|
||||
|
||||
if (activeVehicles.length === 0) {
|
||||
onHitTriggers.forEach((trigger: Trigger) => {
|
||||
const triggerKey = `${point.uuid}-${trigger.uuid}`;
|
||||
newTriggerCounts[triggerKey] =
|
||||
(newTriggerCounts[triggerKey] || 0) + 1;
|
||||
shouldLog = true;
|
||||
newTriggerLogs.push({
|
||||
timestamp: currentTime,
|
||||
pointId: point.uuid,
|
||||
objectId,
|
||||
triggerId: trigger.uuid,
|
||||
const connections = point.connections?.targets || [];
|
||||
|
||||
connections.forEach((connection) => {
|
||||
const connectedModelUUID = connection.modelUUID;
|
||||
|
||||
const matchingArmPath = armBotPaths.find((path) => path.modeluuid === connectedModelUUID);
|
||||
|
||||
if (matchingArmPath) {
|
||||
deferredArmBotUpdates.current.push({
|
||||
uuid: connectedModelUUID,
|
||||
triggerId: trigger.uuid,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let processTotalHits = Object.values(newTriggerCounts).reduce(
|
||||
(a, b) => a + b,
|
||||
0
|
||||
);
|
||||
let processTotalHits = Object.values(newTriggerCounts).reduce((a, b) => a + b, 0);
|
||||
|
||||
// Handle logic for vehicles and ArmBots when a trigger is hit
|
||||
if (shouldLog) {
|
||||
// Handle vehicle logic (existing code)
|
||||
vehiclePaths.forEach((vehiclePath) => {
|
||||
if (vehiclePath.points?.length > 0) {
|
||||
const vehiclePoint = vehiclePath.points[0];
|
||||
@@ -568,48 +569,6 @@ export const useProcessAnimation = (
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle ArmBot logic (new code)
|
||||
armBotPaths.forEach((armBotPath) => {
|
||||
if (armBotPath.points?.length > 0) {
|
||||
const armBotPoint = armBotPath.points[0];
|
||||
const action = armBotPoint.actions?.[0];
|
||||
const maxHitCount = action?.hitCount;
|
||||
|
||||
if (maxHitCount !== undefined) {
|
||||
const armBotId = armBotPath.modeluuid;
|
||||
let armBotEntry = armBotRef.current.find(
|
||||
(a: any) =>
|
||||
a.armBotId === armBotId && a.processId === processId
|
||||
);
|
||||
|
||||
if (!armBotEntry) {
|
||||
armBotEntry = {
|
||||
processId,
|
||||
armBotId,
|
||||
maxHitCount: maxHitCount,
|
||||
isActive: false,
|
||||
hitCount: 0,
|
||||
status: "stationed",
|
||||
};
|
||||
armBotRef.current.push(armBotEntry);
|
||||
console.log('armBotEntry: ', armBotEntry);
|
||||
}
|
||||
|
||||
if (!armBotEntry.isActive) {
|
||||
armBotEntry.hitCount++;
|
||||
armBotEntry.lastUpdated = currentTime;
|
||||
|
||||
if (armBotEntry.hitCount >= armBotEntry.maxHitCount) {
|
||||
armBotEntry.isActive = true;
|
||||
// Reset trigger counts when ArmBot activates, similar to vehicle
|
||||
newTriggerCounts = {};
|
||||
processTotalHits = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -622,9 +581,24 @@ export const useProcessAnimation = (
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (deferredArmBotUpdates.current.length > 0) {
|
||||
const updates = [...deferredArmBotUpdates.current];
|
||||
deferredArmBotUpdates.current = [];
|
||||
|
||||
setArmBots((prev) =>
|
||||
prev.map((bot) => {
|
||||
const update = updates.find((u) => u.uuid === bot.uuid);
|
||||
|
||||
return update
|
||||
? { ...bot, triggerId: update.triggerId, isActive: true }
|
||||
: bot;
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [animationStates]);
|
||||
|
||||
// Utility functions
|
||||
const hasNonInheritActions = useCallback(
|
||||
@@ -632,9 +606,7 @@ export const useProcessAnimation = (
|
||||
return actions.some(
|
||||
(action) => action.isUsed && action.type !== "Inherit"
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
}, []);
|
||||
|
||||
const getPointDataForAnimationIndex = useCallback(
|
||||
(process: ProcessData, index: number): ProcessPoint | null => {
|
||||
|
||||
@@ -6,42 +6,69 @@ import useModuleStore from "../../store/useModuleStore";
|
||||
import ProcessContainer from "./process/processContainer";
|
||||
import Agv from "../builder/agv/agv";
|
||||
import ArmBot from "./armbot/ArmBot";
|
||||
import StaticMachine from "./staticMachine/staticMachine";
|
||||
|
||||
interface ArmBotState {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
status: string;
|
||||
material: string;
|
||||
triggerId: string;
|
||||
connections: {
|
||||
source: { modelUUID: string; pointUUID: string };
|
||||
targets: { modelUUID: string; pointUUID: string }[];
|
||||
};
|
||||
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
interface StaticMachineState {
|
||||
uuid: string;
|
||||
status: string;
|
||||
actions: { uuid: string; name: string; buffer: number; material: string; };
|
||||
machineTriggerId: string;
|
||||
connectedArmBot: string;
|
||||
}
|
||||
|
||||
function Simulation() {
|
||||
const { activeModule } = useModuleStore();
|
||||
const pathsGroupRef = useRef() as React.MutableRefObject<THREE.Group>;
|
||||
const [processes, setProcesses] = useState<any[]>([]);
|
||||
const { activeModule } = useModuleStore();
|
||||
const pathsGroupRef = useRef() as React.MutableRefObject<THREE.Group>;
|
||||
const [armBots, setArmBots] = useState<ArmBotState[]>([]);
|
||||
const [staticMachines, setStaticMachines] = useState<StaticMachineState[]>([]);
|
||||
const [processes, setProcesses] = useState<any[]>([]);
|
||||
const agvRef = useRef([]);
|
||||
const MaterialRef = useRef([]);
|
||||
|
||||
const agvRef = useRef([]);
|
||||
const MaterialRef = useRef([]);
|
||||
const { simulationStates } = useSimulationStates();
|
||||
|
||||
return (
|
||||
<>
|
||||
{activeModule === "simulation" && (
|
||||
return (
|
||||
<>
|
||||
<PathCreation pathsGroupRef={pathsGroupRef} />
|
||||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<PathCreation pathsGroupRef={pathsGroupRef} />
|
||||
|
||||
<PathConnector pathsGroupRef={pathsGroupRef} />
|
||||
<PathConnector pathsGroupRef={pathsGroupRef} />
|
||||
|
||||
<ProcessContainer
|
||||
processes={processes}
|
||||
setProcesses={setProcesses}
|
||||
agvRef={agvRef}
|
||||
MaterialRef={MaterialRef}
|
||||
/>
|
||||
<ProcessContainer
|
||||
processes={processes}
|
||||
setProcesses={setProcesses}
|
||||
agvRef={agvRef}
|
||||
MaterialRef={MaterialRef}
|
||||
armBots={armBots}
|
||||
setArmBots={setArmBots}
|
||||
/>
|
||||
|
||||
<Agv
|
||||
processes={processes}
|
||||
agvRef={agvRef}
|
||||
MaterialRef={MaterialRef}
|
||||
/>
|
||||
<Agv
|
||||
processes={processes}
|
||||
agvRef={agvRef}
|
||||
MaterialRef={MaterialRef}
|
||||
/>
|
||||
|
||||
</>
|
||||
)}
|
||||
<StaticMachine setArmBots={setArmBots} staticMachines={staticMachines} setStaticMachines={setStaticMachines} />
|
||||
<ArmBot armBots={armBots} setArmBots={setArmBots} setStaticMachines={setStaticMachines} />
|
||||
</>
|
||||
)}
|
||||
<ArmBot />
|
||||
</>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
export default Simulation;
|
||||
|
||||
81
app/src/modules/simulation/staticMachine/staticMachine.tsx
Normal file
81
app/src/modules/simulation/staticMachine/staticMachine.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import * as SimulationTypes from '../../../types/simulationTypes';
|
||||
import { useSimulationStates } from '../../../store/store';
|
||||
import StaticMachineInstances from './staticMachineInstances';
|
||||
|
||||
interface ArmBotState {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
status: string;
|
||||
material: string;
|
||||
triggerId: string;
|
||||
connections: {
|
||||
source: { modelUUID: string; pointUUID: string };
|
||||
targets: { modelUUID: string; pointUUID: string }[];
|
||||
};
|
||||
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
|
||||
isActive?: boolean;
|
||||
}
|
||||
interface StaticMachineState {
|
||||
uuid: string;
|
||||
status: string;
|
||||
actions: { uuid: string; name: string; buffer: number; material: string; };
|
||||
machineTriggerId: string;
|
||||
connectedArmBot: string;
|
||||
}
|
||||
|
||||
type StaticMachineProps = {
|
||||
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
|
||||
staticMachines: StaticMachineState[];
|
||||
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
|
||||
}
|
||||
|
||||
function StaticMachine({ setArmBots, staticMachines, setStaticMachines }: StaticMachineProps) {
|
||||
|
||||
const { simulationStates } = useSimulationStates();
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = simulationStates.filter((s): s is SimulationTypes.StaticMachineEventsSchema => s.type === "StaticMachine");
|
||||
const initialStates: StaticMachineState[] = filtered
|
||||
.filter(machine => machine.points.connections.targets.length > 0)
|
||||
.map(machine => ({
|
||||
uuid: machine.modeluuid,
|
||||
status: "idle",
|
||||
actions: machine.points.actions,
|
||||
machineTriggerId: machine.points.triggers.uuid,
|
||||
connectedArmBot: machine.points.connections.targets[0].modelUUID
|
||||
}));
|
||||
setStaticMachines(initialStates);
|
||||
}, [simulationStates]);
|
||||
|
||||
const updateArmBotTriggerAndMachineStatus = (armBotUuid: string, triggerId: string, machineId: string) => {
|
||||
setArmBots((prevArmBots) => {
|
||||
return prevArmBots.map(bot => {
|
||||
if (bot.uuid === armBotUuid) {
|
||||
return { ...bot, triggerId: triggerId };
|
||||
}
|
||||
return bot;
|
||||
});
|
||||
});
|
||||
setStaticMachines((prevStaticMachines) => {
|
||||
return prevStaticMachines.map(machine => {
|
||||
if (machine.uuid === machineId) {
|
||||
return { ...machine, status: "idle" };
|
||||
} else {
|
||||
return machine;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{staticMachines.map((machine, index) => (
|
||||
<StaticMachineInstances key={index} machine={machine} updateArmBotTriggerAndMachineStatus={updateArmBotTriggerAndMachineStatus} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default StaticMachine;
|
||||
@@ -0,0 +1,33 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { useAnimationPlaySpeed } from '../../../store/usePlayButtonStore';
|
||||
|
||||
interface StaticMachineState {
|
||||
uuid: string;
|
||||
status: string;
|
||||
actions: { uuid: string; name: string; buffer: number; material: string; };
|
||||
machineTriggerId: string;
|
||||
connectedArmBot: string;
|
||||
}
|
||||
|
||||
type StaticMachineInstancesProps = {
|
||||
machine: StaticMachineState,
|
||||
updateArmBotTriggerAndMachineStatus: (armBotUuid: string, triggerId: string, machineId: string) => void;
|
||||
}
|
||||
|
||||
function StaticMachineInstances({ machine, updateArmBotTriggerAndMachineStatus }: StaticMachineInstancesProps) {
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
|
||||
useEffect(() => {
|
||||
if (machine.status === 'running') {
|
||||
setTimeout(() => {
|
||||
updateArmBotTriggerAndMachineStatus(machine.connectedArmBot, machine.machineTriggerId, machine.uuid);
|
||||
}, machine.actions.buffer * 1000 * speed);
|
||||
}
|
||||
}, [machine])
|
||||
|
||||
return (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
export default StaticMachineInstances
|
||||
Reference in New Issue
Block a user