enhance simulation components with state management and event handling for armbot

This commit is contained in:
Gomathi 2025-06-06 18:04:46 +05:30
parent 73d2cc8c73
commit 60c0bef5f9
8 changed files with 225 additions and 49 deletions

View File

@ -89,7 +89,7 @@ function AgvSimulation({ data, assetName, assetUUID }: AgvSimulationProps) {
if (materialRef.current && offsetRef.current) {
materialRef.current.position.copy(currentPos.clone().add(offsetRef.current));
}
console.log(` ${assetName} is running at ${data.percentage}%`);
});
useEffect(() => {

View File

@ -8,7 +8,7 @@ type PointWithDegree = {
degree: number;
};
function ArmAnimator({ armBot, ikSolver, setIkSolver, targetBone, restPosition, path }: any) {
function ArmAnimator({ armBot, ikSolver, setIkSolver, targetBone, restPosition, path, assetName, data }: any) {
const { scene } = useThree();
const progressRef = useRef(0);
const curveRef = useRef<THREE.Vector3[] | null>(null);
@ -24,9 +24,13 @@ function ArmAnimator({ armBot, ikSolver, setIkSolver, targetBone, restPosition,
const { speed } = useAnimationPlaySpeed();
let duration = 5000
const [isRunning, setIsRunning] = useState(false);
const lastPercentageRef = useRef<number>(0);
const targetPercentageRef = useRef<number>(0);
const interpolationStartTimeRef = useRef<number>(performance.now());
useEffect(() => {
console.log('path: ', path);
setCurrentPath(path);
}, [path]);
@ -183,22 +187,32 @@ function ArmAnimator({ armBot, ikSolver, setIkSolver, targetBone, restPosition,
// Frame update for animation
useFrame((state, delta) => {
if (!startTimeRef.current || !isRunning) return;
if (!ikSolver || !customCurvePoints || customCurvePoints.length === 0) return;
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
if (!bone) return;
const now = performance.now();
const elapsed = now - interpolationStartTimeRef.current;
const duration = 2000;
const t = Math.min(elapsed / duration, 1);
const interpolatedPercentage =
lastPercentageRef.current +
(targetPercentageRef.current - lastPercentageRef.current) * t;
const progress = Math.min(interpolatedPercentage / 100, 1);
const distances = segmentDistancesRef.current;
const totalDistance = totalDistanceRef.current;
// Initialize start time
if (startTimeRef.current === null) {
startTimeRef.current = state.clock.elapsedTime;
}
const elapsed = (state.clock.elapsedTime - startTimeRef.current) * 1000; // ms
const clampedProgress = Math.min(elapsed / duration, 1); // progress [0,1]
const coveredDistance = clampedProgress * totalDistance;
const coveredDistance = progress * totalDistance;
// console.log('coveredDistance: ', coveredDistance);
// Traverse segments to find current position
let index = 0;
@ -215,6 +229,8 @@ function ArmAnimator({ armBot, ikSolver, setIkSolver, targetBone, restPosition,
const segmentDistance = distances[index];
const t = (coveredDistance - accumulatedDistance) / segmentDistance;
if (startPoint && endPoint) {
const position = startPoint.clone().lerp(endPoint, t);
bone.position.copy(position);
@ -224,7 +240,7 @@ function ArmAnimator({ armBot, ikSolver, setIkSolver, targetBone, restPosition,
ikSolver.update();
// Reset at the end
if (clampedProgress >= 1) {
if (progress >= 1) {
setCurrentPath([]);
setCustomCurvePoints([]);
curveRef.current = null;
@ -237,6 +253,25 @@ function ArmAnimator({ armBot, ikSolver, setIkSolver, targetBone, restPosition,
}
});
useEffect(() => {
if (data.assetName !== assetName) return;
if (data.state === 'running' && data.percentage !== undefined) {
console.log('data.percentage: ', data.percentage);
if (data.percentage === 0) {
startTimeRef.current = performance.now();
lastPercentageRef.current = 0;
targetPercentageRef.current = 0
} else {
lastPercentageRef.current = targetPercentageRef.current;
}
targetPercentageRef.current = data.percentage;
interpolationStartTimeRef.current = performance.now();
setIsRunning(true);
} else {
setIsRunning(false);
}
}, [data, assetName]);
return (
<></>

View File

@ -85,7 +85,7 @@ function ConveyorSimulation({ data, assetName, assetUUID }: ConveyorSimulationPr
materialRef.current.position.copy(currentPos);
materialRef.current.quaternion.copy(quaternionRef.current);
console.log(` ${assetName} is running at ${data.percentage}%`);
});
// Update movement state

View File

@ -71,7 +71,7 @@ function IIotIkSolver({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKI
setSelectedArm(OOI.Target_Bone);
scene.add(helper);
// scene.add(helper);
}, [cloned, gltf, setIkSolver]);

View File

@ -43,7 +43,7 @@ function MachineSimulation({ data, assetName, assetUUID }: MachineSimulationProp
const percentage = percentageRef.current;
// 🟢 Use percentage in logic if needed
console.log(` ${assetName} is running at ${data.percentage}%`);
});
return null;

View File

@ -14,12 +14,13 @@ const MqttListener: React.FC<MqttListenerProps> = ({ setData }) => {
useEffect(() => {
if (!client) return;
const topic = 'conveyor0001/0001/status';
const topic1 = 'conveyor0001/0001/status';
const topic2 = 'conveyor0002/0001/status';
const topic3 = 'cnc0001/0001/status'
const topic4 = 'agv0001/0001/status'
const topic5 = 'arm0001/0001/status'
client.subscribe(topic, {} as IClientSubscribeOptions, (err) => {
client.subscribe(topic1, {} as IClientSubscribeOptions, (err) => {
if (err) {
console.error('Subscription error:', err);
}
@ -39,36 +40,58 @@ const MqttListener: React.FC<MqttListenerProps> = ({ setData }) => {
console.error('Subscription error:', err);
}
});
client.subscribe(topic5, {} as IClientSubscribeOptions, (err) => {
if (err) {
console.error('Subscription error:', err);
}
});
const handleMessage = (receivedTopic: string, payload: Buffer) => {
if (receivedTopic === topic) {
if (receivedTopic === topic1) {
const msgStr = payload.toString();
setData(JSON.parse(msgStr))
// setMessage(msgStr);
let mqttData = JSON.parse(msgStr);
// if (mqttData.state === "running") {
setData(mqttData)
// }
}
if (receivedTopic === topic2) {
const msgStr = payload.toString();
setData(JSON.parse(msgStr))
// setMessage(msgStr);
let mqttData = JSON.parse(msgStr);
// if (mqttData.state === "running") {
setData(mqttData)
// }
}
if (receivedTopic === topic3) {
const msgStr = payload.toString();
setData(JSON.parse(msgStr))
// setMessage(msgStr);
let mqttData = JSON.parse(msgStr);
// if (mqttData.state === "running") {
setData(mqttData)
// }
}
if (receivedTopic === topic4) {
const msgStr = payload.toString();
setData(JSON.parse(msgStr))
// setMessage(msgStr);
let mqttData = JSON.parse(msgStr);
// if (mqttData.state === "running") {
setData(mqttData)
// }
}
if (receivedTopic === topic5) {
const msgStr = payload.toString();
let mqttData = JSON.parse(msgStr);
// if (mqttData.state === "running") {
setData(mqttData)
// }
}
};
client.on('message', handleMessage);
return () => {
client.off('message', handleMessage);
client.unsubscribe(topic);
client.unsubscribe(topic1);
client.unsubscribe(topic2);
client.unsubscribe(topic3);
client.unsubscribe(topic4);
client.unsubscribe(topic5);
};
}, [client]);

View File

@ -6,6 +6,7 @@ import ConveyorSimulation from './ConveyorSimulation';
import MachineSimulation from './MachineSimulation';
import AgvSimulation from './AgvSimulation';
import RoboticArmSimulation from './RoboticArmSimulation';
import useModuleStore from '../../store/useModuleStore';
type MqttData = {
state?: string;
@ -17,8 +18,14 @@ type MqttData = {
function RealTimeSimulation() {
const [data, setData] = useState<MqttData>({});
const { activeModule } = useModuleStore();
useEffect(() => {
if (activeModule !== "visualization") return;
// console.log('data: ', data);
if (data.state === "running") {
console.log('data: ', data);
}
}, [data])
return (
@ -27,7 +34,7 @@ function RealTimeSimulation() {
<ConveyorSimulation data={data} assetName="conveyor0002" assetUUID="1288e8f9-95dd-4a1a-a087-0673bbbc3b34" />
<MachineSimulation data={data} assetName="cnc0001" assetUUID="d3598136-40f6-43a9-afd2-8a8899c0986d" />
<AgvSimulation data={data} assetName="agv0001" assetUUID="1dd01c22-cc58-40cf-9e08-84c26f3f4eba" />
<RoboticArmSimulation data={data} assetName="roboticArm" assetUUID="f8344b0b-0c13-4e78-be1f-b6cc210038a4" />
<RoboticArmSimulation data={data} assetName="arm0001" assetUUID="f8344b0b-0c13-4e78-be1f-b6cc210038a4" />
<MqttListener setData={setData} />
</>

View File

@ -6,6 +6,7 @@ import armModel from "../../assets/gltf-glb/rigged/ik_arm_1.glb";
import * as THREE from "three";
import ArmAnimator from "./ArmAnimator";
import { useFloorItems } from "../../store/builder/store";
import { MaterialModel } from "../simulation/materials/instances/material/materialModel";
type ArmBotSimulationProps = {
data: any;
@ -26,6 +27,13 @@ function RoboticArmSimulation({
const targetBone = "Target";
const restPosition = new THREE.Vector3(0, 1.75, -1.6);
const [path, setPath] = useState<[number, number, number][]>([]);
const [isRunning, setIsRunning] = useState(false);
const startTimeRef = useRef<number | null>(null);
const lastPercentageRef = useRef<number>(0);
const targetPercentageRef = useRef<number>(0);
const interpolationStartTimeRef = useRef<number>(performance.now());
const materialRef = useRef<any>(null);
useEffect(() => {
if (activeModule !== "visualization") return;
@ -43,44 +51,139 @@ function RoboticArmSimulation({
position: [RoboticArmObject.position.x, RoboticArmObject.position.y, RoboticArmObject.position.z,],
rotation: [RoboticArmObject.rotation.x, RoboticArmObject.rotation.y, RoboticArmObject.rotation.z,],
};
setArmBot(updatedArmBot);
}, [scene, assetUUID, activeModule]);
const lastEventRef = useRef<string | null>(null);
const lastPathRef = useRef<string>(""); // Stringified path to compare
useEffect(() => {
if (data.assetName !== assetName) return;
if (!ikSolver) return;
if (!isRunning) return;
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 (!targetBones) return;
//action 1
const startPoint = new THREE.Vector3(-0.5119150023588372, 1.08653750252812, 1.2082537344869024);
const endPoint = new THREE.Vector3(-0.005966507840761359, 1.101367457937616, -1.242848820163239);
//action 2
const startPoint1 = new THREE.Vector3(-0.005966507840761359, 1.101367457937616, -1.242848820163239);
const endPoint1 = new THREE.Vector3(0.8449084618119792, 1.0275816535760263, 1.399557309225635);
const startPoint = new THREE.Vector3(-0.050142171738890684, 1.9, -1.999371341850562);
const endPoint = new THREE.Vector3(1.9999942666761656, 1.9, 0.004788868599774787);
const curve1 = createCurveBetweenTwoPoints(targetBones.position.clone(), restPosition.clone());
const curve2 = createCurveBetweenTwoPoints(restPosition.clone(), startPoint.clone());
const curve3 = createCurveBetweenTwoPoints(startPoint.clone(), endPoint.clone());
const curve4 = createCurveBetweenTwoPoints(endPoint.clone(), restPosition.clone());
const curveList = [curve1, curve2, curve3, curve4];
let allPoints: [number, number, number][] = [];
curveList.forEach((curve, index) => {
setTimeout(() => {
if (lastEventRef.current === data.event) return;
if (data.event === 'pick1') {
console.log("picking");
const curve = createCurveBetweenTwoPoints(targetBones.position.clone(), startPoint.clone());
if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z]));
}
}, index * 5000); // 2 seconds delay between each curve
});
}, [ikSolver]);
lastEventRef.current = 'pick1';
}
if (data.event === 'drop1') {
console.log("dropping");
const curve = createCurveBetweenTwoPoints(startPoint.clone(), endPoint.clone());
if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z]));
}
lastEventRef.current = 'drop1';
}
if (data.event === 'pick2') {
console.log("picking111");
const curve = createCurveBetweenTwoPoints(targetBones.position.clone(), startPoint1.clone());
if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z]));
}
lastEventRef.current = 'pick2';
}
if (data.event === 'drop2') {
console.log("dropping1111");
const curve = createCurveBetweenTwoPoints(startPoint1.clone(), endPoint1.clone());
if (curve) {
setPath(curve.points.map(point => [point.x, point.y, point.z]));
}
lastEventRef.current = 'drop2';
}
}, [ikSolver, data, assetName, isRunning]);
useEffect(() => {
if (data.assetName !== assetName) return;
if (data.state === 'running' && data.percentage !== undefined) {
if (data.percentage === 0) {
lastPercentageRef.current = 0;
targetPercentageRef.current = 0
} else {
lastPercentageRef.current = targetPercentageRef.current;
}
targetPercentageRef.current = data.percentage;
interpolationStartTimeRef.current = performance.now();
setIsRunning(true);
} else {
setIsRunning(false);
}
}, [data, assetName]);
function createCurveBetweenTwoPoints(p1: any, p2: any) {
const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5);
const points = [p1, mid, p2];
return new THREE.CatmullRomCurve3(points);
}
useEffect(() => {
if (!ikSolver || !materialRef.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 (data.state === "running" && data.assetName === assetName) {
// 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 adjustedPosition = boneWorldPos.clone().addScaledVector(direction, 0.01);
//set position
materialRef.current.position.copy(adjustedPosition);
// Set rotation
const worldQuaternion = new THREE.Quaternion();
bone.getWorldQuaternion(worldQuaternion);
materialRef.current.quaternion.copy(worldQuaternion);
}
}, [data])
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
if (data.assetName !== assetName) return;
if (data.state === 'running' && typeof data.percentage === 'number') {
const shouldBeVisible = data.percentage <= 100;
// Only update visibility if it changes
setIsVisible(prev => {
if (prev !== shouldBeVisible) {
return shouldBeVisible;
}
return prev;
});
} else {
setIsVisible(false);
}
}, [data, assetName]);
return (
<>
{activeModule === "visualization" && armBot && (
@ -99,7 +202,15 @@ function RoboticArmSimulation({
restPosition={restPosition}
targetBone={targetBone}
path={path}
assetName={assetName}
data={data}
/>
{/* <MaterialModel
materialId={`${assetName}-mat`}
matRef={materialRef}
materialType={'Default material'}
visible={isVisible}
/> */}
</>
)}
</>