完善 xyzbc-trt 刀具偏置闭环

This commit is contained in:
mes123456
2026-07-02 09:41:10 -04:00
parent bb939644b9
commit 8fd9952dac
16 changed files with 15926 additions and 10971 deletions

View File

@@ -8,6 +8,7 @@
"dev": "python3 -m http.server 4174",
"smoke": "npm run smoke:node",
"smoke:node": "node ../tests/node/verify_xyzbc_trt_web_app.mjs",
"smoke:browser": "bash ../tests/browser/verify_xyzbc_trt_browser.sh",
"evidence:web": "node ../tools/collect-web-xyzbc-trt-evidence.mjs",
"evidence:compare": "node ../tools/compare-xyzbc-trt-evidence.mjs"
},

View File

@@ -553,6 +553,78 @@ export function createToolDbReadiness(toolDb) {
};
}
export function createToolRuntimeState(toolDb, options = {}) {
if (!toolDb) {
return {
apiName: "web-rtcp-5axis-tool-runtime-state",
ready: false,
activeOffsetApplied: false,
toolInSpindle: 0,
toolFromPocket: 0,
currentPocket: 0,
activeToolNumber: 0,
activePocket: 0,
currentTool: null,
activeToolOffset: null,
kinematics: {
toolOffsetZ: 0,
source: "tool_db_not_ready",
},
pathTool: createPathTool(null, 0),
vismach: {
toolOffset: finiteNumber(options.fallbackToolLength, 0),
source: "fallback_tool_preview_length",
},
interpreterParameters: createToolParameters(null),
semanticBoundary: "linuxcnc_current_tool_offset_runtime_state_for_web_simulation",
};
}
const state = cloneToolDb(toolDb);
const activeOffset = state.activeToolOffset
? {
...state.activeToolOffset,
offset: { ...state.activeToolOffset.offset },
}
: null;
const currentTool = state.toolTableCurrent
? cloneToolEntry(state.toolTableCurrent)
: cloneToolEntry(state.toolTable.pockets[0] || createEmptyToolEntry({ idx: 0, pocket: 0, toolNumber: 0 }));
const activeToolNumber = activeOffset?.toolNumber
?? (currentTool.toolNumber > 0 ? currentTool.toolNumber : 0);
const activePocket = activeOffset?.pocket
?? (currentTool.toolNumber > 0 ? currentTool.pocket : 0);
const activeOffsetZ = activeOffset ? finiteNumber(activeOffset.offset?.z, 0) : 0;
return {
apiName: "web-rtcp-5axis-tool-runtime-state",
ready: state.processReady === true,
activeOffsetApplied: Boolean(activeOffset),
toolInSpindle: state.toolInSpindle,
toolFromPocket: state.toolFromPocket,
currentPocket: state.currentPocket,
activeToolNumber,
activePocket,
currentTool,
activeToolOffset: activeOffset,
kinematics: {
toolOffsetZ: activeOffsetZ,
source: activeOffset ? "activeToolOffset.offset.z" : "no_active_g43_tool_length_offset",
halPin: "motion.tooloffset.z",
downstreamPins: ["xyzbc-trt-kins.tool-offset", "xyzbc-trt-gui.tool-offset"],
},
pathTool: createPathTool(activeOffset || currentTool, activeOffsetZ),
vismach: {
toolOffset: activeOffset
? activeOffsetZ
: finiteNumber(options.fallbackToolLength, 0),
source: activeOffset ? "activeToolOffset.offset.z" : "fallback_tool_preview_length",
},
interpreterParameters: { ...state.interpreterParameters },
semanticBoundary: "linuxcnc_current_tool_offset_runtime_state_for_web_simulation",
};
}
export function detectToolDbStorageCapability(globalScope = globalThis) {
const forcedUnavailable = Boolean(globalScope.__WEB_RTCP_FORCE_OPFS_UNAVAILABLE__);
const isBrowser = typeof globalScope.window === "object" || typeof globalScope.document === "object";
@@ -855,6 +927,23 @@ function createToolParameters(activeOffset) {
return parameters;
}
function createPathTool(tool, length) {
return {
id: Number(tool?.toolNumber) || 0,
pocket: Number(tool?.pocket) || 0,
length: finiteNumber(length, 0),
diameter: finiteNumber(tool?.diameter, 0),
};
}
function finiteNumber(...values) {
for (const value of values) {
const number = Number(value);
if (Number.isFinite(number)) return number;
}
return 0;
}
function createToolDbEvent(code, payload = {}) {
return {
apiName: "web-rtcp-5axis-tool-db-event",

View File

@@ -0,0 +1,111 @@
const DEFAULT_LINEAR_UNITS = "mm";
export const XYZBC_TRT_VISMACH_PINS = [
"table-x",
"saddle-y",
"spindle-z",
"tilt-b",
"rotate-c",
"tool-offset",
"x-offset",
"z-offset",
];
export function buildVismachModelState(state = {}) {
const profile = state.profile || {};
const axisPose = resolveAxisPose(state);
const offsets = profile.offsets || {};
const toolOffset = resolveToolOffset(state);
const xOffset = finiteNumber(offsets.x, -20);
const zOffset = finiteNumber(offsets.z, -15);
const linearUnits = resolveLinearUnits(state);
return {
apiName: "web-rtcp-5axis-vismach-model-state",
profileId: profile.id || state.machineProfile || null,
sourceGui: "src/hal/user_comps/vismach/xyzbc-trt-gui.py",
webModel: "app/src/visualization/five-axis-scene.js",
linearUnits,
pins: {
"table-x": finiteNumber(axisPose.x, 0),
"saddle-y": finiteNumber(axisPose.y, 0),
"spindle-z": finiteNumber(axisPose.z, 0),
"tilt-b": finiteNumber(axisPose.b, 0),
"rotate-c": finiteNumber(axisPose.c, 0),
"tool-offset": toolOffset,
"x-offset": xOffset,
"z-offset": zOffset,
},
transforms: {
table: {
translate: { x: finiteNumber(axisPose.x, 0), y: 0, z: 0 },
sourcePins: ["table-x"],
},
saddle: {
translate: { x: 0, y: finiteNumber(axisPose.y, 0), z: 0 },
sourcePins: ["saddle-y"],
},
spindle: {
translate: { x: 0, y: 0, z: finiteNumber(axisPose.z, 0) },
sourcePins: ["spindle-z"],
},
tilt: {
rotateDeg: { x: 0, y: finiteNumber(axisPose.b, 0), z: 0 },
sourcePins: ["tilt-b"],
},
rotary: {
rotateDeg: { x: 0, y: 0, z: finiteNumber(axisPose.c, 0) },
sourcePins: ["rotate-c"],
},
tool: {
translate: { x: xOffset, y: 0, z: zOffset - toolOffset },
sourcePins: ["tool-offset", "x-offset", "z-offset"],
},
},
halNets: [
{ signal: "table-x", source: "joint.0.pos-fb", target: "xyzbc-trt-gui.table-x" },
{ signal: "saddle-y", source: "joint.1.pos-fb", target: "xyzbc-trt-gui.saddle-y" },
{ signal: "spindle-z", source: "joint.2.pos-fb", target: "xyzbc-trt-gui.spindle-z" },
{ signal: "tilt-b", source: "joint.3.pos-fb", target: "xyzbc-trt-gui.tilt-b" },
{ signal: "rotate-c", source: "joint.4.pos-fb", target: "xyzbc-trt-gui.rotate-c" },
{ signal: "tool-offset", source: "motion.tooloffset.z", target: "xyzbc-trt-kins.tool-offset" },
{ signal: "tool-offset", source: "xyzbc-trt-kins.tool-offset", target: "xyzbc-trt-gui.tool-offset" },
{ signal: "x-offset", source: "xyzbc-trt-kins.x-offset", target: "xyzbc-trt-gui.x-offset" },
{ signal: "z-offset", source: "xyzbc-trt-kins.z-offset", target: "xyzbc-trt-gui.z-offset" },
],
clearTraceSignal: "pyvcp.vismach-clear => vismach.plotclear",
semanticBoundary: "web_threejs_vismach_equivalent_driven_by_xyzbc_trt_hal_pins",
};
}
function resolveAxisPose(state) {
if (state.programRuntimeFeedback?.axisPose) return state.programRuntimeFeedback.axisPose;
if (state.taskHalStatus?.ui?.axisPose) return state.taskHalStatus.ui.axisPose;
return state.axisPose || {};
}
function resolveToolOffset(state) {
if (Number.isFinite(Number(state.toolRuntimeState?.vismach?.toolOffset))) {
return Number(state.toolRuntimeState.vismach.toolOffset);
}
return finiteNumber(
state.toolDbSimulation?.activeToolOffset?.offset?.z,
state.toolPreview?.length,
0,
);
}
function resolveLinearUnits(state) {
return state.linuxCncIniConfig?.traj?.linearUnits
|| state.programRuntimeFeedback?.linearUnits
|| state.programExecutionTiming?.linearUnits
|| DEFAULT_LINEAR_UNITS;
}
function finiteNumber(...values) {
for (const value of values) {
const number = Number(value);
if (Number.isFinite(number)) return number;
}
return 0;
}

View File

@@ -5,6 +5,7 @@ import {
linearValueToMeters,
resolveStateLinearUnits,
} from "../runtime/linear-units.js";
import { buildVismachModelState } from "../runtime/vismach-model-state.js";
const scenes = new WeakMap();
const CAMERA_PRESETS = {
@@ -62,6 +63,7 @@ export function renderFiveAxisScene(canvas, state) {
preview.executedPath.geometry,
preview.currentSegmentPath.geometry,
]),
vismachModel: preview.currentVismachModelState,
});
}
@@ -123,6 +125,7 @@ function createScene(canvas) {
toolMarker,
toolAxis,
currentToolhead: new THREE.Vector3(),
currentVismachModelState: null,
lastSelectedView: null,
lastCameraRevision: null,
lastFitKey: null,
@@ -177,6 +180,7 @@ function renderFallbackPreview(preview, state) {
currentSegmentPointCount: currentSegmentPoints.length,
pathFitBounds: computePointBounds(previewPoints.concat(executedPoints, currentSegmentPoints)) !== null,
pathBounds: summarizeBounds(computePointBounds(previewPoints.concat(executedPoints, currentSegmentPoints))),
vismachModel: buildVismachModelState(state),
});
canvas.dataset.threeFallbackReason = preview.errorMessage;
@@ -268,6 +272,9 @@ function exposePreviewDataset(canvas, state, preview) {
canvas.dataset.threeArcPathPoints = String(preview.arcPointCount || 0);
canvas.dataset.threeCurrentSegmentPoints = String(preview.currentSegmentPointCount || 0);
canvas.dataset.threeCurrentSegmentType = currentSegmentType(state);
canvas.dataset.threeVismachPins = JSON.stringify(preview.vismachModel?.pins || {});
canvas.dataset.threeVismachTransforms = JSON.stringify(preview.vismachModel?.transforms || {});
canvas.dataset.threeVismachPinDrivenModel = preview.vismachModel?.semanticBoundary || "none";
}
function createLine(color, opacity) {
@@ -291,6 +298,19 @@ function createMachineReferenceModel() {
);
base.position.z = -0.016;
const tableAxis = new THREE.Group();
tableAxis.name = "vismach-table-x";
const saddleAxis = new THREE.Group();
saddleAxis.name = "vismach-saddle-y";
const spindleAxis = new THREE.Group();
spindleAxis.name = "vismach-spindle-z";
const tiltAxis = new THREE.Group();
tiltAxis.name = "vismach-tilt-b";
const rotaryAxis = new THREE.Group();
rotaryAxis.name = "vismach-rotate-c";
const toolOffsetAxis = new THREE.Group();
toolOffsetAxis.name = "vismach-tool-offset";
const table = new THREE.Mesh(
new THREE.BoxGeometry(0.37, 0.235, 0.005),
new THREE.MeshBasicMaterial({ color: 0x3a444d, transparent: true, opacity: 0.78 }),
@@ -329,9 +349,21 @@ function createMachineReferenceModel() {
cutter.position.z = 0.008;
toolHolder.add(holderBody, cutter);
root.add(base, table, xAxis, yAxis, zAxis, rotaryA, rotaryC, toolHolder);
toolOffsetAxis.add(toolHolder);
spindleAxis.add(toolOffsetAxis);
rotaryAxis.add(table, rotaryC);
tiltAxis.add(rotaryA, rotaryAxis);
saddleAxis.add(tiltAxis);
tableAxis.add(saddleAxis);
root.add(base, xAxis, yAxis, zAxis, tableAxis, spindleAxis);
return {
root,
tableAxis,
saddleAxis,
spindleAxis,
tiltAxis,
rotaryAxis,
toolOffsetAxis,
rotaryA,
rotaryC,
toolHolder,
@@ -410,15 +442,23 @@ function updateToolExecutionMarker(preview, state, toolPosition) {
function updateMachineReferenceModel(preview, state, toolPosition) {
const model = preview.machineModel;
if (!model) return;
const a = degreesToRadians(state.axisPose?.a);
const b = degreesToRadians(state.axisPose?.b);
const c = degreesToRadians(state.axisPose?.c);
model.rotaryA.rotation.x = a;
model.rotaryA.rotation.y = Math.PI / 2 + b;
model.rotaryC.rotation.z = c;
const vismach = buildVismachModelState(state);
preview.currentVismachModelState = summarizeVismachModelStateForDataset(vismach);
const toMeters = (value) => linearValueToMeters(value, vismach.linearUnits);
model.tableAxis.position.x = toMeters(vismach.transforms.table.translate.x);
model.saddleAxis.position.y = toMeters(vismach.transforms.saddle.translate.y);
model.spindleAxis.position.z = toMeters(vismach.transforms.spindle.translate.z);
model.tiltAxis.rotation.y = degreesToRadians(vismach.transforms.tilt.rotateDeg.y);
model.rotaryAxis.rotation.z = degreesToRadians(vismach.transforms.rotary.rotateDeg.z);
model.toolOffsetAxis.position.set(
toMeters(vismach.transforms.tool.translate.x),
0,
toMeters(vismach.transforms.tool.translate.z),
);
model.rotaryA.rotation.y = Math.PI / 2;
model.rotaryC.rotation.x = Math.PI / 2;
const tcpPosition = toolPosition || toPreviewVector(state.tcpPose || state.axisPose, state);
model.toolHolder.position.copy(tcpPosition);
const toolVector = toToolVector(state.toolAxisVector);
model.toolHolder.lookAt(tcpPosition.clone().add(toolVector));
}
@@ -652,6 +692,26 @@ function summarizeBounds(bounds) {
};
}
function summarizeVismachModelStateForDataset(vismach) {
const summarizeVector = (vector = {}) => ({
x: round(vector.x),
y: round(vector.y),
z: round(vector.z),
});
return {
...vismach,
pins: Object.fromEntries(Object.entries(vismach.pins).map(([key, value]) => [key, round(value)])),
transforms: {
table: { ...vismach.transforms.table, translate: summarizeVector(vismach.transforms.table.translate) },
saddle: { ...vismach.transforms.saddle, translate: summarizeVector(vismach.transforms.saddle.translate) },
spindle: { ...vismach.transforms.spindle, translate: summarizeVector(vismach.transforms.spindle.translate) },
tilt: { ...vismach.transforms.tilt },
rotary: { ...vismach.transforms.rotary },
tool: { ...vismach.transforms.tool, translate: summarizeVector(vismach.transforms.tool.translate) },
},
};
}
function drawFallbackPolyline(ctx, points, cx, cy, scale) {
for (let index = 0; index < points.length; index += 1) {
const point = points[index];