Support XYZAC and XYZBC in shared web machine UI
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -46,14 +46,15 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat
|
||||
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);
|
||||
const coordinates = visualRef.current.machineModel?.coordinate_system ?? "XYZAC";
|
||||
const assembly = buildXyzacMachine(coordinates); 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);
|
||||
label.font = "24px sans-serif"; label.fillText(`${coordinates} / 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 }));
|
||||
@@ -158,7 +159,8 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat
|
||||
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);
|
||||
const coordinates = state.machineModel?.coordinate_system ?? "XYZAC";
|
||||
offsets.position.set(coordinates === "XYZBC" ? Number(state.offsets.x_offset ?? 0) * .01 : 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; }
|
||||
@@ -179,8 +181,10 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat
|
||||
}, []);
|
||||
|
||||
const model = visual.machineModel;
|
||||
const coordinates = model?.coordinate_system ?? "XYZAC";
|
||||
const axisLabels = [...coordinates];
|
||||
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">
|
||||
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={`${coordinates} 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")}
|
||||
@@ -189,10 +193,11 @@ export function XyzacMachineView({ visual, toolpath, view = "p", zoom = 1, rotat
|
||||
{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")}\n${visual.joints.map((value, index) => `J${index} ${value.toFixed(3)}`).join(" ")}${showVelocity ? `\nV ${Number(visual.velocityMmMin ?? 0).toFixed(1)}` : ""}` : "XYZAC TRT\nWASM state pending"}</pre>
|
||||
<pre className="axis-preview-hud xyzac-machine-hud" aria-label={`${coordinates} authoritative visual state`}>{visual.authoritative ? `${coordinates} TRT\n${visual.joints.map((value, index) => `${axisLabels[index] ?? `J${index}`} ${value.toFixed(3)}`).join("\n")}\n${visual.joints.map((value, index) => `J${index} ${value.toFixed(3)}`).join(" ")}${showVelocity ? `\nV ${Number(visual.velocityMmMin ?? 0).toFixed(1)}` : ""}` : `${coordinates} TRT\nWASM state pending`}</pre>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export function XyzacMachineViewFromSnapshot({ snapshot, ...props }: Omit<Props, "visual"> & { snapshot: XyzacVisualSource | null | undefined }) {
|
||||
return <XyzacMachineView visual={projectXyzacVisual(snapshot)} {...props} />;
|
||||
const visual = projectXyzacVisual(snapshot);
|
||||
return <XyzacMachineView key={visual.machineModel?.id ?? "machine-model-pending"} visual={visual} {...props} />;
|
||||
}
|
||||
|
||||
@@ -385,7 +385,10 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
originGroup.scale.setScalar(Math.max(.015, bounds.radius * .35));
|
||||
const pose = sourcePoint(renderedPose);
|
||||
toolGroup.position.copy(pose);
|
||||
toolGroup.rotation.set(-Number(renderedPose.a ?? 0) * DEG, -Number(renderedPose.c ?? 0) * DEG, 0);
|
||||
const isXyzbc = String(state.machineModel?.coordinate_system ?? "XYZAC") === "XYZBC";
|
||||
const tilt = -Number(renderedPose[isXyzbc ? "b" : "a"] ?? 0) * DEG;
|
||||
const rotary = -Number(renderedPose.c ?? 0) * DEG;
|
||||
toolGroup.rotation.set(isXyzbc ? 0 : tilt, rotary, isXyzbc ? tilt : 0);
|
||||
const toolScale = Math.min(1, Math.max(0.015, bounds.radius * 0.35));
|
||||
toolGroup.scale.setScalar(toolScale);
|
||||
tool.position.set(0, .57, 0); tip.position.set(0, 0, 0);
|
||||
@@ -404,7 +407,8 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
element.dataset.xyzacLimitsVisible = String(limitGrid.visible);
|
||||
element.dataset.xyzacExtentsVisible = String(extents.visible);
|
||||
element.dataset.xyzacOffset = JSON.stringify(offset);
|
||||
if (hudRef.current) hudRef.current.textContent = `TOOL PREVIEW\nX ${Number(renderedPose.x).toFixed(3)} Y ${Number(renderedPose.y).toFixed(3)} Z ${Number(renderedPose.z).toFixed(3)}\nA ${Number(renderedPose.a).toFixed(3)} C ${Number(renderedPose.c).toFixed(3)}\nJ0 ${Number(renderedJoints[0] ?? 0).toFixed(3)} J1 ${Number(renderedJoints[1] ?? 0).toFixed(3)} J2 ${Number(renderedJoints[2] ?? 0).toFixed(3)}\nJ3 ${Number(renderedJoints[3] ?? 0).toFixed(3)} J4 ${Number(renderedJoints[4] ?? 0).toFixed(3)}`;
|
||||
const rotaryTiltAxis = String(state.machineModel?.coordinate_system ?? "XYZAC") === "XYZBC" ? "b" : "a";
|
||||
if (hudRef.current) hudRef.current.textContent = `TOOL PREVIEW\nX ${Number(renderedPose.x).toFixed(3)} Y ${Number(renderedPose.y).toFixed(3)} Z ${Number(renderedPose.z).toFixed(3)}\n${rotaryTiltAxis.toUpperCase()} ${Number(renderedPose[rotaryTiltAxis]).toFixed(3)} C ${Number(renderedPose.c).toFixed(3)}\nJ0 ${Number(renderedJoints[0] ?? 0).toFixed(3)} J1 ${Number(renderedJoints[1] ?? 0).toFixed(3)} J2 ${Number(renderedJoints[2] ?? 0).toFixed(3)}\nJ3 ${Number(renderedJoints[3] ?? 0).toFixed(3)} J4 ${Number(renderedJoints[4] ?? 0).toFixed(3)}`;
|
||||
if (layer.showVelocity && hudRef.current) hudRef.current.textContent += `\nV ${Number(state.velocityMmMin ?? 0).toFixed(1)} mm/min`;
|
||||
controls.mouseButtons.LEFT = rotateRef.current ? THREE.MOUSE.ROTATE : THREE.MOUSE.PAN;
|
||||
controls.update(); renderer.render(scene, camera); frame = requestAnimationFrame(render);
|
||||
@@ -417,7 +421,10 @@ export function XyzacToolPreview({ visual, view = "p", zoom = 1, rotateMode = fa
|
||||
const helixCount = visual.toolpath.filter((segment) => (segment as { kind?: string }).kind === "helix").length;
|
||||
const sampleCount = visual.toolpath.reduce((count, segment) => count + (Array.isArray((segment as { samples?: unknown[] }).samples) ? (segment as { samples: unknown[] }).samples.length : 0), 0);
|
||||
const controllerButton = (action: string, label: string, glyph: string) => ["zoomIn", "zoomOut"].includes(action) ? null : <button type="button" data-axis-control={`preview-${action}`} data-xyzac-controller={action} title={label} aria-label={label} onClick={() => { const controller = (host.current as (HTMLDivElement & { xyzacController?: Record<string, () => void> }) | null)?.xyzacController; controller?.[action]?.(); }}>{glyph}</button>;
|
||||
return <div className="three-viewer xyzac-tool-preview" ref={host} data-xyzac-authoritative={String(visual.authoritative)} data-xyzac-preview="toolpath" data-xyzac-controller="orbit" data-xyzac-origin="0,0,0" data-xyzac-origin-axes="X,Y,Z" data-xyzac-toolpath-count={visual.toolpath.length} data-xyzac-helix-count={helixCount} data-xyzac-sample-count={sampleCount} data-xyzac-samples-source="linuxcnc-wasm-sim_posemath_segment_point" data-xyzac-live-source="linuxcnc-source-tp" data-xyzac-live-progress={visual.activeProgress} data-xyzac-live-pose={JSON.stringify(visual.livePose)} data-xyzac-tick={visual.tick} data-xyzac-state-hash={visual.stateHash} data-xyzac-active-segment={visual.activeSegmentId ?? ""} data-xyzac-joints={JSON.stringify(visual.joints)} aria-label="XYZAC authoritative tool preview"><div className="xyzac-view-controller" role="toolbar" aria-label="3D preview controller">{controllerButton("zoomIn", "Zoom in", "+")}{controllerButton("zoomOut", "Zoom out", "-")}<span className="xyzac-controller-divider" aria-hidden="true" />{controllerButton("panLeft", "Pan left", "L")}{controllerButton("panRight", "Pan right", "R")}{controllerButton("panUp", "Pan up", "U")}{controllerButton("panDown", "Pan down", "D")}<span className="xyzac-controller-divider" aria-hidden="true" />{controllerButton("reset", "Reset 3D view", "0")}</div><pre ref={hudRef} className="axis-preview-hud" aria-label="XYZAC tool preview state">{visual.authoritative ? `TOOL PREVIEW\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)}\nJ0 ${Number(visual.joints[0] ?? 0).toFixed(3)} J1 ${Number(visual.joints[1] ?? 0).toFixed(3)} J2 ${Number(visual.joints[2] ?? 0).toFixed(3)}\nJ3 ${Number(visual.joints[3] ?? 0).toFixed(3)} J4 ${Number(visual.joints[4] ?? 0).toFixed(3)}` : "TOOL PREVIEW\nWASM state pending"}</pre></div>;
|
||||
const coordinates = String(visual.machineModel?.coordinate_system ?? "XYZAC");
|
||||
const tiltAxis = coordinates === "XYZBC" ? "b" : "a";
|
||||
const tiltLabel = tiltAxis.toUpperCase();
|
||||
return <div className="three-viewer xyzac-tool-preview" ref={host} data-xyzac-authoritative={String(visual.authoritative)} data-xyzac-preview="toolpath" data-xyzac-controller="orbit" data-xyzac-origin="0,0,0" data-xyzac-origin-axes="X,Y,Z" data-xyzac-toolpath-count={visual.toolpath.length} data-xyzac-helix-count={helixCount} data-xyzac-sample-count={sampleCount} data-xyzac-samples-source="linuxcnc-wasm-sim_posemath_segment_point" data-xyzac-live-source="linuxcnc-source-tp" data-xyzac-live-progress={visual.activeProgress} data-xyzac-live-pose={JSON.stringify(visual.livePose)} data-xyzac-tick={visual.tick} data-xyzac-state-hash={visual.stateHash} data-xyzac-active-segment={visual.activeSegmentId ?? ""} data-xyzac-joints={JSON.stringify(visual.joints)} aria-label={`${coordinates} authoritative tool preview`}><div className="xyzac-view-controller" role="toolbar" aria-label="3D preview controller">{controllerButton("zoomIn", "Zoom in", "+")}{controllerButton("zoomOut", "Zoom out", "-")}<span className="xyzac-controller-divider" aria-hidden="true" />{controllerButton("panLeft", "Pan left", "L")}{controllerButton("panRight", "Pan right", "R")}{controllerButton("panUp", "Pan up", "U")}{controllerButton("panDown", "Pan down", "D")}<span className="xyzac-controller-divider" aria-hidden="true" />{controllerButton("reset", "Reset 3D view", "0")}</div><pre ref={hudRef} className="axis-preview-hud" aria-label={`${coordinates} tool preview state`}>{visual.authoritative ? `TOOL PREVIEW\nX ${Number(pose.x).toFixed(3)} Y ${Number(pose.y).toFixed(3)} Z ${Number(pose.z).toFixed(3)}\n${tiltLabel} ${Number(pose[tiltAxis] ?? 0).toFixed(3)} C ${Number(pose.c).toFixed(3)}\nJ0 ${Number(visual.joints[0] ?? 0).toFixed(3)} J1 ${Number(visual.joints[1] ?? 0).toFixed(3)} J2 ${Number(visual.joints[2] ?? 0).toFixed(3)}\nJ3 ${Number(visual.joints[3] ?? 0).toFixed(3)} J4 ${Number(visual.joints[4] ?? 0).toFixed(3)}` : "TOOL PREVIEW\nWASM state pending"}</pre></div>;
|
||||
}
|
||||
|
||||
export function XyzacToolPreviewFromSnapshot({ snapshot, ...props }: Omit<Props, "visual"> & { snapshot: XyzacVisualSource | null | undefined }) {
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { XYZAC_KINEMATICS, type XyzacOperationTrace, type XyzacPanelView } from "./xyzac-panel-contract";
|
||||
import { type XyzacOperationTrace, type XyzacPanelView } from "./xyzac-panel-contract";
|
||||
import type { SimSnapshotV1 } from "./protocol";
|
||||
import { DEFAULT_MACHINE } from "../../workspace/machine-definitions.mjs";
|
||||
|
||||
type Command = (kind: string, payload?: Record<string, unknown>, action?: string) => void;
|
||||
|
||||
export function XyzacKinematicsControl({ view, command, loading = false }: { view: XyzacPanelView; command: Command; loading?: boolean }) {
|
||||
const active = view.kinematics?.active_type;
|
||||
return <section className="xyzac-kinematics" aria-label="XYZAC kinematics">
|
||||
<div className="xyzac-section-title"><strong>Switchkins</strong><span>{view.kinematics?.native_type ?? "Unavailable"}</span></div>
|
||||
const machine = view.machine ?? DEFAULT_MACHINE;
|
||||
const modes = [{ type: 0, action: "switchkinsIdentity", label: "Identity", mcode: "M429" }, { type: 1, action: "switchkinsXyzac", label: `TCP: ${machine.coordinates}`, mcode: "M428" }, { type: 2, action: "switchkinsUserk", label: "User K", mcode: "M430" }];
|
||||
return <section className="xyzac-kinematics" aria-label={`${machine.coordinates} kinematics`}>
|
||||
<div className="xyzac-section-title"><strong>{machine.label}</strong><span>{view.kinematics?.native_type ?? "Unavailable"}</span></div>
|
||||
<div className="xyzac-segments" role="group" aria-label="Kinematics mode">
|
||||
{XYZAC_KINEMATICS.map((item) => {
|
||||
{modes.map((item) => {
|
||||
const gate = view.gates[item.action];
|
||||
const disabled = loading || view.status !== "ready" || gate?.enabled === false;
|
||||
return <button key={item.type} type="button" data-axis-control={`switchkins-${item.type}`} className={active === item.type ? "active" : ""} aria-pressed={active === item.type} disabled={disabled} title={gate?.reason ?? item.mcode} onClick={() => command("KINEMATICS_SELECT", { type: item.type }, item.action)}><span>{item.label}</span><small>{item.mcode}</small></button>;
|
||||
@@ -20,18 +23,23 @@ export function XyzacKinematicsControl({ view, command, loading = false }: { vie
|
||||
|
||||
export function XyzacManualPanel({ view }: { view: XyzacPanelView }) {
|
||||
const pose = view.pose;
|
||||
const axes = ["x", "y", "z", "a", "c"] as const;
|
||||
return <section className="xyzac-manual-summary" aria-label="XYZAC authoritative position">
|
||||
<div className="xyzac-section-title"><strong>XYZAC authority</strong><span>{view.kinematics?.linear_units ?? "mm"} / {view.kinematics?.angular_units ?? "deg"}</span></div>
|
||||
const machine = view.machine ?? DEFAULT_MACHINE;
|
||||
const axes = machine.axes;
|
||||
const offsets = view.kinematics?.offsets;
|
||||
const offsetRows = machine.coordinates === "XYZBC"
|
||||
? [`X offset ${offsets ? Number(offsets.x_offset ?? 0).toFixed(3) : "-"}`, `Z offset ${offsets ? Number(offsets.z_offset).toFixed(3) : "-"}`, `Tool ${offsets ? Number(offsets.tool_offset).toFixed(3) : "-"}`]
|
||||
: [`Y offset ${offsets ? Number(offsets.y_offset).toFixed(3) : "-"}`, `Z offset ${offsets ? Number(offsets.z_offset).toFixed(3) : "-"}`, `Tool ${offsets ? Number(offsets.tool_offset).toFixed(3) : "-"}`];
|
||||
return <section className="xyzac-manual-summary" aria-label={`${machine.coordinates} authoritative position`}>
|
||||
<div className="xyzac-section-title"><strong>{machine.coordinates} authority</strong><span>{view.kinematics?.linear_units ?? "mm"} / {view.kinematics?.angular_units ?? "deg"}</span></div>
|
||||
<div className="xyzac-position-grid">
|
||||
{axes.map((axis, index) => <div key={axis}><b>{axis.toUpperCase()}</b><output>{pose ? Number(pose[axis] ?? Number.NaN).toFixed(axis === "a" || axis === "c" ? 3 : 4) : "-"}</output><small>J{index} {view.joints.length === 5 ? Number(view.joints[index]).toFixed(4) : "-"}</small></div>)}
|
||||
{axes.map((axis, index) => <div key={axis}><b>{axis.toUpperCase()}</b><output>{pose ? Number(pose[axis as keyof typeof pose] ?? Number.NaN).toFixed(["a", "b", "c"].includes(axis) ? 3 : 4) : "-"}</output><small>J{index} {view.joints.length === machine.joint_count ? Number(view.joints[index]).toFixed(4) : "-"}</small></div>)}
|
||||
</div>
|
||||
<div className="xyzac-offset-row"><span>Tool {view.kinematics ? Number(view.kinematics.offsets.tool_offset).toFixed(3) : "-"}</span><span>Y offset {view.kinematics ? Number(view.kinematics.offsets.y_offset).toFixed(3) : "-"}</span><span>Z offset {view.kinematics ? Number(view.kinematics.offsets.z_offset).toFixed(3) : "-"}</span></div>
|
||||
<div className="xyzac-offset-row">{offsetRows.map((row) => <span key={row}>{row}</span>)}</div>
|
||||
</section>;
|
||||
}
|
||||
|
||||
export function XyzacProgramPane({ status, currentLine, sourceHash, children }: { status: string; currentLine: number | null; sourceHash?: string | null; children: ReactNode }) {
|
||||
return <section className="xyzac-program" aria-label="XYZAC program"><header><strong>XYZAC Program</strong><span>{status}</span><span>Line {currentLine ?? "-"}</span><code>{sourceHash?.slice(0, 18) ?? "no source"}</code></header>{children}</section>;
|
||||
return <section className="xyzac-program" aria-label="TRT program"><header><strong>TRT Program</strong><span>{status}</span><span>Line {currentLine ?? "-"}</span><code>{sourceHash?.slice(0, 18) ?? "no source"}</code></header>{children}</section>;
|
||||
}
|
||||
|
||||
export function XyzacLiveParameters({ snapshot }: { snapshot: SimSnapshotV1 | null }) {
|
||||
@@ -56,15 +64,18 @@ export function XyzacLiveParameters({ snapshot }: { snapshot: SimSnapshotV1 | nu
|
||||
|
||||
export function XyzacDiagnostics({ view, operations, alarms, error }: { view: XyzacPanelView; operations: XyzacOperationTrace[]; alarms: Array<Record<string, unknown>>; error: { code: string; message?: string } | null }) {
|
||||
const disabled = Object.entries(view.gates).filter(([, gate]) => gate.enabled === false);
|
||||
return <section className="xyzac-diagnostics" aria-label="XYZAC diagnostics">
|
||||
return <section className="xyzac-diagnostics" aria-label={`${view.machine.coordinates} diagnostics`}>
|
||||
<div className="xyzac-diagnostic-summary"><strong>{view.status.toUpperCase()}</strong><span>Tick {view.tick}</span><code>{view.stateHash || "no hash"}</code></div>
|
||||
{error && <div className="xyzac-error" role="alert"><b>{error.code}</b><span>{error.message}</span></div>}
|
||||
<div><h3>Operation trace</h3>{operations.length ? operations.slice(-8).reverse().map((item, index) => <div className="xyzac-diagnostic-row" key={`${item.operation_id ?? "operation"}-${index}`}><code>{item.operation_id ?? "-"}</code><span>{item.kind}</span><b>{item.phase}</b><span>{item.reason ?? item.response_kind}</span></div>) : <span>No operations</span>}</div>
|
||||
<div><h3>Alarms / recovery</h3>{alarms.length ? alarms.slice(-5).map((alarm, index) => <div className="xyzac-diagnostic-row" key={`${String(alarm.id ?? alarm.code ?? "alarm")}-${index}`}><code>{String(alarm.code ?? "alarm")}</code><span>{String(alarm.severity ?? "error")}</span></div>) : <span>No alarms</span>}<div className="xyzac-diagnostic-row"><code>recovery</code><span>{view.recovery ? JSON.stringify(view.recovery) : "idle"}</span></div></div>
|
||||
<div><h3>Gate reasons</h3>{disabled.length ? disabled.slice(0, 10).map(([name, gate]) => <div className="xyzac-diagnostic-row" key={name}><code>{name}</code><span>{gate.reason}</span></div>) : <span>All XYZAC gates enabled</span>}</div>
|
||||
<div><h3>Gate reasons</h3>{disabled.length ? disabled.slice(0, 10).map(([name, gate]) => <div className="xyzac-diagnostic-row" key={name}><code>{name}</code><span>{gate.reason}</span></div>) : <span>All {view.machine.coordinates} gates enabled</span>}</div>
|
||||
</section>;
|
||||
}
|
||||
|
||||
export function XyzacTrtPanel({ view, command, loading = false }: { view: XyzacPanelView; command: Command; loading?: boolean }) {
|
||||
return <section className="xyzac-control-band" aria-label="XYZAC TRT controls"><XyzacKinematicsControl view={view} command={command} loading={loading} /><XyzacManualPanel view={view} /></section>;
|
||||
export function XyzacTrtPanel({ view, command, loading = false, machineOptions = [], selectedMachine, onMachineChange }: { view: XyzacPanelView; command: Command; loading?: boolean; machineOptions?: readonly { entrypoint: string; label: string }[]; selectedMachine?: string; onMachineChange?: (entrypoint: string) => void }) {
|
||||
return <section className="xyzac-control-band" aria-label={`${view.machine?.coordinates ?? "TRT"} controls`}>
|
||||
{machineOptions.length > 1 && <label className="xyzac-machine-selector">Machine type<select data-axis-control="machine-type-select" value={selectedMachine ?? view.machine?.entrypoint} onChange={(event) => onMachineChange?.(event.target.value)}>{machineOptions.map((machine) => <option key={machine.entrypoint} value={machine.entrypoint}>{machine.label}</option>)}</select></label>}
|
||||
<XyzacKinematicsControl view={view} command={command} loading={loading} /><XyzacManualPanel view={view} />
|
||||
</section>;
|
||||
}
|
||||
|
||||
4
web/react/src/legacy.d.ts
vendored
4
web/react/src/legacy.d.ts
vendored
@@ -1,4 +1,8 @@
|
||||
declare module "*.mjs" {
|
||||
export type MachineDefinition = { id: string; label: string; coordinates: string; axes: readonly string[]; joint_count: number; entrypoint: string; program: string; kinematics_type: number; runtime: string; model: string; source_path: string; offsets: Record<string, number>; axis_limits: Record<string, number[]> };
|
||||
export const MACHINE_DEFINITIONS: readonly MachineDefinition[];
|
||||
export const DEFAULT_MACHINE: MachineDefinition;
|
||||
export function machineDefinition(value?: string): MachineDefinition | null;
|
||||
export const SIM_CASE_CATALOG: Array<{ path: string; label?: string; category?: string; status?: string }>;
|
||||
export function loadSimulationCase(options: { path: string; baseUrl?: string }): Promise<{ status: string; entrypoint: string; files: Record<string, string>; missing: string[]; fetched: string[]; state_hash: string; halManifest: unknown[] }>;
|
||||
export function toWorkerConfiguration(bundle: unknown): Record<string, unknown>;
|
||||
|
||||
@@ -99,26 +99,28 @@ export type SimSnapshotV1 = {
|
||||
task_authority?: string;
|
||||
};
|
||||
|
||||
export type XyzacKinematicsSnapshot = {
|
||||
export type TrtKinematicsSnapshot = {
|
||||
schema: "wasm-xyzac-kinematics.v1";
|
||||
status: "verified" | "error" | "blocked";
|
||||
active_type: 0 | 1 | 2;
|
||||
native_type: string;
|
||||
coordinates: "XYZAC";
|
||||
coordinates: "XYZAC" | "XYZBC";
|
||||
joint_count: 5;
|
||||
linear_units: "mm";
|
||||
angular_units: "deg";
|
||||
pose: Record<"x" | "y" | "z" | "a" | "b" | "c" | "u" | "v" | "w", number>;
|
||||
joints: [number, number, number, number, number];
|
||||
offsets: { x_rot_point: number; y_offset: number; z_offset: number; tool_offset: number };
|
||||
offsets: { x_rot_point: number; y_offset: number; z_offset: number; tool_offset: number; y_rot_point?: number; z_rot_point?: number; x_offset?: number };
|
||||
generation: number;
|
||||
switch_count: number;
|
||||
state_hash: string;
|
||||
wasm_state_hash: string;
|
||||
};
|
||||
|
||||
export type XyzacKinematicsSnapshot = TrtKinematicsSnapshot;
|
||||
|
||||
export type XyzacCapabilitySnapshot = {
|
||||
coordinates: "XYZAC";
|
||||
coordinates: "XYZAC" | "XYZBC";
|
||||
joint_count: 5;
|
||||
active_type: 0 | 1 | 2;
|
||||
hal_pin_vars: boolean;
|
||||
@@ -176,7 +178,7 @@ export type XyzacMachineModel = {
|
||||
source: "linuxcnc-wasm-worker";
|
||||
source_path: string;
|
||||
source_hash: string | null;
|
||||
coordinate_system: "XYZAC";
|
||||
coordinate_system: "XYZAC" | "XYZBC";
|
||||
joints: string[];
|
||||
components: string[];
|
||||
};
|
||||
|
||||
@@ -71,6 +71,7 @@ function projection(result: Record<string, unknown>, message: MessageEnvelope, p
|
||||
const executionTrace = resetExecutionTrace ? trace.slice(-EXECUTION_TRACE_CACHE_LIMIT) : trace.length ? [...previous.executionTrace, ...trace].slice(-EXECUTION_TRACE_CACHE_LIMIT) : previous.executionTrace;
|
||||
const delta = result.motion_sample as { tick?: number; pose?: Record<string, number>; joints?: number[]; active_segment_id?: number; active_progress?: number } | undefined;
|
||||
if (!panel && delta && previous.snapshot) {
|
||||
const previousSnapshot = previous.snapshot;
|
||||
const pose = delta.pose ?? {};
|
||||
const joints = Array.isArray(delta.joints) ? [...delta.joints] : previous.snapshot.motion?.joints ?? [];
|
||||
const visual = previous.snapshot.visual ? {
|
||||
@@ -97,16 +98,8 @@ function projection(result: Record<string, unknown>, message: MessageEnvelope, p
|
||||
motion: {
|
||||
...previous.snapshot.motion,
|
||||
...(result.motion as SimSnapshotV1["motion"] | undefined),
|
||||
// Keep the XYZAC axis vector intact on every motion delta. Older
|
||||
// projection code rebuilt only XYZ, which made A/C disappear from
|
||||
// consumers that read the canonical motion array.
|
||||
axes: [
|
||||
Number(pose.x ?? previous.snapshot.motion?.axes?.[0] ?? 0),
|
||||
Number(pose.y ?? previous.snapshot.motion?.axes?.[1] ?? 0),
|
||||
Number(pose.z ?? previous.snapshot.motion?.axes?.[2] ?? 0),
|
||||
Number(pose.a ?? previous.snapshot.motion?.axes?.[3] ?? 0),
|
||||
Number(pose.c ?? previous.snapshot.motion?.axes?.[4] ?? 0),
|
||||
],
|
||||
// Keep the active machine's axis vector intact on every motion delta.
|
||||
axes: [...String(previousSnapshot.kinematics?.coordinates ?? "XYZAC").toLowerCase()].map((axis, index) => Number(pose[axis] ?? previousSnapshot.motion?.axes?.[index] ?? 0)),
|
||||
joints,
|
||||
},
|
||||
wasm_motion: (result.wasm_motion as Record<string, unknown>) ?? previous.snapshot.wasm_motion,
|
||||
@@ -150,7 +143,8 @@ function projection(result: Record<string, unknown>, message: MessageEnvelope, p
|
||||
})
|
||||
: previous.toolpath;
|
||||
const pose = panel.visual.pose;
|
||||
const axes = [pose.x, pose.y, pose.z, pose.a, pose.c].map((value) => Number.isFinite(value) ? value : 0);
|
||||
const coordinateSystem = String(panel.kinematics?.coordinates ?? panel.xyzac?.coordinates ?? "XYZAC");
|
||||
const axes = [...coordinateSystem.toLowerCase()].map((axis) => Number.isFinite(pose?.[axis]) ? Number(pose[axis]) : 0);
|
||||
const snapshot = {
|
||||
schema: panel.schema,
|
||||
source_status: panel.source_status,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { INITIAL_STATE, reduceFrontendState } from "./store.ts";
|
||||
import { projectXyzacPanel } from "./xyzac-panel-contract.ts";
|
||||
|
||||
const envelope = (kind, seq, payload, tick) => ({ schema_version: 1, kind, context_id: INITIAL_STATE.contextId, session_id: "motion-projection", seq, simulation_tick: tick, payload });
|
||||
const panel = {
|
||||
@@ -36,4 +37,8 @@ const next = reduceFrontendState(initial, envelope("COMMAND_RESULT", 2, {
|
||||
assert.deepEqual(next.snapshot.motion.axes, [11, 21, 31, 41, 51]);
|
||||
assert.equal(next.snapshot.visual.pose.a, 41);
|
||||
assert.equal(next.snapshot.visual.pose.c, 51);
|
||||
const xyzbcPanel = projectXyzacPanel({ simulation_tick: 3, state_hash: "bc", kinematics: { schema: "wasm-xyzac-kinematics.v1", status: "verified", active_type: 1, native_type: "xyzbc-trt-kins", coordinates: "XYZBC", joint_count: 5, linear_units: "mm", angular_units: "deg", pose: { x: 0, y: 0, z: 0, a: 0, b: 12, c: 20, u: 0, v: 0, w: 0 }, joints: [1, 2, 3, 12, 20], offsets: { x_rot_point: 0, y_rot_point: 0, z_rot_point: 0, x_offset: -20, y_offset: 0, z_offset: -15, tool_offset: 0 }, generation: 1, switch_count: 1, state_hash: "k", wasm_state_hash: "w" }, xyzac: { coordinates: "XYZBC", joint_count: 5, active_type: 1, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true }, visual: { pose: { x: 0, y: 0, z: 0, a: 0, b: 12, c: 20 }, joints: [1, 2, 3, 12, 20] } });
|
||||
assert.equal(xyzbcPanel.machine.coordinates, "XYZBC");
|
||||
assert.deepEqual(xyzbcPanel.machine.axes, ["x", "y", "z", "b", "c"]);
|
||||
assert.deepEqual(xyzbcPanel.joints, [1, 2, 3, 12, 20]);
|
||||
console.log(JSON.stringify({ schema: "xyzac-motion-projection.v1", status: "verified", axes: next.snapshot.motion.axes, pose: { a: next.snapshot.visual.pose.a, c: next.snapshot.visual.pose.c } }));
|
||||
|
||||
@@ -6,7 +6,9 @@ const MM = .01;
|
||||
const DEG = Math.PI / 180;
|
||||
|
||||
// Source coordinates remain millimetres, Z up. The existing viewer uses Y up.
|
||||
export function buildXyzacMachine() {
|
||||
export function buildXyzacMachine(coordinates = "XYZAC") {
|
||||
// XYZAC retains the established tilt-a group identity; XYZBC uses tilt-b.
|
||||
const rotaryAxis = coordinates[3]?.toLowerCase() ?? "a";
|
||||
const geometries = new Map<string, THREE.BufferGeometry>();
|
||||
const materials = {
|
||||
enamel: new THREE.MeshStandardMaterial({ color: 0xd9e1e1, roughness: .32, metalness: .25 }),
|
||||
@@ -51,7 +53,7 @@ export function buildXyzacMachine() {
|
||||
const lathe = (parent: THREE.Object3D, name: string, profile: number[][], finish: Finish) =>
|
||||
mesh(parent, name, geometry(name, () => new THREE.LatheGeometry(profile.map(([r, z]) => new THREE.Vector2(r * MM, z * MM)), 64)), finish, 0, 0, 0);
|
||||
|
||||
const machine = group(null, "xyzac-machine");
|
||||
const machine = group(null, coordinates === "XYZBC" ? "xyzbc-machine" : "xyzac-machine");
|
||||
for (const x of [-95, 95]) for (const y of [-72, 135]) {
|
||||
cylinder(machine, "levelling-pad", x, y, -259, 17, 9, "rubber");
|
||||
cylinder(machine, "levelling-stud", x, y, -249, 8, 15, "steel");
|
||||
@@ -141,14 +143,14 @@ export function buildXyzacMachine() {
|
||||
cylinder(brackets, "bearing-seal", x < 0 ? -82 : 82, 0, 0, 12, 3, "dark", "x");
|
||||
for (const y of [-27, 27]) bolt(brackets, x, y, -38);
|
||||
}
|
||||
box(brackets, "a-axis-servo", -94, 0, 0, 23, 48, 46, "teal", 4);
|
||||
cylinder(brackets, "a-axis-end-cap", -107, 0, 0, 16, 3, "dark", "x");
|
||||
box(brackets, `${rotaryAxis}-axis-servo`, -94, 0, 0, 23, 48, 46, "teal", 4);
|
||||
cylinder(brackets, `${rotaryAxis}-axis-end-cap`, -107, 0, 0, 16, 3, "dark", "x");
|
||||
|
||||
const tilt = group(tableX, "tilt-a");
|
||||
const tilt = group(tableX, `tilt-${rotaryAxis}`);
|
||||
box(tilt, "yoke-plate", 0, 0, -30, 130, 80, 10, "teal", 2);
|
||||
for (const x of [-60, 60]) {
|
||||
box(tilt, "yoke-side-plate", x, 0, -15, 10, 80, 34, "teal", 2);
|
||||
cylinder(tilt, "a-trunnion-shaft", x, 0, 0, 10, 22, "steel", "x");
|
||||
cylinder(tilt, `${rotaryAxis}-trunnion-shaft`, x, 0, 0, 10, 22, "steel", "x");
|
||||
}
|
||||
const rotaryBase = group(tilt, "c-rotary-base");
|
||||
box(rotaryBase, "c-drive-housing", 0, 0, -24, 104, 100, 12, "dark", 3);
|
||||
@@ -178,15 +180,34 @@ export function buildXyzacMachine() {
|
||||
let toolLength = NaN;
|
||||
const update = (state: Pick<XyzacVisualState, "joints" | "offsets">) => {
|
||||
const [tableXJoint, saddleYJoint, spindleZJoint, tiltAJoint, rotateCJoint] = state.joints;
|
||||
const xOffset = Number(state.offsets.x_offset ?? 0);
|
||||
const yOffset = Number(state.offsets.y_offset ?? 0);
|
||||
const zOffset = Number(state.offsets.z_offset ?? 0);
|
||||
tableX.position.x = -tableXJoint * MM;
|
||||
saddle.position.z = -saddleYJoint * MM;
|
||||
zslide.position.y = (200 - spindleZJoint) * MM;
|
||||
tilt.rotation.x = -tiltAJoint * DEG;
|
||||
if (coordinates === "XYZBC") {
|
||||
head.position.y = (150 + spindleZJoint) * MM;
|
||||
// XYZBC is a table tilt: the source model rotates the B assembly
|
||||
// around machine Y. The Web scene maps source Y to Three.js Z.
|
||||
head.rotation.set(0, 0, 0);
|
||||
zslide.position.y = 0;
|
||||
tilt.rotation.set(0, 0, -tiltAJoint * DEG);
|
||||
// xyzbc-trt-gui applies x-offset to both sides of the B table and
|
||||
// applies z-offset with opposite signs to the inner/outer groups.
|
||||
tilt.position.set(xOffset * MM, zOffset * MM, yOffset * MM);
|
||||
brackets.position.copy(tilt.position);
|
||||
rotaryBase.position.set(xOffset * MM, -zOffset * MM, 0);
|
||||
} else {
|
||||
zslide.position.y = (200 - spindleZJoint) * MM;
|
||||
head.position.y = 2;
|
||||
head.rotation.set(0, 0, 0);
|
||||
tilt.rotation.x = -tiltAJoint * DEG;
|
||||
tilt.position.set(0, zOffset * MM, yOffset * MM);
|
||||
brackets.position.copy(tilt.position);
|
||||
rotaryBase.position.copy(tilt.position).negate();
|
||||
}
|
||||
rotate.rotation.y = -rotateCJoint * DEG;
|
||||
tool.position.y = -state.offsets.tool_offset * MM;
|
||||
tilt.position.set(0, state.offsets.z_offset * MM, state.offsets.y_offset * MM);
|
||||
brackets.position.copy(tilt.position);
|
||||
rotaryBase.position.copy(tilt.position).negate();
|
||||
if (toolLength !== state.offsets.tool_offset) {
|
||||
toolLength = state.offsets.tool_offset;
|
||||
const reach = Math.max(.5, 10 + toolLength);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { XyzacCapabilitySnapshot, XyzacKinematicsSnapshot } from "./protocol";
|
||||
import { DEFAULT_MACHINE, MACHINE_DEFINITIONS } from "../../workspace/machine-definitions.mjs";
|
||||
|
||||
export const XYZAC_CASE = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini" as const;
|
||||
export const XYZAC_AXES = ["x", "y", "z", "a", "c"] as const;
|
||||
export const MACHINE_CASES = MACHINE_DEFINITIONS;
|
||||
export const XYZAC_CASE = DEFAULT_MACHINE.entrypoint;
|
||||
export const XYZAC_AXES: readonly string[] = DEFAULT_MACHINE.axes;
|
||||
export const XYZAC_COMMANDS = {
|
||||
power: "POWER",
|
||||
selectKinematics: "KINEMATICS_SELECT",
|
||||
@@ -25,6 +27,7 @@ export type XyzacPanelView = {
|
||||
joints: number[];
|
||||
gates: Record<string, XyzacGate>;
|
||||
recovery: Record<string, unknown> | null;
|
||||
machine: typeof DEFAULT_MACHINE;
|
||||
};
|
||||
|
||||
type XyzacPanelSource = {
|
||||
@@ -38,13 +41,18 @@ type XyzacPanelSource = {
|
||||
};
|
||||
|
||||
export function projectXyzacPanel(snapshot: XyzacPanelSource | null | undefined): XyzacPanelView {
|
||||
const kinematics = snapshot?.kinematics?.coordinates === "XYZAC" && snapshot.kinematics.joint_count === 5 ? snapshot.kinematics : null;
|
||||
const capability = snapshot?.xyzac?.coordinates === "XYZAC" && snapshot.xyzac.joint_count === 5 ? snapshot.xyzac : null;
|
||||
const rawKinematics = snapshot?.kinematics;
|
||||
const rawCapability = snapshot?.xyzac;
|
||||
const machine = MACHINE_DEFINITIONS.find((entry) => entry.coordinates === rawKinematics?.coordinates) ?? DEFAULT_MACHINE;
|
||||
const kinematics = rawKinematics && rawKinematics.coordinates === machine.coordinates && rawKinematics.joint_count === machine.joint_count ? rawKinematics : null;
|
||||
const capability = rawCapability && rawCapability.coordinates === machine.coordinates && rawCapability.joint_count === machine.joint_count ? rawCapability : null;
|
||||
const visualPose = snapshot?.visual?.pose;
|
||||
const pose = visualPose && [visualPose.x, visualPose.y, visualPose.z, visualPose.a, visualPose.c].every((value) => Number.isFinite(value))
|
||||
const poseAxes = [...machine.coordinates.toLowerCase()];
|
||||
const pose = visualPose && poseAxes.every((axis) => Number.isFinite(visualPose[axis]))
|
||||
? { ...(kinematics?.pose ?? { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0, u: 0, v: 0, w: 0 }), ...visualPose }
|
||||
: kinematics?.pose ?? null;
|
||||
const joints = snapshot?.visual?.joints?.length === 5 ? snapshot.visual.joints.slice(0, 5) : kinematics?.joints ? [...kinematics.joints] : [];
|
||||
const visualJoints = snapshot?.visual?.joints;
|
||||
const joints = visualJoints?.length === machine.joint_count ? visualJoints.slice(0, machine.joint_count) : kinematics?.joints ? [...kinematics.joints] : [];
|
||||
return {
|
||||
status: kinematics && capability ? "ready" : "unavailable",
|
||||
tick: snapshot?.simulation_tick ?? 0,
|
||||
@@ -55,5 +63,6 @@ export function projectXyzacPanel(snapshot: XyzacPanelSource | null | undefined)
|
||||
joints,
|
||||
gates: snapshot?.gates ?? {},
|
||||
recovery: (snapshot?.authoritative?.recovery as Record<string, unknown> | undefined) ?? null,
|
||||
machine,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,8 +35,10 @@ export type XyzacVisualSource = {
|
||||
|
||||
export function projectXyzacVisual(snapshot: XyzacVisualSource | null | undefined): XyzacVisualState {
|
||||
const kinematics = snapshot?.kinematics;
|
||||
const coordinateSystem = String(kinematics?.coordinates ?? snapshot?.visual?.machine_model?.coordinate_system ?? "XYZAC");
|
||||
const poseAxes = [...coordinateSystem.toLowerCase()];
|
||||
const visualPose = snapshot?.visual?.pose;
|
||||
const pose = visualPose && [visualPose.x, visualPose.y, visualPose.z, visualPose.a, visualPose.c].every((value) => Number.isFinite(value))
|
||||
const pose = visualPose && poseAxes.every((axis) => Number.isFinite(visualPose[axis]))
|
||||
? { ...ZERO_POSE, ...visualPose }
|
||||
: kinematics?.pose ?? ZERO_POSE;
|
||||
const visualJoints = snapshot?.visual?.joints;
|
||||
|
||||
@@ -40,6 +40,7 @@ function linuxcncAssets(): Plugin {
|
||||
["linuxcnc/share/axis/images", "linuxcnc/share/axis/images"],
|
||||
["linuxcnc/lib/hallib", "linuxcnc/lib/hallib"],
|
||||
["linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py", "linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py"],
|
||||
["linuxcnc/src/hal/user_comps/vismach/xyzbc-trt-gui.py", "linuxcnc/src/hal/user_comps/vismach/xyzbc-trt-gui.py"],
|
||||
["linuxcnc/configs/sim/qtvcp_screens/qtaxis_lathe/qtaxis/images", "linuxcnc/configs/sim/qtvcp_screens/qtaxis_lathe/qtaxis/images"],
|
||||
];
|
||||
for (const [from, to] of copies) cpSync(resolve(repoRoot, from), resolve(output, to), { recursive: true, dereference: false });
|
||||
|
||||
Reference in New Issue
Block a user