Refactor Simulations, RenameTooltip, EditWidgetOption, and RoboticArmAnimator components: streamline imports, enhance UI elements, and improve event handling logic.

This commit is contained in:
2025-05-03 11:17:14 +05:30
parent 6a79ef563c
commit b4e4bf7fb3
5 changed files with 452 additions and 416 deletions

View File

@@ -1,15 +1,15 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useRef, useState } from "react";
import { import {
AddIcon, AddIcon,
ArrowIcon, ArrowIcon,
RemoveIcon, RemoveIcon,
ResizeHeightIcon, ResizeHeightIcon,
} from "../../../icons/ExportCommonIcons"; } from "../../../icons/ExportCommonIcons";
import RenameInput from "../../../ui/inputs/RenameInput"; import RenameInput from "../../../ui/inputs/RenameInput";
import { handleResize } from "../../../../functions/handleResizePannel"; import { handleResize } from "../../../../functions/handleResizePannel";
import { import {
useSelectedAsset, useSelectedAsset,
useSelectedProduct, useSelectedProduct,
} from "../../../../store/simulation/useSimulationStore"; } from "../../../../store/simulation/useSimulationStore";
import { useProductStore } from "../../../../store/simulation/useProductStore"; import { useProductStore } from "../../../../store/simulation/useProductStore";
import { generateUUID } from "three/src/math/MathUtils"; import { generateUUID } from "three/src/math/MathUtils";
@@ -22,206 +22,222 @@ import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertP
import { deleteProductApi } from "../../../../services/simulation/deleteProductApi"; import { deleteProductApi } from "../../../../services/simulation/deleteProductApi";
interface Event { interface Event {
pathName: string; pathName: string;
} }
interface ListProps { interface ListProps {
val: Event; val: Event;
} }
const List: React.FC<ListProps> = ({ val }) => { const List: React.FC<ListProps> = ({ val }) => {
return ( return (
<div className="process-container"> <div className="process-container">
<div className="value">{val.pathName}</div> <div className="value">{val.pathName}</div>
</div> </div>
); );
}; };
const Simulations: React.FC = () => { const Simulations: React.FC = () => {
const productsContainerRef = useRef<HTMLDivElement>(null); const productsContainerRef = useRef<HTMLDivElement>(null);
const { products, addProduct, removeProduct, renameProduct, addEvent, removeEvent, } = useProductStore(); const {
const { selectedProduct, setSelectedProduct } = useSelectedProduct(); products,
const { getEventByModelUuid } = useEventsStore(); addProduct,
const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); removeProduct,
const email = localStorage.getItem('email') renameProduct,
const organization = (email!.split("@")[1]).split(".")[0]; addEvent,
const [openObjects, setOpenObjects] = useState(true); removeEvent,
} = useProductStore();
const { selectedProduct, setSelectedProduct } = useSelectedProduct();
const { getEventByModelUuid } = useEventsStore();
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
const [openObjects, setOpenObjects] = useState(true);
const handleAddProduct = () => { const handleAddProduct = () => {
const id = generateUUID(); const id = generateUUID();
const name = `Product ${products.length + 1}`; const name = `Product ${products.length + 1}`;
addProduct(name, id); addProduct(name, id);
upsertProductOrEventApi({ productName: name, productId: id, organization: organization }); upsertProductOrEventApi({
}; productName: name,
productId: id,
organization: organization,
});
};
const handleRemoveProduct = (productId: string) => { const handleRemoveProduct = (productId: string) => {
const currentIndex = products.findIndex((p) => p.productId === productId); const currentIndex = products.findIndex((p) => p.productId === productId);
const isSelected = selectedProduct.productId === productId; const isSelected = selectedProduct.productId === productId;
const updatedProducts = products.filter((p) => p.productId !== productId); const updatedProducts = products.filter((p) => p.productId !== productId);
if (isSelected) { if (isSelected) {
if (updatedProducts.length > 0) { if (updatedProducts.length > 0) {
let newSelectedIndex = currentIndex; let newSelectedIndex = currentIndex;
if (currentIndex >= updatedProducts.length) { if (currentIndex >= updatedProducts.length) {
newSelectedIndex = updatedProducts.length - 1; newSelectedIndex = updatedProducts.length - 1;
}
setSelectedProduct(
updatedProducts[newSelectedIndex].productId,
updatedProducts[newSelectedIndex].productName
);
} else {
setSelectedProduct("", "");
}
} }
setSelectedProduct(
updatedProducts[newSelectedIndex].productId,
updatedProducts[newSelectedIndex].productName
);
} else {
setSelectedProduct("", "");
}
}
removeProduct(productId); removeProduct(productId);
deleteProductApi(productId, organization); deleteProductApi(productId, organization);
}; };
const handleRenameProduct = (productId: string, newName: string) => { const handleRenameProduct = (productId: string, newName: string) => {
renameProduct(productId, newName); renameProduct(productId, newName);
if (selectedProduct.productId === productId) { if (selectedProduct.productId === productId) {
setSelectedProduct(productId, newName); setSelectedProduct(productId, newName);
} }
}; };
const handleRemoveEventFromProduct = () => { const handleRemoveEventFromProduct = () => {
if (selectedAsset) { if (selectedAsset) {
const email = localStorage.getItem('email') const email = localStorage.getItem("email");
const organization = (email!.split("@")[1]).split(".")[0]; const organization = email!.split("@")[1].split(".")[0];
deleteEventDataApi({ deleteEventDataApi({
productId: selectedProduct.productId, productId: selectedProduct.productId,
modelUuid: selectedAsset.modelUuid, modelUuid: selectedAsset.modelUuid,
organization: organization organization: organization,
}); });
removeEvent(selectedProduct.productId, selectedAsset.modelUuid); removeEvent(selectedProduct.productId, selectedAsset.modelUuid);
clearSelectedAsset(); clearSelectedAsset();
} }
}; };
const selectedProductData = products.find( const selectedProductData = products.find(
(product) => product.productId === selectedProduct.productId (product) => product.productId === selectedProduct.productId
); );
const events: Event[] = selectedProductData?.eventDatas.map((event) => ({ const events: Event[] =
pathName: event.modelName, selectedProductData?.eventDatas.map((event) => ({
pathName: event.modelName,
})) || []; })) || [];
return ( return (
<div className="simulations-container"> <div className="simulations-container">
<div className="header">Simulations</div> <div className="header">Simulations</div>
<div className="add-product-container"> <div className="add-product-container">
<div className="actions section"> <div className="actions section">
<div className="header"> <div className="header">
<div className="header-value">Products</div> <div className="header-value">Products</div>
<div className="add-button" onClick={handleAddProduct}> <button className="add-button" onClick={handleAddProduct}>
<AddIcon /> Add <AddIcon /> Add
</div> </button>
</div> </div>
<div <div
className="lists-main-container" className="lists-main-container"
ref={productsContainerRef} ref={productsContainerRef}
style={{ height: "120px" }} style={{ height: "120px" }}
> >
<div className="list-container"> <div className="list-container">
{products.map((product, index) => ( {products.map((product, index) => (
<div <div
key={product.productId} key={product.productId}
className={`list-item ${selectedProduct.productId === product.productId className={`list-item ${
? "active" selectedProduct.productId === product.productId
: "" ? "active"
}`} : ""
> }`}
<div >
className="value" {/* eslint-disable-next-line */}
onClick={() => <div
setSelectedProduct(product.productId, product.productName) className="value"
} onClick={() =>
> setSelectedProduct(product.productId, product.productName)
<input }
type="radio" >
name="products" <input
checked={selectedProduct.productId === product.productId} type="radio"
readOnly name="products"
/> checked={selectedProduct.productId === product.productId}
<RenameInput readOnly
value={product.productName}
onRename={(newName) =>
handleRenameProduct(product.productId, newName)
}
/>
</div>
{products.length > 1 && (
<div
className="remove-button"
onClick={() => handleRemoveProduct(product.productId)}
>
<RemoveIcon />
</div>
)}
</div>
))}
</div>
<div
className="resize-icon"
id="action-resize"
onMouseDown={(e) => handleResize(e, productsContainerRef)}
>
<ResizeHeightIcon />
</div>
</div>
</div>
<div className="simulation-process section">
<button
className="collapse-header-container"
onClick={() => setOpenObjects(!openObjects)}
>
<div className="header">Events</div>
<div className="arrow-container">
<ArrowIcon />
</div>
</button>
{openObjects &&
events.map((event, index) => <List key={index} val={event} />)}
</div>
<div className="compare-simulations-container">
<div className="compare-simulations-header">
Need to Compare Layout?
</div>
<div className="content">
Click <span>'Compare'</span> to review and analyze the layout
differences between them.
</div>
<div className="input">
<input type="button" value={"Compare"} className="submit" />
</div>
</div>
</div>
{selectedAsset && (
<RenderOverlay>
<EditWidgetOption
options={["Add to Product", "Remove from Product"]}
onClick={(option) => {
if (option === "Add to Product") {
handleAddEventToProduct({
event: getEventByModelUuid(selectedAsset.modelUuid),
addEvent,
selectedProduct,
clearSelectedAsset
});
} else {
handleRemoveEventFromProduct();
}
}}
/> />
</RenderOverlay> <RenameInput
)} value={product.productName}
onRename={(newName) =>
handleRenameProduct(product.productId, newName)
}
/>
</div>
{products.length > 1 && (
<button
className="remove-button"
onClick={() => handleRemoveProduct(product.productId)}
>
<RemoveIcon />
</button>
)}
</div>
))}
</div>
<button
className="resize-icon"
id="action-resize"
onMouseDown={(e: any) => handleResize(e, productsContainerRef)}
>
<ResizeHeightIcon />
</button>
</div>
</div> </div>
)
<div className="simulation-process section">
<button
className="collapse-header-container"
onClick={() => setOpenObjects(!openObjects)}
>
<div className="header">Events</div>
<div className="arrow-container">
<ArrowIcon />
</div>
</button>
{openObjects &&
events.map((event, index) => (
<List key={`${index}-${event.pathName}`} val={event} />
))}
</div>
<div className="compare-simulations-container">
<div className="compare-simulations-header">
Need to Compare Layout?
</div>
<div className="content">
Click '<span>Compare</span>' to review and analyze the layout
differences between them.
</div>
<div className="input">
<input type="button" value={"Compare"} className="submit" />
</div>
</div>
</div>
{selectedAsset && (
<RenderOverlay>
<EditWidgetOption
options={["Add to Product", "Remove from Product"]}
onClick={(option) => {
if (option === "Add to Product") {
handleAddEventToProduct({
event: getEventByModelUuid(selectedAsset.modelUuid),
addEvent,
selectedProduct,
clearSelectedAsset,
});
} else {
handleRemoveEventFromProduct();
}
}}
/>
</RenderOverlay>
)}
</div>
);
}; };
export default Simulations; export default Simulations;

