Complete 3axis tutorial native and Web/WASM parity tools

This commit is contained in:
2026-08-24 14:01:30 +08:00
parent 938654b19d
commit 7e90142b63
147 changed files with 17639 additions and 557 deletions

View File

@@ -2,7 +2,7 @@ import { useEffect, useRef } from "react";
import * as THREE from "three";
type ViewMode = "z" | "z2" | "x" | "y" | "y2" | "p";
type Segment = { start?: Record<string, number>; end?: Record<string, number> };
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 };
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) {
@@ -36,15 +36,32 @@ export function ThreeMachineView({ axes, view = "p", zoom = 1, rotateMode = fals
grid.name = "axis-grid";
scene.add(grid);
scene.add(new THREE.AxesHelper(2.2));
const table = new THREE.Mesh(new THREE.BoxGeometry(5.2, 0.18, 3.6), new THREE.MeshStandardMaterial({ color: 0x263740, roughness: 0.8 }));
table.position.y = -0.1;
scene.add(table);
const gantry = new THREE.Mesh(new THREE.BoxGeometry(0.18, 2.2, 0.18), new THREE.MeshStandardMaterial({ color: 0x647c88 }));
gantry.position.set(0, 1, 0);
scene.add(gantry);
const tool = new THREE.Mesh(new THREE.ConeGeometry(0.12, 0.55, 16), new THREE.MeshStandardMaterial({ color: 0xc9e86d, emissive: 0x243200 }));
scene.add(tool);
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);
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);
@@ -65,16 +82,34 @@ export function ThreeMachineView({ axes, view = "p", zoom = 1, rotateMode = fals
let frame = 0;
const render = () => {
const [x = 0, y = 0, z = 0] = axesRef.current;
tool.position.set(THREE.MathUtils.clamp(Number(x) || 0, -2.3, 2.3), 0.35 + THREE.MathUtils.clamp(Number(z) || 0, -1, 2), THREE.MathUtils.clamp(Number(y) || 0, -1.4, 1.4));
const bounds = segmentsRef.current.reduce((box, segment) => {
for (const point of [segment.start ?? {}, segment.end ?? {}]) {
for (const axis of ["x", "y", "z"] as const) {
const value = Number(point[axis] ?? 0);
if (Number.isFinite(value)) { box.min[axis] = Math.min(box.min[axis], value); box.max[axis] = Math.max(box.max[axis], value); }
}
}
return box;
}, { min: { x: 0, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 } });
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;
pathGroup.visible = layersRef.current.showPath || layersRef.current.showLive;
pathGroup.visible = layersRef.current.showPath;
livePathGroup.visible = layersRef.current.showLive;
pathGroup.clear();
const material = new THREE.LineBasicMaterial({ color: layersRef.current.showLive ? 0x5fc4d8 : 0x5c80a0 });
livePathGroup.clear();
const completedMaterial = new THREE.LineBasicMaterial({ color: 0x5c80a0 });
const liveMaterial = new THREE.LineBasicMaterial({ color: 0x5fc4d8 });
for (const segment of segmentsRef.current) {
const start = segment.start ?? {}; const end = segment.end ?? {};
const points = [new THREE.Vector3(Number(start.x ?? 0), Number(start.z ?? 0), Number(start.y ?? 0)), new THREE.Vector3(Number(end.x ?? 0), Number(end.z ?? 0), Number(end.y ?? 0))];
pathGroup.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(points), material));
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 target = segment.active ? livePathGroup : pathGroup;
target.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(points), segment.active ? liveMaterial : completedMaterial));
}
if (layersRef.current.showLimits) {
const limits = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(5, 2, 3.5)), new THREE.LineBasicMaterial({ color: 0xd87d6c }));
@@ -89,7 +124,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, 0);
camera.lookAt(0, 1.8, 0);
scene.rotation.y = rotateRef.current ? (performance.now() / 5000) % (Math.PI * 2) : 0;
renderer.render(scene, camera);
frame = requestAnimationFrame(render);