完善 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,8 +8,16 @@ import {
selectMachineFileProgram,
stageProfileMachineFiles,
} from "../app/src/runtime/linuxcnc-machine-file-staging.js";
import {
applyToolCommandSequence,
createToolDbSimulation,
createToolRuntimeState,
extractToolCommandSequenceFromProgram,
parseLinuxCncToolTable,
} from "../app/src/runtime/tool-db-simulation.js";
import { createSimulationStore } from "../app/src/state/store.js";
import { getFiveAxisProfile } from "../app/src/profiles/index.js";
import { buildVismachModelState } from "../app/src/runtime/vismach-model-state.js";
const SAMPLE_PERIOD_MS = 20;
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
@@ -42,7 +50,13 @@ const paths = await collectPathEvidence({
selectedPlan,
wasmArtifacts,
});
const semanticFields = buildSemanticFields({ profile, ini, staged, state });
const toolRuntime = buildToolRuntimeEvidence({
profile,
staged,
selectedPlan,
fallbackToolLength: state.toolPreview?.length,
});
const semanticFields = buildSemanticFields({ profile, ini, staged, state, toolRuntime });
const evidence = {
apiName: "xyzbc-trt-web-opfs-wasm-evidence",
@@ -110,6 +124,7 @@ const evidence = {
pathSampling: createPathSampling(),
previewPath: paths.previewPath,
executionPath: paths.executionPath,
toolRuntime,
...semanticFields,
wasm: wasmArtifacts,
coverage: {
@@ -137,6 +152,11 @@ const evidence = {
&& Boolean(semanticFields.axisJointLimits.axisLimits.C),
previewPathAvailable: paths.previewPath.sampleCount > 0,
executionPathAvailable: paths.executionPath.sampleCount > 0,
toolTableToToolOffsetClosed: toolRuntime.ready
&& toolRuntime.toolTable.toolCount > 0
&& toolRuntime.activeOffsetApplied
&& toolRuntime.kinematics.toolOffsetZ === toolRuntime.pathTool.length
&& toolRuntime.vismach.toolOffset === toolRuntime.pathTool.length,
wasmArtifactsReady: wasmArtifacts.ready,
},
blockers: [
@@ -222,11 +242,16 @@ async function collectPathEvidence({ profile, staged, selectedPlan, wasmArtifact
files: staged.save.files,
executionMode: "fiveAxisRemap",
});
const previewPath = pathFromWebMotion(execution, profile);
const toolRuntime = buildToolRuntimeEvidence({
profile,
staged,
selectedPlan,
});
const previewPath = pathFromWebMotion(execution, profile, toolRuntime.pathTool);
return {
previewPath,
executionPath: wasmArtifacts.ready
? await pathFromTaskHalExecution({ profile, staged, selectedPlan, execution })
? await pathFromTaskHalExecution({ profile, staged, selectedPlan, execution, toolRuntime })
: emptyPath("web-task-hal", "missing task/HAL WASM runtime artifacts"),
};
} catch (error) {
@@ -237,7 +262,7 @@ async function collectPathEvidence({ profile, staged, selectedPlan, wasmArtifact
}
}
function pathFromWebMotion(execution, profile) {
function pathFromWebMotion(execution, profile, pathTool = null) {
const plannerSamples = execution.plannerTiming?.samples || [];
const motion = execution.motion || [];
const motionByIndex = new Map(motion.map((event, index) => [index, event]));
@@ -252,7 +277,7 @@ function pathFromWebMotion(execution, profile) {
motionType: motionTypeFromCanonical(sample.type || event.type),
activeKinematics: activeKinematics(event),
axes,
tool: firstTool(profile),
tool: pathTool || firstTool(profile),
feed: sample.currentVelocityMmPerMin ?? event.feedRate ?? 0,
spindle: 0,
});
@@ -311,7 +336,7 @@ function interpolatePlannerSample(left, right, timeMs) {
};
}
async function pathFromTaskHalExecution({ profile, staged, selectedPlan, execution }) {
async function pathFromTaskHalExecution({ profile, staged, selectedPlan, execution, toolRuntime = null }) {
try {
const { createLinuxCncTaskHalSdk } = await import("../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js");
const {
@@ -377,7 +402,7 @@ async function pathFromTaskHalExecution({ profile, staged, selectedPlan, executi
switchkinsType: status.ui?.switchkinsType ?? motion.switchkinsType,
}),
axes: xyzbcAxesFromTaskHalStatus(status),
tool: firstTool(profile),
tool: toolRuntime?.pathTool || firstTool(profile),
feed: status.ui?.currentVelocity ?? Number(motion.currentVel || motion.currentVelocity || 0) * 60,
spindle: spindleFromTaskHalStatus(status),
});
@@ -409,7 +434,7 @@ async function pathFromTaskHalExecution({ profile, staged, selectedPlan, executi
}
}
function buildSemanticFields({ profile, ini, staged, state }) {
function buildSemanticFields({ profile, ini, staged, state, toolRuntime }) {
const postguiNets = [
{ signal: "kinstype.is-0", source: "kinstype.is-0", target: "pyvcp.multilabel.0.legend0", boundary: "postgui-hal" },
{ signal: "kinstype.is-1", source: "kinstype.is-1", target: "pyvcp.multilabel.0.legend1", boundary: "postgui-hal" },
@@ -425,6 +450,10 @@ function buildSemanticFields({ profile, ini, staged, state }) {
...(profile.hal?.halcmd?.offsetNets || []),
].filter(Boolean).map((net) => ({ ...net, boundary: "ini-halcmd" }));
const gcodeFiles = staged.save.gcodeFiles || [];
const vismachModelState = buildVismachModelState({
...state,
toolRuntimeState: toolRuntime,
});
return {
startupSequence: [
@@ -460,6 +489,8 @@ function buildSemanticFields({ profile, ini, staged, state }) {
zRotPoint: profile.offsets?.zRotPoint,
conventionalDirections: profile.offsets?.conventionalDirections,
toolOffsetSource: "motion.tooloffset.z",
toolOffsetValue: toolRuntime.kinematics.toolOffsetZ,
toolOffsetToolNumber: toolRuntime.activeToolNumber,
pins: profile.halPins,
},
axisJointLimits: {
@@ -498,17 +529,26 @@ function buildSemanticFields({ profile, ini, staged, state }) {
vismachEquivalent: {
sourceGui: "src/hal/user_comps/vismach/xyzbc-trt-gui.py",
webModel: "app/src/visualization/five-axis-scene.js",
pins: [
"table-x",
"saddle-y",
"spindle-z",
"tilt-b",
"rotate-c",
"tool-offset",
"x-offset",
"z-offset",
],
pins: Object.keys(vismachModelState.pins),
pinValues: vismachModelState.pins,
transforms: vismachModelState.transforms,
halNets: vismachModelState.halNets,
clearTraceSignal: "pyvcp.vismach-clear => vismach.plotclear",
semanticBoundary: vismachModelState.semanticBoundary,
},
toolOffsetClosure: {
sourceToolTable: toolRuntime.toolTable.sourceRel,
selectedProgramSourceRel: toolRuntime.selectedProgramSourceRel,
programToolCommands: toolRuntime.programToolCommands,
activeToolOffset: toolRuntime.activeToolOffset,
kinematicsToolOffsetZ: toolRuntime.kinematics.toolOffsetZ,
pathTool: toolRuntime.pathTool,
vismachToolOffset: toolRuntime.vismach.toolOffset,
closed: toolRuntime.ready
&& toolRuntime.activeOffsetApplied
&& toolRuntime.kinematics.toolOffsetZ === toolRuntime.pathTool.length
&& toolRuntime.vismach.toolOffset === toolRuntime.pathTool.length,
semanticBoundary: "tool_table_current_t_p_z_d_drives_kinematics_path_and_vismach",
},
ngcguiSubroutines: ["xyzbc_switchkins_sub.ngc", "centering.ngc", "helix_bc.ngc"].map((filename) => ({
filename,
@@ -524,6 +564,58 @@ function buildSemanticFields({ profile, ini, staged, state }) {
};
}
function buildToolRuntimeEvidence({ profile, staged, selectedPlan, fallbackToolLength = 0 }) {
const toolTableFile = staged.save.files.find((file) => file.sourceRel === profile.toolTablePath)
|| staged.save.files.find((file) => file.kind === "toolTable");
const programFile = staged.save.files.find((file) => (
file.sourceRel === selectedPlan.selectedProgramSourceRel
|| (file.wasmPath || file.path) === selectedPlan.wasmProgramPath
));
const toolTable = parseLinuxCncToolTable(toolTableFile?.text || "", {
sourceRel: toolTableFile?.sourceRel || profile.toolTablePath,
path: toolTableFile?.wasmPath || toolTableFile?.path || null,
});
const programToolCommands = extractToolCommandSequenceFromProgram(programFile?.text || "");
const commands = programToolCommands.length > 0
? programToolCommands
: defaultToolActivationCommands(toolTable);
const toolDb = applyToolCommandSequence(createToolDbSimulation({
toolTable,
profile,
storageMode: staged.save.storageMode,
}), commands);
const runtimeState = createToolRuntimeState(toolDb, { fallbackToolLength });
return {
...runtimeState,
selectedProgramSourceRel: selectedPlan.selectedProgramSourceRel,
toolTable: {
sourceRel: toolTable.sourceRel,
toolCount: toolTable.toolCount,
entries: toolTable.entries.map((entry) => ({
idx: entry.idx,
toolNumber: entry.toolNumber,
pocket: entry.pocket,
z: entry.offset.z,
diameter: entry.diameter,
})),
},
programToolCommands,
appliedToolCommands: commands,
defaultActivationUsed: programToolCommands.length === 0,
};
}
function defaultToolActivationCommands(toolTable) {
const preferred = toolTable.entries.find((entry) => entry.offset.z !== 0)
|| toolTable.entries.find((entry) => entry.toolNumber > 0);
if (!preferred) return [];
return [
{ code: "T", toolNumber: preferred.toolNumber, source: "default-tool-table-activation" },
{ code: "M6", source: "default-tool-table-activation" },
{ code: "G43", h: preferred.toolNumber, toolNumber: preferred.toolNumber, source: "default-tool-table-activation" },
];
}
function createPathSampling() {
return {
samplePeriodMs: SAMPLE_PERIOD_MS,

View File

@@ -37,6 +37,17 @@ const checks = [
check("tool-table", "web tool table staged", webEvidence.coverage?.toolTableStaged === true, {
toolTable: webEvidence.ini?.toolTable,
}),
check("tool-table", "web tool table active offset drives kinematics, path, and Vismach", webEvidence.coverage?.toolTableToToolOffsetClosed === true, {
toolRuntime: {
activeToolNumber: webEvidence.toolRuntime?.activeToolNumber,
activePocket: webEvidence.toolRuntime?.activePocket,
kinematicsToolOffsetZ: webEvidence.toolRuntime?.kinematics?.toolOffsetZ,
pathTool: webEvidence.toolRuntime?.pathTool,
vismachToolOffset: webEvidence.toolRuntime?.vismach?.toolOffset,
defaultActivationUsed: webEvidence.toolRuntime?.defaultActivationUsed,
},
toolOffsetClosure: webEvidence.toolOffsetClosure,
}),
check("pyvcp", "web PyVCP XML staged", webEvidence.coverage?.pyvcpXmlStaged === true, {
pyvcpXmlPath: webEvidence.profile?.pyvcpXmlPath,
}),