View File

@@ -25,10 +25,8 @@ const RenameTooltip: React.FC<RenameTooltipProps> = ({ name, onSubmit }) => {
<div <div
className="rename-tool-tip" className="rename-tool-tip"
style={{ style={{
position: "absolute",
top: `${top}px`, top: `${top}px`,
left: `${left}px`, left: `${left}px`,
zIndex: 100,
}} }}
> >
<div className="header"> <div className="header">

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react"; import React from "react";
import { import {
useLeftData, useLeftData,
useTopData, useTopData,
@@ -28,13 +28,13 @@ const EditWidgetOption: React.FC<EditWidgetOptionProps> = ({
> >
<div className="context-menu-options"> <div className="context-menu-options">
{options.map((option, index) => ( {options.map((option, index) => (
<div <button
className="option" className="option"
key={index} key={`${index}-${option}`}
onClick={() => onClick(option)} onClick={() => onClick(option)}
> >
{option} {option}
</div> </button>
))} ))}
</div> </div>
</div> </div>

View File

@@ -64,8 +64,6 @@ const SimulationPlayer: React.FC = () => {
const handleMouseDown = () => { const handleMouseDown = () => {
isDragging.current = true; isDragging.current = true;
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
}; };
const handleMouseMove = (e: MouseEvent) => { const handleMouseMove = (e: MouseEvent) => {
@@ -80,11 +78,11 @@ const SimulationPlayer: React.FC = () => {
const handleMouseUp = () => { const handleMouseUp = () => {
isDragging.current = false; isDragging.current = false;
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
}; };
useEffect(() => { useEffect(() => {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => { return () => {
document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp); document.removeEventListener("mouseup", handleMouseUp);
@@ -109,24 +107,6 @@ const SimulationPlayer: React.FC = () => {
{ name: "process 9", completed: 90 }, // 90% completed { name: "process 9", completed: 90 }, // 90% completed
{ name: "process 10", completed: 30 }, // 30% completed { name: "process 10", completed: 30 }, // 30% completed
]; ];
// Move getRandomColor out of render
const getRandomColor = () => {
const letters = "0123456789ABCDEF";
let color = "#";
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
// Store colors for each process item
const [_, setProcessColors] = useState<string[]>([]);
// Generate colors on mount or when process changes
useEffect(() => {
const generatedColors = process.map(() => getRandomColor());
setProcessColors(generatedColors);
}, []);
const intervals = [10, 20, 30, 40, 50, 60]; // in minutes const intervals = [10, 20, 30, 40, 50, 60]; // in minutes
const totalSegments = intervals.length; const totalSegments = intervals.length;
@@ -218,7 +198,7 @@ const SimulationPlayer: React.FC = () => {
</div> </div>
</div> </div>
)} )}
{subModule === "simulations" && ( {subModule !== "analysis" && (
<div className="header"> <div className="header">
<InfoIcon /> <InfoIcon />
{playSimulation {playSimulation
@@ -281,7 +261,7 @@ const SimulationPlayer: React.FC = () => {
const segmentProgress = (index / totalSegments) * 100; const segmentProgress = (index / totalSegments) * 100;
const isFilled = progress >= segmentProgress; const isFilled = progress >= segmentProgress;
return ( return (
<React.Fragment key={index}> <React.Fragment key={`${index}-${label}`}>
<div className="label-dot-wrapper"> <div className="label-dot-wrapper">
<div className="label">{label} mins</div> <div className="label">{label} mins</div>
<div <div
@@ -360,6 +340,7 @@ const SimulationPlayer: React.FC = () => {
className="process-wrapper" className="process-wrapper"
style={{ padding: expand ? "0px" : "5px 35px" }} style={{ padding: expand ? "0px" : "5px 35px" }}
> >
{/* eslint-disable-next-line */}
<div <div
className="process-container" className="process-container"
ref={processWrapperRef} ref={processWrapperRef}
@@ -367,7 +348,7 @@ const SimulationPlayer: React.FC = () => {
> >
{process.map((item, index) => ( {process.map((item, index) => (
<div <div
key={index} key={`${index}-${item.name}`}
className="process" className="process"
style={{ style={{
width: `${item.completed}%`, width: `${item.completed}%`,

View File

@@ -1,220 +1,261 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from "react";
import { useFrame } from '@react-three/fiber'; import { useFrame } from "@react-three/fiber";
import * as THREE from 'three'; import * as THREE from "three";
import { Line } from '@react-three/drei'; import { Line } from "@react-three/drei";
import { import {
useAnimationPlaySpeed, useAnimationPlaySpeed,
usePauseButtonStore, usePauseButtonStore,
usePlayButtonStore, usePlayButtonStore,
useResetButtonStore useResetButtonStore,
} from '../../../../../store/usePlayButtonStore'; } from "../../../../../store/usePlayButtonStore";
function RoboticArmAnimator({ function RoboticArmAnimator({
HandleCallback, HandleCallback,
restPosition, restPosition,
ikSolver, ikSolver,
targetBone, targetBone,
armBot, armBot,
logStatus, logStatus,
path path,
}: any) { }: any) {
const progressRef = useRef(0); const progressRef = useRef(0);
const curveRef = useRef<THREE.Vector3[] | null>(null); const curveRef = useRef<THREE.Vector3[] | null>(null);
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); const [currentPath, setCurrentPath] = useState<[number, number, number][]>(
const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]); []
const [customCurvePoints, setCustomCurvePoints] = useState<THREE.Vector3[] | null>(null); );
const [circlePoints, setCirclePoints] = useState<[number, number, number][]>(
[]
);
const [customCurvePoints, setCustomCurvePoints] = useState<
THREE.Vector3[] | null
>(null);
// Zustand stores // Zustand stores
const { isPlaying } = usePlayButtonStore(); const { isPlaying } = usePlayButtonStore();
const { isPaused } = usePauseButtonStore(); const { isPaused } = usePauseButtonStore();
const { isReset } = useResetButtonStore(); const { isReset } = useResetButtonStore();
const { speed } = useAnimationPlaySpeed(); const { speed } = useAnimationPlaySpeed();
// Update path state whenever `path` prop changes // Update path state whenever `path` prop changes
useEffect(() => { useEffect(() => {
setCurrentPath(path); setCurrentPath(path);
}, [path]); }, [path]);
// Reset logic when `isPlaying` changes // Reset logic when `isPlaying` changes
useEffect(() => { useEffect(() => {
if (!isPlaying) { if (!isPlaying) {
setCurrentPath([]); setCurrentPath([]);
curveRef.current = null; curveRef.current = null;
}
}, [isPlaying]);
// Handle circle points based on armBot position
useEffect(() => {
const points = generateRingPoints(1.6, 64)
setCirclePoints(points);
}, [armBot.position]);
function generateRingPoints(radius: any, segments: any) {
const points: [number, number, number][] = [];
for (let i = 0; i < segments; i++) {
// Calculate angle for current segment
const angle = (i / segments) * Math.PI * 2;
// Calculate x and z coordinates (y remains the same for a flat ring)
const x = Math.cos(angle) * radius;
const z = Math.sin(angle) * radius;
points.push([x, 1.5, z]);
}
return points;
} }
}, [isPlaying]);
const findNearestIndex = (nearestPoint: [number, number, number], points: [number, number, number][], epsilon = 1e-6) => { // Handle circle points based on armBot position
for (let i = 0; i < points.length; i++) { useEffect(() => {
const [x, y, z] = points[i]; const points = generateRingPoints(1.6, 64);
if ( setCirclePoints(points);
Math.abs(x - nearestPoint[0]) < epsilon && }, [armBot.position]);
Math.abs(y - nearestPoint[1]) < epsilon &&
Math.abs(z - nearestPoint[2]) < epsilon function generateRingPoints(radius: any, segments: any) {
) { const points: [number, number, number][] = [];
return i; // Found the matching index for (let i = 0; i < segments; i++) {
} // Calculate angle for current segment
const angle = (i / segments) * Math.PI * 2;
// Calculate x and z coordinates (y remains the same for a flat ring)
const x = Math.cos(angle) * radius;
const z = Math.sin(angle) * radius;
points.push([x, 1.5, z]);
}
return points;
}
const findNearestIndex = (
nearestPoint: [number, number, number],
points: [number, number, number][],
epsilon = 1e-6
) => {
for (let i = 0; i < points.length; i++) {
const [x, y, z] = points[i];
if (
Math.abs(x - nearestPoint[0]) < epsilon &&
Math.abs(y - nearestPoint[1]) < epsilon &&
Math.abs(z - nearestPoint[2]) < epsilon
) {
return i; // Found the matching index
}
}
return -1; // Not found
};
// Handle nearest points and final path (including arc points)
useEffect(() => {
if (circlePoints.length > 0 && currentPath.length > 0) {
const start = currentPath[0];
const end = currentPath[currentPath.length - 1];
const raisedStart = [start[0], start[1] + 0.5, start[2]] as [
number,
number,
number
];
const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [
number,
number,
number
];
const findNearest = (target: [number, number, number]) => {
return circlePoints.reduce((nearest, point) => {
const distance = Math.hypot(
target[0] - point[0],
target[1] - point[1],
target[2] - point[2]
);
const nearestDistance = Math.hypot(
target[0] - nearest[0],
target[1] - nearest[1],
target[2] - nearest[2]
);
return distance < nearestDistance ? point : nearest;
}, circlePoints[0]);
};
const nearestToStart = findNearest(raisedStart);
const nearestToEnd = findNearest(raisedEnd);
const indexOfNearestStart = findNearestIndex(
nearestToStart,
circlePoints
);
const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints);
// Find clockwise and counter-clockwise distances
const clockwiseDistance =
(indexOfNearestEnd - indexOfNearestStart + 64) % 64;
const counterClockwiseDistance =
(indexOfNearestStart - indexOfNearestEnd + 64) % 64;
const clockwiseIsShorter = clockwiseDistance <= counterClockwiseDistance;
// Collect arc points between start and end
let arcPoints: [number, number, number][] = [];
if (clockwiseIsShorter) {
if (indexOfNearestStart <= indexOfNearestEnd) {
arcPoints = circlePoints.slice(
indexOfNearestStart,
indexOfNearestEnd + 1
);
} else {
// Wrap around
arcPoints = [
...circlePoints.slice(indexOfNearestStart, 64),
...circlePoints.slice(0, indexOfNearestEnd + 1),
];
} }
return -1; // Not found } else if (indexOfNearestStart >= indexOfNearestEnd) {
}; for (
let i = indexOfNearestStart;
i !== (indexOfNearestEnd - 1 + 64) % 64;
// Handle nearest points and final path (including arc points) i = (i - 1 + 64) % 64
useEffect(() => { ) {
if (circlePoints.length > 0 && currentPath.length > 0) { arcPoints.push(circlePoints[i]);
const start = currentPath[0];
const end = currentPath[currentPath.length - 1];
const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number];
const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number];
const findNearest = (target: [number, number, number]) => {
return circlePoints.reduce((nearest, point) => {
const distance = Math.hypot(
target[0] - point[0],
target[1] - point[1],
target[2] - point[2]
);
const nearestDistance = Math.hypot(
target[0] - nearest[0],
target[1] - nearest[1],
target[2] - nearest[2]
);
return distance < nearestDistance ? point : nearest;
}, circlePoints[0]);
};
const nearestToStart = findNearest(raisedStart);
const nearestToEnd = findNearest(raisedEnd);
const indexOfNearestStart = findNearestIndex(nearestToStart, circlePoints);
const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints);
// Find clockwise and counter-clockwise distances
const clockwiseDistance = (indexOfNearestEnd - indexOfNearestStart + 64) % 64;
const counterClockwiseDistance = (indexOfNearestStart - indexOfNearestEnd + 64) % 64;
const clockwiseIsShorter = clockwiseDistance <= counterClockwiseDistance;
// Collect arc points between start and end
let arcPoints: [number, number, number][] = [];
if (clockwiseIsShorter) {
if (indexOfNearestStart <= indexOfNearestEnd) {
arcPoints = circlePoints.slice(indexOfNearestStart, indexOfNearestEnd + 1);
} else {
// Wrap around
arcPoints = [
...circlePoints.slice(indexOfNearestStart, 64),
...circlePoints.slice(0, indexOfNearestEnd + 1)
];
}
} else {
if (indexOfNearestStart >= indexOfNearestEnd) {
for (let i = indexOfNearestStart; i !== (indexOfNearestEnd - 1 + 64) % 64; i = (i - 1 + 64) % 64) {
arcPoints.push(circlePoints[i]);
}
} else {
for (let i = indexOfNearestStart; i !== (indexOfNearestEnd - 1 + 64) % 64; i = (i - 1 + 64) % 64) {
arcPoints.push(circlePoints[i]);
}
}
}
// Continue your custom path logic
const pathVectors = [
new THREE.Vector3(start[0], start[1], start[2]), // start
new THREE.Vector3(raisedStart[0], raisedStart[1], raisedStart[2]), // lift up
new THREE.Vector3(nearestToStart[0], raisedStart[1], nearestToStart[2]), // move to arc start
...arcPoints.map(point => new THREE.Vector3(point[0], raisedStart[1], point[2])),
new THREE.Vector3(nearestToEnd[0], raisedEnd[1], nearestToEnd[2]), // move from arc end
new THREE.Vector3(raisedEnd[0], raisedEnd[1], raisedEnd[2]), // lowered end
new THREE.Vector3(end[0], end[1], end[2]) // end
];
const customCurve = new THREE.CatmullRomCurve3(pathVectors, false, 'centripetal', 1);
const generatedPoints = customCurve.getPoints(100);
setCustomCurvePoints(generatedPoints);
} }
}, [circlePoints, currentPath]); }
// Frame update for animation // Continue your custom path logic
useFrame((_, delta) => { const pathVectors = [
if (!ikSolver) return; new THREE.Vector3(start[0], start[1], start[2]), // start
new THREE.Vector3(raisedStart[0], raisedStart[1], raisedStart[2]), // lift up
new THREE.Vector3(nearestToStart[0], raisedStart[1], nearestToStart[2]), // move to arc start
...arcPoints.map(
(point) => new THREE.Vector3(point[0], raisedStart[1], point[2])
),
new THREE.Vector3(nearestToEnd[0], raisedEnd[1], nearestToEnd[2]), // move from arc end
new THREE.Vector3(raisedEnd[0], raisedEnd[1], raisedEnd[2]), // lowered end
new THREE.Vector3(end[0], end[1], end[2]), // end
];
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone); const customCurve = new THREE.CatmullRomCurve3(
if (!bone) return; pathVectors,
false,
"centripetal",
1
);
const generatedPoints = customCurve.getPoints(100);
setCustomCurvePoints(generatedPoints);
}
}, [circlePoints, currentPath]);
if (isPlaying) { // Frame update for animation
if (!isPaused && customCurvePoints && currentPath.length > 0) { useFrame((_, delta) => {
const curvePoints = customCurvePoints; if (!ikSolver) return;
const speedAdjustedProgress = progressRef.current + (speed * armBot.speed);
const index = Math.floor(speedAdjustedProgress);
if (index >= curvePoints.length) { const bone = ikSolver.mesh.skeleton.bones.find(
// Reached the end of the curve (b: any) => b.name === targetBone
HandleCallback();
setCurrentPath([]);
curveRef.current = null;
progressRef.current = 0;
} else {
const point = curvePoints[index];
bone.position.copy(point);
progressRef.current = speedAdjustedProgress;
}
} else if (isPaused) {
logStatus(armBot.modelUuid, 'Simulation Paused');
}
ikSolver.update();
} else if (!isPlaying && currentPath.length === 0) {
// Not playing anymore, reset to rest
bone.position.copy(restPosition);
ikSolver.update();
}
});
return (
<>
{customCurvePoints && currentPath && isPlaying && (
<mesh rotation={armBot.rotation} position={armBot.position}>
<Line
points={customCurvePoints.map((p) => [p.x, p.y, p.z] as [number, number, number])}
color="green"
lineWidth={5}
dashed={false}
/>
</mesh>
)}
<mesh position={[armBot.position[0], armBot.position[1] + 1.5, armBot.position[2]]} rotation={[-Math.PI / 2, 0, 0]}>
<ringGeometry args={[1.59, 1.61, 64]} />
<meshBasicMaterial color="green" side={THREE.DoubleSide} />
</mesh>
</>
); );
if (!bone) return;
if (isPlaying) {
if (!isPaused && customCurvePoints && currentPath.length > 0) {
const curvePoints = customCurvePoints;
const speedAdjustedProgress =
progressRef.current + speed * armBot.speed;
const index = Math.floor(speedAdjustedProgress);
if (index >= curvePoints.length) {
// Reached the end of the curve
HandleCallback();
setCurrentPath([]);
curveRef.current = null;
progressRef.current = 0;
} else {
const point = curvePoints[index];
bone.position.copy(point);
progressRef.current = speedAdjustedProgress;
}
} else if (isPaused) {
logStatus(armBot.modelUuid, "Simulation Paused");
}
ikSolver.update();
} else if (!isPlaying && currentPath.length === 0) {
// Not playing anymore, reset to rest
bone.position.copy(restPosition);
ikSolver.update();
}
});
return (
<>
{customCurvePoints && currentPath && isPlaying && (
<mesh rotation={armBot.rotation} position={armBot.position}>
<Line
points={customCurvePoints.map(
(p) => [p.x, p.y, p.z] as [number, number, number]
)}
color="green"
lineWidth={5}
dashed={false}
/>
</mesh>
)}
<mesh
position={[
armBot.position[0],
armBot.position[1] + 1.5,
armBot.position[2],
]}
rotation={[-Math.PI / 2, 0, 0]}
visible={false}
>
<ringGeometry args={[1.59, 1.61, 64]} />
<meshBasicMaterial color="green" side={THREE.DoubleSide} />
</mesh>
</>
);
} }
export default RoboticArmAnimator; export default RoboticArmAnimator;