Match 3axis preview to AXIS backplot
This commit is contained in:
@@ -2,23 +2,22 @@ import { useEffect, useRef } from "react";
|
||||
import * as THREE from "three";
|
||||
|
||||
type ViewMode = "z" | "z2" | "x" | "y" | "y2" | "p";
|
||||
type Segment = { id?: number; source_line?: number | null; active?: boolean; rapid?: boolean; start?: Record<string, number>; end?: Record<string, number> };
|
||||
type Props = { axes: number[]; view?: ViewMode; zoom?: number; rotateMode?: boolean; segments?: Segment[]; showGrid?: boolean; showTool?: boolean; showPath?: boolean; showLive?: boolean; showLimits?: boolean; showVelocity?: boolean; showOffsets?: boolean; showExtents?: boolean };
|
||||
type Point = Record<string, number>;
|
||||
type Segment = { id?: number; source_line?: number | null; active?: boolean; rapid?: boolean; motion_type?: number; turn?: number; center?: Point; normal?: Point; start?: Point; end?: Point };
|
||||
type Props = { axes: number[]; view?: ViewMode; zoom?: number; rotateMode?: boolean; segments?: Segment[]; showPath?: boolean; showLive?: boolean; showLimits?: boolean; showVelocity?: boolean; showOffsets?: boolean; showExtents?: boolean };
|
||||
|
||||
export function ThreeMachineView({ axes, view = "p", zoom = 1, rotateMode = false, segments = [], showGrid = true, showTool = true, showPath = true, showLive = true, showLimits = false, showVelocity = false, showOffsets = false, showExtents = false }: Props) {
|
||||
export function ThreeMachineView({ axes, view = "p", zoom = 1, rotateMode = false, segments = [], showPath = true, showLive = true, showLimits = false, showVelocity = false, showOffsets = false, showExtents = false }: Props) {
|
||||
const host = useRef<HTMLDivElement>(null);
|
||||
const axesRef = useRef(axes);
|
||||
const viewRef = useRef(view);
|
||||
const zoomRef = useRef(zoom);
|
||||
const rotateRef = useRef(rotateMode);
|
||||
const segmentsRef = useRef(segments);
|
||||
const layersRef = useRef({ showGrid, showTool, showPath, showLive, showLimits, showVelocity, showOffsets, showExtents });
|
||||
axesRef.current = axes;
|
||||
const layersRef = useRef({ showPath, showLive, showLimits, showVelocity, showOffsets, showExtents });
|
||||
viewRef.current = view;
|
||||
zoomRef.current = zoom;
|
||||
rotateRef.current = rotateMode;
|
||||
segmentsRef.current = segments;
|
||||
layersRef.current = { showGrid, showTool, showPath, showLive, showLimits, showVelocity, showOffsets, showExtents };
|
||||
layersRef.current = { showPath, showLive, showLimits, showVelocity, showOffsets, showExtents };
|
||||
|
||||
useEffect(() => {
|
||||
const element = host.current;
|
||||
@@ -32,43 +31,14 @@ export function ThreeMachineView({ axes, view = "p", zoom = 1, rotateMode = fals
|
||||
renderer.setPixelRatio(Math.min(globalThis.devicePixelRatio || 1, 2));
|
||||
element.appendChild(renderer.domElement);
|
||||
|
||||
const grid = new THREE.GridHelper(8, 16, 0x405765, 0x1e303b);
|
||||
grid.name = "axis-grid";
|
||||
scene.add(grid);
|
||||
scene.add(new THREE.AxesHelper(2.2));
|
||||
const machine = new THREE.Group();
|
||||
machine.name = "axis-3axis-tutorial-model";
|
||||
const frameMaterial = new THREE.MeshStandardMaterial({ color: 0x7d8b91, roughness: 0.82 });
|
||||
const xMaterial = new THREE.MeshStandardMaterial({ color: 0x3d6fc4, roughness: 0.72 });
|
||||
const yMaterial = new THREE.MeshStandardMaterial({ color: 0x3f9b69, roughness: 0.72 });
|
||||
const zMaterial = new THREE.MeshStandardMaterial({ color: 0xd3ba45, roughness: 0.7 });
|
||||
const toolMaterial = new THREE.MeshStandardMaterial({ color: 0xc9e86d, emissive: 0x243200 });
|
||||
const sourceScale = 0.01;
|
||||
const frameAssembly = new THREE.Group(); frameAssembly.name = "frame"; machine.add(frameAssembly);
|
||||
const base = new THREE.Mesh(new THREE.BoxGeometry(2, 0.2, 5.6), frameMaterial); base.position.set(0, -0.1, 0); frameAssembly.add(base);
|
||||
const column = new THREE.Mesh(new THREE.BoxGeometry(1.2, 4, 0.6), frameMaterial); column.position.set(0, 2, -2.3); frameAssembly.add(column);
|
||||
const top = new THREE.Mesh(new THREE.BoxGeometry(1.6, 0.4, 1.2), frameMaterial); top.position.set(0, 4.2, -2.2); frameAssembly.add(top);
|
||||
const yAssembly = new THREE.Group(); yAssembly.name = "yassembly"; machine.add(yAssembly);
|
||||
const yBase = new THREE.Mesh(new THREE.BoxGeometry(2, 0.1, 2), yMaterial); yBase.position.set(0, 0.25, 0); yAssembly.add(yBase);
|
||||
const xAssembly = new THREE.Group(); xAssembly.name = "xassembly"; yAssembly.add(xAssembly);
|
||||
const xBase = new THREE.Mesh(new THREE.BoxGeometry(10, 0.3, 2), xMaterial); xBase.position.set(0, 0.35, 0); xAssembly.add(xBase);
|
||||
const work = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.12, 1.2), new THREE.MeshStandardMaterial({ color: 0x8f9da4, roughness: 0.9 }));
|
||||
work.position.set(0, 0.56, 0); xAssembly.add(work);
|
||||
const zAssembly = new THREE.Group(); zAssembly.name = "zassembly"; machine.add(zAssembly);
|
||||
const zBody = new THREE.Mesh(new THREE.BoxGeometry(0.6, 1.0, 0.9), zMaterial); zBody.position.set(0, 3.4, 0); zAssembly.add(zBody);
|
||||
const spindle = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.3, 1.0, 24), zMaterial); spindle.position.set(0, 2.9, 0); zAssembly.add(spindle);
|
||||
const cat30 = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0.3, 0.35, 20), new THREE.MeshStandardMaterial({ color: 0xd15a52 })); cat30.position.set(0, 2.35, 0); zAssembly.add(cat30);
|
||||
const tool = new THREE.Mesh(new THREE.ConeGeometry(0.12, 0.8, 16), toolMaterial); tool.position.set(0, 1.85, 0); zAssembly.add(tool);
|
||||
scene.add(machine);
|
||||
// AXIS preview is a backplot of the source program. The Vismach solid
|
||||
// model belongs to the machine view and must not be drawn in this pane.
|
||||
const axesHelper = new THREE.AxesHelper(2.2);
|
||||
axesHelper.name = "axis-coordinate-axes";
|
||||
scene.add(axesHelper);
|
||||
const pathGroup = new THREE.Group(); pathGroup.name = "axis-toolpath"; scene.add(pathGroup);
|
||||
const livePathGroup = new THREE.Group(); livePathGroup.name = "axis-live-toolpath"; scene.add(livePathGroup);
|
||||
const overlayGroup = new THREE.Group(); overlayGroup.name = "axis-overlays"; scene.add(overlayGroup);
|
||||
const ambient = new THREE.HemisphereLight(0xc8e4ec, 0x111820, 1.8);
|
||||
scene.add(ambient);
|
||||
const key = new THREE.DirectionalLight(0xffffff, 2.2);
|
||||
key.position.set(3, 6, 4);
|
||||
scene.add(key);
|
||||
|
||||
const resize = () => {
|
||||
const width = Math.max(element.clientWidth, 320);
|
||||
const height = Math.max(element.clientHeight, 280);
|
||||
@@ -81,7 +51,6 @@ export function ThreeMachineView({ axes, view = "p", zoom = 1, rotateMode = fals
|
||||
resize();
|
||||
let frame = 0;
|
||||
const render = () => {
|
||||
const [x = 0, y = 0, z = 0] = axesRef.current;
|
||||
const bounds = segmentsRef.current.reduce((box, segment) => {
|
||||
for (const point of [segment.start ?? {}, segment.end ?? {}]) {
|
||||
for (const axis of ["x", "y", "z"] as const) {
|
||||
@@ -94,22 +63,46 @@ export function ThreeMachineView({ axes, view = "p", zoom = 1, rotateMode = fals
|
||||
const span = Math.max(bounds.max.x - bounds.min.x, bounds.max.y - bounds.min.y, bounds.max.z - bounds.min.z, 1);
|
||||
const scale = Math.min(0.12, 4.2 / span);
|
||||
const center = { x: (bounds.min.x + bounds.max.x) / 2, y: (bounds.min.y + bounds.max.y) / 2, z: (bounds.min.z + bounds.max.z) / 2 };
|
||||
xAssembly.position.x = Number(x || 0) * sourceScale;
|
||||
yAssembly.position.z = Number(y || 0) * sourceScale;
|
||||
zAssembly.position.y = Number(z || 0) * sourceScale;
|
||||
grid.visible = layersRef.current.showGrid;
|
||||
tool.visible = layersRef.current.showTool;
|
||||
// Keep the live coordinates available for the backplot marker without
|
||||
// introducing a solid tool or machine mesh into the source preview.
|
||||
pathGroup.visible = layersRef.current.showPath;
|
||||
livePathGroup.visible = layersRef.current.showLive;
|
||||
pathGroup.clear();
|
||||
livePathGroup.clear();
|
||||
const completedMaterial = new THREE.LineBasicMaterial({ color: 0x5c80a0 });
|
||||
const liveMaterial = new THREE.LineBasicMaterial({ color: 0x5fc4d8 });
|
||||
const completedMaterial = new THREE.LineBasicMaterial({ color: 0xf0f0f0 });
|
||||
const liveMaterial = new THREE.LineBasicMaterial({ color: 0xff6666 });
|
||||
const traverseMaterial = new THREE.LineBasicMaterial({ color: 0x4d8080 });
|
||||
const toView = (point: Point) => new THREE.Vector3(
|
||||
(Number(point.x ?? 0) - center.x) * scale,
|
||||
(Number(point.z ?? 0) - center.z) * scale,
|
||||
(Number(point.y ?? 0) - center.y) * scale,
|
||||
);
|
||||
const arcPoints = (segment: Segment, start: Point, end: Point, centerPoint: Point, normal: Point) => {
|
||||
const startVector = new THREE.Vector3(Number(start.x ?? 0) - Number(centerPoint.x ?? 0), Number(start.y ?? 0) - Number(centerPoint.y ?? 0), Number(start.z ?? 0) - Number(centerPoint.z ?? 0));
|
||||
const endVector = new THREE.Vector3(Number(end.x ?? 0) - Number(centerPoint.x ?? 0), Number(end.y ?? 0) - Number(centerPoint.y ?? 0), Number(end.z ?? 0) - Number(centerPoint.z ?? 0));
|
||||
const axis = new THREE.Vector3(Number(normal.x ?? 0), Number(normal.y ?? 0), Number(normal.z ?? 0));
|
||||
if (!Number.isFinite(axis.length()) || axis.length() === 0 || startVector.length() === 0) return [toView(start), toView(end)];
|
||||
axis.normalize();
|
||||
let angle = Math.atan2(axis.dot(startVector.clone().cross(endVector)), startVector.dot(endVector));
|
||||
const direction = Number(segment.turn ?? 1) < 0 ? -1 : 1;
|
||||
if (Math.abs(angle) < 1e-8 && startVector.distanceTo(endVector) < 1e-7) angle = direction * Math.PI * 2;
|
||||
else if (angle * direction < 0) angle += direction * Math.PI * 2;
|
||||
const quaternion = new THREE.Quaternion();
|
||||
const points: THREE.Vector3[] = [];
|
||||
for (let index = 0; index <= 32; index += 1) {
|
||||
const rotated = startVector.clone().applyQuaternion(quaternion.setFromAxisAngle(axis, angle * index / 32));
|
||||
points.push(toView({ x: Number(centerPoint.x ?? 0) + rotated.x, y: Number(centerPoint.y ?? 0) + rotated.y, z: Number(centerPoint.z ?? 0) + rotated.z }));
|
||||
}
|
||||
return points;
|
||||
};
|
||||
for (const segment of segmentsRef.current) {
|
||||
const start = segment.start ?? {}; const end = segment.end ?? {};
|
||||
const points = [new THREE.Vector3((Number(start.x ?? 0) - center.x) * scale, (Number(start.z ?? 0) - center.z) * scale, (Number(start.y ?? 0) - center.y) * scale), new THREE.Vector3((Number(end.x ?? 0) - center.x) * scale, (Number(end.z ?? 0) - center.z) * scale, (Number(end.y ?? 0) - center.y) * scale)];
|
||||
const points = segment.center && segment.normal && segment.motion_type === 2
|
||||
? arcPoints(segment, start, end, segment.center, segment.normal)
|
||||
: [toView(start), toView(end)];
|
||||
const target = segment.active ? livePathGroup : pathGroup;
|
||||
target.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(points), segment.active ? liveMaterial : completedMaterial));
|
||||
const material = segment.active ? liveMaterial : segment.motion_type === 0 || segment.rapid ? traverseMaterial : completedMaterial;
|
||||
target.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(points), material));
|
||||
}
|
||||
if (layersRef.current.showLimits) {
|
||||
const limits = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(5, 2, 3.5)), new THREE.LineBasicMaterial({ color: 0xd87d6c }));
|
||||
@@ -124,7 +117,7 @@ export function ThreeMachineView({ axes, view = "p", zoom = 1, rotateMode = fals
|
||||
else if (selectedView === "y") camera.position.set(0.01, 2, distance);
|
||||
else if (selectedView === "y2") camera.position.set(0.01, 2, -distance);
|
||||
else camera.position.set(6 / Math.max(0.5, zoomRef.current), 5 / Math.max(0.5, zoomRef.current), 7 / Math.max(0.5, zoomRef.current));
|
||||
camera.lookAt(0, 1.8, 0);
|
||||
camera.lookAt(0, 0, 0);
|
||||
scene.rotation.y = rotateRef.current ? (performance.now() / 5000) % (Math.PI * 2) : 0;
|
||||
renderer.render(scene, camera);
|
||||
frame = requestAnimationFrame(render);
|
||||
@@ -133,5 +126,9 @@ export function ThreeMachineView({ axes, view = "p", zoom = 1, rotateMode = fals
|
||||
return () => { cancelAnimationFrame(frame); observer.disconnect(); renderer.dispose(); element.replaceChildren(); };
|
||||
}, []);
|
||||
|
||||
return <div className="three-viewer" ref={host} aria-label="Three.js LinuxCNC machine visualization"><span className="three-viewer-label">Three.js / authoritative pose</span></div>;
|
||||
const [x = 0, y = 0, z = 0] = axes;
|
||||
const format = (value: number) => Number(value || 0).toFixed(3);
|
||||
return <div className="three-viewer" ref={host} aria-label="AXIS source program backplot">
|
||||
<pre className="axis-preview-hud" aria-label="AXIS preview coordinates">{`X: ${format(x)} DTG X: ${format(-x)}\nY: ${format(y)} DTG Y: ${format(-y)}\nZ: ${format(z)} DTG Z: ${format(-z)}\nG54 X: ${format(x)} G92 X: 0.000\nG54 Y: ${format(y)} G92 Y: 0.000\nG54 Z: ${format(z)} G92 Z: 0.000\nG54 R: 0.000\nTLO X: 0.000\nTLO Y: 0.000\nTLO Z: 0.000\nVel: 0.000`}</pre>
|
||||
</div>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user