Add dual canvas XYZBC and refined machine visualization
This commit is contained in:
@@ -1,115 +1,198 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import * as THREE from "three";
|
||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
||||
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
|
||||
import { axisIcon } from "./sourceAssets";
|
||||
import { buildXyzacMachine } from "./xyzac-machine-model";
|
||||
import { projectXyzacVisual, type XyzacVisualSource, type XyzacVisualState } from "./xyzac-visual-adapter";
|
||||
|
||||
type Point = Record<string, number>;
|
||||
type Segment = { id?: number; active?: boolean; motion_type?: number; turn?: number; center?: Point; normal?: Point; samples?: Point[]; start?: Point; end?: Point };
|
||||
type Segment = { id?: number; active?: boolean; motion_type?: number; samples?: Point[]; start?: Point; end?: Point };
|
||||
type Props = { visual: XyzacVisualState; toolpath?: Segment[]; view?: "z" | "z2" | "x" | "y" | "y2" | "p"; zoom?: number; rotateMode?: boolean; showPath?: boolean; showLive?: boolean; showLimits?: boolean; showVelocity?: boolean; showOffsets?: boolean; showExtents?: boolean };
|
||||
type Controller = Record<string, () => void>;
|
||||
type Host = HTMLDivElement & { xyzacMachineController?: Controller };
|
||||
const toView = (point: Point) => new THREE.Vector3(Number(point.x ?? 0) * .01, Number(point.z ?? 0) * .01, Number(point.y ?? 0) * .01);
|
||||
|
||||
const DEG = Math.PI / 180;
|
||||
const MM = 0.01;
|
||||
|
||||
function material(color: number) { return new THREE.MeshStandardMaterial({ color, roughness: .78, metalness: .12 }); }
|
||||
function sourcePosition(x: number, y: number, z: number) { return new THREE.Vector3(x * MM, z * MM, y * MM); }
|
||||
function sourceBox(width: number, depth: number, height: number, color: number) { return new THREE.Mesh(new THREE.BoxGeometry(width * MM, height * MM, depth * MM), material(color)); }
|
||||
function sourceCylinderZ(radius: number, height: number, color: number) { return new THREE.Mesh(new THREE.CylinderGeometry(radius * MM, radius * MM, height * MM, 32), material(color)); }
|
||||
function translate(name: string, object: THREE.Object3D, x: number, y: number, z: number) { object.name = name; object.position.copy(sourcePosition(x, y, z)); return object; }
|
||||
|
||||
export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotateMode = false, showPath = true, showLive = true, showLimits = false, showVelocity = false, showOffsets = false, showExtents = false }: Props) {
|
||||
const host = useRef<HTMLDivElement>(null);
|
||||
export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotateMode = false, showPath = false, showLive = false, showLimits = false, showVelocity = false, showOffsets = false, showExtents = false }: Props) {
|
||||
const host = useRef<Host>(null);
|
||||
const visualRef = useRef(visual); visualRef.current = visual;
|
||||
const toolpathRef = useRef(toolpath); toolpathRef.current = toolpath;
|
||||
const viewRef = useRef(view); viewRef.current = view;
|
||||
const zoomRef = useRef(zoom); zoomRef.current = zoom;
|
||||
const rotateRef = useRef(rotateMode); rotateRef.current = rotateMode;
|
||||
const layersRef = useRef({ showPath, showLive, showLimits, showVelocity, showOffsets, showExtents });
|
||||
layersRef.current = { showPath, showLive, showLimits, showVelocity, showOffsets, showExtents };
|
||||
const settings = useRef({ toolpath, view, zoom, rotateMode, showPath, showLive, showLimits, showOffsets, showExtents });
|
||||
settings.current = { toolpath, view, zoom, rotateMode, showPath, showLive, showLimits, showOffsets, showExtents };
|
||||
|
||||
useEffect(() => {
|
||||
const element = host.current;
|
||||
if (!element) return;
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x081017);
|
||||
scene.add(new THREE.HemisphereLight(0xe6f2ff, 0x202830, 1.65));
|
||||
const key = new THREE.DirectionalLight(0xffffff, 2.1); key.position.set(5, 8, 10); scene.add(key);
|
||||
const camera = new THREE.PerspectiveCamera(38, 1, .1, 100);
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false, preserveDrawingBuffer: true });
|
||||
renderer.setPixelRatio(Math.min(globalThis.devicePixelRatio || 1, 2));
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
renderer.toneMappingExposure = 1;
|
||||
renderer.shadowMap.enabled = true;
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
renderer.domElement.dataset.engine = "three.js r185 / xyzac-trt-vismach";
|
||||
element.appendChild(renderer.domElement);
|
||||
|
||||
const machine = new THREE.Group(); machine.name = "xyzac-machine"; scene.add(machine);
|
||||
machine.add(translate("base", sourceBox(240, 260, 150, 0x43616b), 0, 30, -175));
|
||||
machine.add(translate("column", sourceBox(100, 100, 510, 0x3b7a72), 0, 150, 5));
|
||||
const head = new THREE.Group(); head.name = "head"; head.position.copy(sourcePosition(0, 0, 200)); machine.add(head);
|
||||
head.add(translate("head-body", sourceBox(60, 60, 135, 0x277c83), 0, 0, 87.5));
|
||||
head.add(translate("motor", sourceCylinderZ(30, 65, 0x277c83), 0, 200, 165));
|
||||
const spindle = new THREE.Group(); spindle.name = "spindle"; spindle.position.copy(sourcePosition(0, 0, 20)); head.add(spindle);
|
||||
spindle.add(translate("spindle-housing", sourceCylinderZ(20, 135, 0x31888d), 0, 0, 67.5));
|
||||
spindle.add(translate("spindle-nose", sourceCylinderZ(10, 20, 0x4da0a0), 0, 0, 10));
|
||||
const tool = new THREE.Group(); tool.name = "tool-offset"; spindle.add(tool);
|
||||
tool.add(translate("tool-holder", sourceCylinderZ(6, 70, 0x9da7a8), 0, 0, 35));
|
||||
tool.add(translate("tool", sourceCylinderZ(3, 70, 0xd85f4a), 0, 0, 105));
|
||||
const scene = new THREE.Scene(); scene.background = new THREE.Color(0x252b2c);
|
||||
const studio = new RoomEnvironment();
|
||||
const pmrem = new THREE.PMREMGenerator(renderer);
|
||||
const environment = pmrem.fromScene(studio, .04);
|
||||
scene.environment = environment.texture; scene.environmentIntensity = .65;
|
||||
studio.dispose(); pmrem.dispose();
|
||||
scene.add(new THREE.HemisphereLight(0xf4f8ff, 0x4b5450, 1.2));
|
||||
const key = new THREE.DirectionalLight(0xfff4df, 2.7); key.position.set(-4, 9, -6);
|
||||
key.castShadow = true; key.shadow.mapSize.set(2048, 2048); key.shadow.bias = -.0002; key.shadow.normalBias = .012;
|
||||
Object.assign(key.shadow.camera, { left: -6, right: 6, top: 7, bottom: -6, near: .1, far: 30 }); scene.add(key);
|
||||
const rim = new THREE.DirectionalLight(0xd5f0ff, 1.7); rim.position.set(5, 5, 5); scene.add(rim);
|
||||
const fill = new THREE.DirectionalLight(0xffffff, .7); fill.position.set(4, 2, -7); scene.add(fill);
|
||||
const floor = new THREE.Mesh(new THREE.PlaneGeometry(18, 18), new THREE.ShadowMaterial({ opacity: .24 }));
|
||||
floor.rotation.x = -Math.PI / 2; floor.position.y = -2.64; floor.receiveShadow = true; scene.add(floor);
|
||||
|
||||
const assembly = buildXyzacMachine(); scene.add(assembly.machine);
|
||||
assembly.update(visualRef.current);
|
||||
const nameplateCanvas = document.createElement("canvas"); nameplateCanvas.width = 512; nameplateCanvas.height = 128;
|
||||
const label = nameplateCanvas.getContext("2d");
|
||||
if (label) {
|
||||
label.fillStyle = "#176b67"; label.fillRect(0, 0, 512, 128);
|
||||
label.fillStyle = "#f0f4f3"; label.font = "bold 68px sans-serif"; label.fillText("AXIS", 18, 82);
|
||||
label.font = "24px sans-serif"; label.fillText("XYZAC / TRT", 235, 76);
|
||||
}
|
||||
const nameplateTexture = new THREE.CanvasTexture(nameplateCanvas); nameplateTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
const nameplate = new THREE.Mesh(new THREE.PlaneGeometry(.84, .21), new THREE.MeshBasicMaterial({ map: nameplateTexture }));
|
||||
nameplate.position.set(-.57, -1.48, -1.046); nameplate.rotation.y = Math.PI; assembly.machine.add(nameplate);
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(36, 1, .02, 150);
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true; controls.dampingFactor = .12;
|
||||
controls.enableRotate = true;
|
||||
controls.screenSpacePanning = true; controls.minDistance = .25; controls.maxDistance = 65;
|
||||
// Keep the standard Three.js mouse contract: left-drag orbits, middle
|
||||
// drag zooms, right-drag pans, and the wheel zooms. The machine canvas
|
||||
// must remain directly navigable without first toggling a mode button.
|
||||
controls.mouseButtons.LEFT = THREE.MOUSE.ROTATE;
|
||||
controls.mouseButtons.MIDDLE = THREE.MOUSE.DOLLY;
|
||||
controls.mouseButtons.RIGHT = THREE.MOUSE.PAN;
|
||||
controls.touches.ONE = THREE.TOUCH.ROTATE;
|
||||
controls.touches.TWO = THREE.TOUCH.DOLLY_PAN;
|
||||
let frame = 0, lastPreset = "", lastGeometry: Segment[] | null = null, lastPathKey = "";
|
||||
const initialBounds = new THREE.Box3().setFromObject(assembly.machine);
|
||||
const modelCenter = initialBounds.getCenter(new THREE.Vector3());
|
||||
const presetCamera = () => {
|
||||
const { view: selected, zoom: scale } = settings.current;
|
||||
const direction = selected === "z" ? new THREE.Vector3(0, 1, -.001)
|
||||
: selected === "z2" ? new THREE.Vector3(.001, 1, 0)
|
||||
: selected === "x" ? new THREE.Vector3(1, 0, 0)
|
||||
: selected === "y" ? new THREE.Vector3(0, 0, 1)
|
||||
: selected === "y2" ? new THREE.Vector3(0, 0, -1) : new THREE.Vector3(.95, .6, -1.5);
|
||||
// Clear residual damping so a previous drag cannot move the fitted view.
|
||||
controls.enableDamping = false; controls.update(); controls.enableDamping = true;
|
||||
direction.normalize();
|
||||
camera.position.copy(modelCenter).add(direction); camera.lookAt(modelCenter); camera.updateMatrix();
|
||||
const right = new THREE.Vector3().setFromMatrixColumn(camera.matrix, 0);
|
||||
const up = new THREE.Vector3().setFromMatrixColumn(camera.matrix, 1);
|
||||
const tanV = Math.tan(THREE.MathUtils.degToRad(camera.fov / 2));
|
||||
let distance = 0;
|
||||
// Fit all eight bounding-box corners in camera space, including depth.
|
||||
for (const x of [initialBounds.min.x, initialBounds.max.x]) for (const y of [initialBounds.min.y, initialBounds.max.y]) for (const z of [initialBounds.min.z, initialBounds.max.z]) {
|
||||
const point = new THREE.Vector3(x, y, z).sub(modelCenter);
|
||||
distance = Math.max(distance, point.dot(direction) + Math.max(Math.abs(point.dot(right)) / (tanV * camera.aspect), Math.abs(point.dot(up)) / tanV));
|
||||
}
|
||||
controls.target.copy(modelCenter); camera.position.copy(modelCenter).addScaledVector(direction, distance * 1.12 / Math.max(.5, scale));
|
||||
camera.lookAt(controls.target); controls.update();
|
||||
};
|
||||
const resize = () => {
|
||||
const width = Math.max(element.clientWidth, 1), height = Math.max(element.clientHeight, 1);
|
||||
camera.aspect = width / height; camera.updateProjectionMatrix(); renderer.setSize(width, height, false);
|
||||
lastPreset = "";
|
||||
};
|
||||
const observer = new ResizeObserver(resize); observer.observe(element); resize();
|
||||
const pan = (x: number, y: number) => {
|
||||
const amount = camera.position.distanceTo(controls.target) * .035;
|
||||
camera.updateMatrix();
|
||||
const offset = new THREE.Vector3().setFromMatrixColumn(camera.matrix, 0).multiplyScalar(x * amount)
|
||||
.addScaledVector(new THREE.Vector3().setFromMatrixColumn(camera.matrix, 1), y * amount);
|
||||
camera.position.add(offset); controls.target.add(offset); controls.update();
|
||||
};
|
||||
const zoomCamera = (factor: number) => {
|
||||
const offset = camera.position.clone().sub(controls.target);
|
||||
offset.setLength(THREE.MathUtils.clamp(offset.length() * factor, controls.minDistance, controls.maxDistance));
|
||||
camera.position.copy(controls.target).add(offset); controls.update();
|
||||
};
|
||||
element.xyzacMachineController = {
|
||||
zoomIn: () => zoomCamera(.8), zoomOut: () => zoomCamera(1.25),
|
||||
panLeft: () => pan(-1, 0), panRight: () => pan(1, 0), panUp: () => pan(0, 1), panDown: () => pan(0, -1),
|
||||
rotateLeft: () => { controls.rotateLeft(Math.PI / 12); controls.update(); },
|
||||
rotateRight: () => { controls.rotateLeft(-Math.PI / 12); controls.update(); },
|
||||
rotateUp: () => { controls.rotateUp(Math.PI / 12); controls.update(); },
|
||||
rotateDown: () => { controls.rotateUp(-Math.PI / 12); controls.update(); },
|
||||
reset: () => { lastPreset = ""; },
|
||||
tool: () => {
|
||||
const target = assembly.toolTip.getWorldPosition(new THREE.Vector3()).add(new THREE.Vector3(0, .2, 0));
|
||||
controls.target.copy(target); camera.position.copy(target).add(new THREE.Vector3(.95, .55, -1.5)); controls.update();
|
||||
},
|
||||
};
|
||||
|
||||
const zslide = new THREE.Group(); zslide.name = "spindle-z"; machine.add(zslide);
|
||||
const saddle = new THREE.Group(); saddle.name = "saddle-y"; zslide.add(saddle);
|
||||
saddle.add(translate("saddle-body", sourceBox(150, 106, 32, 0x8b9697), 0, 0, -89));
|
||||
saddle.add(translate("z-slide", sourceBox(100, 220, 75, 0xb8bdbe), 0, 10, -142.5));
|
||||
const tableX = new THREE.Group(); tableX.name = "table-x"; saddle.add(tableX);
|
||||
tableX.add(translate("table-bed", sourceBox(300, 100, 17, 0x686f72), 0, 0, -60.5));
|
||||
tableX.add(translate("ways", sourceBox(300, 80, 6, 0x50575a), 0, 0, -72));
|
||||
const tilt = new THREE.Group(); tilt.name = "tilt-a"; tableX.add(tilt);
|
||||
const yOffset = 20; const zOffset = 10;
|
||||
tilt.position.copy(sourcePosition(0, yOffset, zOffset));
|
||||
tilt.rotation.x = 0;
|
||||
const rotate = new THREE.Group(); rotate.name = "rotate-c"; tilt.add(rotate);
|
||||
rotate.position.copy(sourcePosition(0, -yOffset, -zOffset));
|
||||
rotate.add(translate("trunnion", sourceBox(145, 80, 35, 0xb17540), 0, 0, -30));
|
||||
rotate.add(translate("table", sourceCylinderZ(50, 18, 0xb17540), 0, 0, -9));
|
||||
rotate.rotation.y = 0;
|
||||
const yoke = new THREE.Group(); yoke.name = "yoke"; tilt.add(yoke);
|
||||
yoke.add(translate("yoke-plate", sourceBox(130, 80, 10, 0xb17540), 0, 0, -30));
|
||||
const brackets = new THREE.Group(); brackets.name = "brackets"; tableX.add(brackets);
|
||||
brackets.position.copy(sourcePosition(0, yOffset, zOffset));
|
||||
brackets.add(translate("bracket-left", sourceBox(10, 80, 50, 0x9b6b3b), -72, 0, -25));
|
||||
brackets.add(translate("bracket-right", sourceBox(10, 80, 50, 0x9b6b3b), 72, 0, -25));
|
||||
const pathGroup = new THREE.Group(); pathGroup.name = "xyzac-toolpath"; scene.add(pathGroup);
|
||||
const liveGroup = new THREE.Group(); liveGroup.name = "xyzac-live-toolpath"; scene.add(liveGroup);
|
||||
const overlayGroup = new THREE.Group(); overlayGroup.name = "xyzac-overlays"; scene.add(overlayGroup);
|
||||
const resize = () => { const width = Math.max(element.clientWidth, 280); const height = Math.max(element.clientHeight, 260); camera.aspect = width / height; camera.updateProjectionMatrix(); renderer.setSize(width, height, false); };
|
||||
const observer = new ResizeObserver(resize); observer.observe(element); resize();
|
||||
|
||||
const toView = (point: Point) => new THREE.Vector3(Number(point.x ?? 0) * MM, Number(point.z ?? 0) * MM, Number(point.y ?? 0) * MM);
|
||||
let frame = 0;
|
||||
const pathMaterials = [0xdce7e8, 0xff9a55, 0x5c9d9a].map((color) => new THREE.LineBasicMaterial({ color }));
|
||||
const clearLines = (group: THREE.Group) => { group.children.forEach((child) => (child as THREE.Line).geometry.dispose()); group.clear(); };
|
||||
const limits = new THREE.Box3Helper(new THREE.Box3(), 0xdd7566); scene.add(limits);
|
||||
const offsets = new THREE.AxesHelper(.8); scene.add(offsets);
|
||||
const extents = new THREE.Box3Helper(initialBounds, 0xe6c56b); scene.add(extents);
|
||||
const render = () => {
|
||||
const state = visualRef.current; const [tableXJoint, saddleYJoint, spindleZJoint, tiltAJoint, rotateCJoint] = state.joints;
|
||||
tableX.position.x = -tableXJoint * MM; saddle.position.z = -saddleYJoint * MM; zslide.position.y = (200 - spindleZJoint) * MM;
|
||||
tilt.rotation.x = -tiltAJoint * DEG; rotate.rotation.y = -rotateCJoint * DEG; tool.position.y = -state.offsets.tool_offset * MM;
|
||||
pathGroup.clear(); liveGroup.clear();
|
||||
const completed = new THREE.LineBasicMaterial({ color: 0xdce7e8 }); const active = new THREE.LineBasicMaterial({ color: 0xff9a55 }); const rapid = new THREE.LineBasicMaterial({ color: 0x5c9d9a });
|
||||
for (const raw of (toolpathRef.current && toolpathRef.current.length > 0 ? toolpathRef.current : state.toolpath) as Segment[]) {
|
||||
const start = raw.start ?? {}; const end = raw.end ?? {};
|
||||
const points = Array.isArray(raw.samples) && raw.samples.length > 1 ? raw.samples.map(toView) : [toView(start), toView(end)];
|
||||
const group = raw.active || raw.id === state.activeSegmentId ? liveGroup : pathGroup; const line = new THREE.Line(new THREE.BufferGeometry().setFromPoints(points), raw.active || raw.id === state.activeSegmentId ? active : raw.motion_type === 0 ? rapid : completed); group.add(line);
|
||||
const state = visualRef.current, config = settings.current;
|
||||
assembly.update(state);
|
||||
const paths = (config.toolpath ?? state.toolpath) as Segment[];
|
||||
const pathKey = `${config.showPath}:${config.showLive}:${state.activeSegmentId}`;
|
||||
if (paths !== lastGeometry || pathKey !== lastPathKey) {
|
||||
clearLines(pathGroup); clearLines(liveGroup);
|
||||
if (config.showPath || config.showLive) for (const raw of paths) {
|
||||
const live = raw.active || raw.id === state.activeSegmentId;
|
||||
if (live ? !config.showLive : !config.showPath) continue;
|
||||
const points = raw.samples?.length ? raw.samples.map(toView) : [toView(raw.start ?? {}), toView(raw.end ?? {})];
|
||||
(live ? liveGroup : pathGroup).add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(points), pathMaterials[live ? 1 : raw.motion_type === 0 ? 2 : 0]));
|
||||
}
|
||||
lastGeometry = paths; lastPathKey = pathKey;
|
||||
}
|
||||
const layer = layersRef.current;
|
||||
pathGroup.visible = layer.showPath; liveGroup.visible = layer.showLive;
|
||||
overlayGroup.clear();
|
||||
if (layer.showLimits) { const limits = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(4, 2, 3)), new THREE.LineBasicMaterial({ color: 0xdd7566 })); limits.position.set(0, .8, 0); overlayGroup.add(limits); }
|
||||
if (layer.showOffsets) { const offset = new THREE.AxesHelper(1.8); offset.name = "xyzac-offset-axes"; offset.position.set(state.offsets.x_rot_point * MM, state.offsets.z_offset * MM, state.offsets.y_offset * MM); overlayGroup.add(offset); }
|
||||
overlayGroup.visible = layer.showLimits || layer.showOffsets || layer.showExtents || layer.showVelocity;
|
||||
const distance = 9 / Math.max(.5, zoomRef.current); const selected = viewRef.current;
|
||||
if (selected === "z") camera.position.set(0, distance, .01); else if (selected === "z2") camera.position.set(distance * .3, distance * .9, distance * .3); else if (selected === "x") camera.position.set(distance, 2, .01); else if (selected === "y") camera.position.set(.01, 2, distance); else if (selected === "y2") camera.position.set(.01, 2, -distance); else camera.position.set(6 / Math.max(.5, zoomRef.current), 5 / Math.max(.5, zoomRef.current), 7 / Math.max(.5, zoomRef.current));
|
||||
camera.lookAt(0, 0, 0); scene.rotation.y = rotateRef.current ? (performance.now() / 5000) % (Math.PI * 2) : 0; renderer.render(scene, camera); frame = requestAnimationFrame(render);
|
||||
limits.visible = config.showLimits && Boolean(state.machineLimits?.length === 3);
|
||||
if (limits.visible) {
|
||||
const [x, y, z] = state.machineLimits!;
|
||||
limits.box.setFromPoints([toView({ x: x[0], y: y[0], z: z[0] }), toView({ x: x[1], y: y[1], z: z[1] })]);
|
||||
}
|
||||
offsets.visible = config.showOffsets;
|
||||
offsets.position.set(0, state.offsets.z_offset * .01, state.offsets.y_offset * .01);
|
||||
extents.visible = config.showExtents;
|
||||
const preset = `${config.view}:${config.zoom}`;
|
||||
if (preset !== lastPreset) { presetCamera(); lastPreset = preset; }
|
||||
controls.update(); renderer.render(scene, camera);
|
||||
element.dataset.xyzacRenderedTick = String(state.tick);
|
||||
element.dataset.xyzacToolTip = JSON.stringify(assembly.toolTip.getWorldPosition(new THREE.Vector3()).toArray());
|
||||
frame = requestAnimationFrame(render);
|
||||
};
|
||||
render();
|
||||
return () => { cancelAnimationFrame(frame); observer.disconnect(); renderer.dispose(); element.replaceChildren(); };
|
||||
return () => {
|
||||
cancelAnimationFrame(frame); observer.disconnect(); controls.dispose(); delete element.xyzacMachineController;
|
||||
clearLines(pathGroup); clearLines(liveGroup); pathMaterials.forEach((material) => material.dispose());
|
||||
for (const helper of [limits, offsets, extents]) helper.dispose();
|
||||
assembly.dispose(); nameplateTexture.dispose(); nameplate.geometry.dispose(); nameplate.material.dispose();
|
||||
floor.geometry.dispose(); floor.material.dispose(); key.shadow.map?.dispose(); environment.dispose();
|
||||
renderer.dispose(); renderer.domElement.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const pose = visual.pose;
|
||||
return <div className="three-viewer xyzac-machine-view" ref={host} data-xyzac-authoritative={String(visual.authoritative)} data-xyzac-tick={visual.tick} data-xyzac-state-hash={visual.stateHash} data-xyzac-active-segment={visual.activeSegmentId ?? ""} data-xyzac-joints={JSON.stringify(visual.joints)} data-xyzac-offsets={JSON.stringify(visual.offsets)} aria-label="XYZAC TRT Vismach machine"><pre className="axis-preview-hud" aria-label="XYZAC authoritative visual state">{visual.authoritative ? `XYZAC TRT\nX ${Number(pose.x).toFixed(3)} Y ${Number(pose.y).toFixed(3)} Z ${Number(pose.z).toFixed(3)}\nA ${Number(pose.a).toFixed(3)} C ${Number(pose.c).toFixed(3)}\nJ ${visual.joints.map((value) => Number(value).toFixed(2)).join(" ")}\nTick ${visual.tick}\nHash ${visual.stateHash.slice(0, 18)}` : "XYZAC TRT\nWASM state pending"}</pre></div>;
|
||||
const model = visual.machineModel;
|
||||
const controllerButton = (action: string, label: string, asset?: string, glyph?: string) => <button type="button" data-axis-control={`machine-${action}`} data-xyzac-machine-controller={action} title={label} aria-label={label} onClick={() => host.current?.xyzacMachineController?.[action]?.()}>{asset ? <img src={axisIcon(asset)} alt="" /> : glyph}</button>;
|
||||
return <div className="three-viewer xyzac-machine-view" ref={host} data-xyzac-preview="machine" data-xyzac-model-source="linuxcnc-wasm" data-xyzac-mouse-controls="left-rotate-middle-zoom-right-pan-wheel-zoom" data-xyzac-model-id={model?.id ?? ""} data-xyzac-model-source-path={model?.source_path ?? ""} data-xyzac-model-source-hash={model?.source_hash ?? ""} data-xyzac-authoritative={String(visual.authoritative)} data-xyzac-tick={visual.tick} data-xyzac-state-hash={visual.stateHash} data-xyzac-active-segment={visual.activeSegmentId ?? ""} data-xyzac-joints={JSON.stringify(visual.joints)} data-xyzac-offsets={JSON.stringify(visual.offsets)} aria-label="XYZAC TRT Vismach machine model">
|
||||
<div className="xyzac-view-controller xyzac-machine-controller" role="toolbar" aria-label="Machine 3D view controller">
|
||||
{controllerButton("zoomIn", "Zoom in", "tool_zoomin.gif")}{controllerButton("zoomOut", "Zoom out", "tool_zoomout.gif")}
|
||||
{controllerButton("panLeft", "Pan left", undefined, "\u2190")}{controllerButton("panRight", "Pan right", undefined, "\u2192")}
|
||||
{controllerButton("panUp", "Pan up", undefined, "\u2191")}{controllerButton("panDown", "Pan down", undefined, "\u2193")}
|
||||
{controllerButton("rotateLeft", "Rotate left", undefined, "\u21ba")}{controllerButton("rotateRight", "Rotate right", undefined, "\u21bb")}
|
||||
{controllerButton("rotateUp", "Rotate upward", undefined, "\u2b06")}{controllerButton("rotateDown", "Rotate downward", undefined, "\u2b07")}
|
||||
{controllerButton("reset", "Reset machine view", undefined, "\u21ba")}{controllerButton("tool", "Tool close-up", "tool_axis_z.gif")}
|
||||
</div>
|
||||
<pre className="axis-preview-hud xyzac-machine-hud" aria-label="XYZAC authoritative visual state">{visual.authoritative ? `XYZAC TRT\n${visual.joints.map((value, index) => `${["X", "Y", "Z", "A", "C"][index]} ${value.toFixed(3)}`).join("\n")}${showVelocity ? `\nV ${Number(visual.velocityMmMin ?? 0).toFixed(1)}` : ""}` : "XYZAC TRT\nWASM state pending"}</pre>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export function XyzacMachineViewFromSnapshot({ snapshot, toolpath, ...props }: Omit<Props, "visual"> & { snapshot: XyzacVisualSource | null | undefined }) {
|
||||
return <XyzacMachineView visual={projectXyzacVisual(snapshot)} toolpath={toolpath} {...props} />;
|
||||
export function XyzacMachineViewFromSnapshot({ snapshot, ...props }: Omit<Props, "visual"> & { snapshot: XyzacVisualSource | null | undefined }) {
|
||||
return <XyzacMachineView visual={projectXyzacVisual(snapshot)} {...props} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user