继续完成 web-rtcp-5axis-sim-plan

结论:完成 LinuxCNC kinematics WASM ABI 覆盖,并将 web-rtcp-5axis-sim-plan 的 RTCP frame/boundary adapter 接到 xyzac-trt kinematics SDK;Node、build、browser smoke 验证通过。
This commit is contained in:
2026-06-21 16:44:29 +08:00
parent a6eda3fbff
commit 626bcfe8e3
101 changed files with 101586 additions and 770 deletions

View File

@@ -0,0 +1,81 @@
import { xyzacTrtProfile } from "../profiles/xyzac-trt.js";
import { createPyvcpHalBindingSummary, xyzacTrtPyvcpPanelSchema } from "../panel-schema/xyzac-trt-pyvcp.js";
import { createProfileSourceReferenceSummary } from "../profiles/source-reference-map.js";
export function createLinuxCncBoundaryAdapter({
profile = xyzacTrtProfile,
panelSchema = xyzacTrtPyvcpPanelSchema,
runtime = null,
} = {}) {
const sourceSummary = createProfileSourceReferenceSummary(profile.id);
const panelSummary = createPyvcpHalBindingSummary(panelSchema);
const kinematicsRuntimeReady = Boolean(runtime?.kinematicsWasm?.loaded);
const interpreterRuntimeReady = Boolean(runtime?.interpreterWasm?.loaded);
const runtimeReady = kinematicsRuntimeReady && interpreterRuntimeReady;
const linuxCncKinematicsReady = kinematicsRuntimeReady
&& runtime.kinematicsWasm.sourceMode === "source-derived-kinematics-wasm";
return {
apiName: "web-rtcp-5axis-linuxcnc-boundary-adapter",
profileId: profile.id,
profile,
sourceSummary,
panelSummary,
runtimeReady,
kinematicsRuntimeReady,
interpreterRuntimeReady,
profileSummary: createProfileSummary(profile),
linuxCncKinematicsReady,
promotionAllowed: linuxCncKinematicsReady,
fullLinuxCncProgramExecutionReady: false,
semanticBoundary: linuxCncKinematicsReady
? interpreterRuntimeReady
? "linuxcnc_runtime_supplied_but_interpreter_or_remap_not_promoted"
: "linuxcnc_kinematics_wasm_runtime_connected"
: "adapter_entrypoint_only_runtime_not_connected",
adapterPoints: {
kinematicsWasm: runtime?.kinematicsWasm || null,
interpreterWasm: runtime?.interpreterWasm || null,
halPins: profile.halPins,
pyvcpSchemaId: panelSchema.id,
},
};
}
function createProfileSummary(profile) {
return {
machineName: profile.machineName,
coordinates: profile.traj.coordinates,
jointCount: profile.jointConfig.length,
axisCount: Object.keys(profile.axisLimits).length,
mdiCommandCount: profile.halui.mdiCommands.length,
remapCount: profile.remaps.length,
toolCount: profile.toolTable.toolCount,
offsetNetCount: profile.hal.halcmd.offsetNets.length,
feedbackNetCount: profile.hal.halcmd.feedbackNets.length,
halFileCount: profile.hal.halFiles.length + profile.hal.postguiHalFiles.length,
sourceKinds: profile.sourceReferenceObjects.map((reference) => reference.kind),
};
}
export function createLinuxCncBoundaryReadiness(adapter = createLinuxCncBoundaryAdapter()) {
const missing = [];
if (!adapter.kinematicsRuntimeReady) missing.push("kinematics runtime");
if (!adapter.interpreterRuntimeReady) missing.push("interpreter/remap runtime");
if (!adapter.sourceSummary.referenceCount) missing.push("source reference map");
if (!adapter.panelSummary.controlCount) missing.push("PyVCP/HAL panel schema");
return {
apiName: "web-rtcp-5axis-linuxcnc-boundary-readiness",
profileId: adapter.profileId,
ready: adapter.linuxCncKinematicsReady
&& !missing.includes("kinematics runtime")
&& !missing.includes("source reference map")
&& !missing.includes("PyVCP/HAL panel schema"),
missing,
linuxCncKinematicsReady: adapter.linuxCncKinematicsReady,
promotionAllowed: adapter.promotionAllowed,
fullLinuxCncProgramExecutionReady: adapter.fullLinuxCncProgramExecutionReady,
semanticBoundary: adapter.semanticBoundary,
};
}

View File

@@ -0,0 +1,106 @@
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
createLinuxCncKinematicsSdk,
linuxCncKinematicsWasmFile,
supportedLinuxCncKinematicsModules,
} from "../../../../wasm-port/runtime/sdk/src/index.js";
const DEFAULT_MODULE_ID = "xyzac-trt";
const DEFAULT_JOINT_COUNT = 5;
const SOURCE_MODE = "source-derived-kinematics-wasm";
const SEMANTIC_BOUNDARY = "linuxcnc_kinematics_wasm_c_abi";
const __dirname = dirname(fileURLToPath(import.meta.url));
const defaultWasmRoot = resolve(__dirname, "../../../../wasm-port/build/wasm/kinematics");
export async function createLinuxCncKinematicsRuntime({
moduleId = DEFAULT_MODULE_ID,
moduleOptions = null,
switchkinsType = 0,
jointCount = DEFAULT_JOINT_COUNT,
wasmRoot = defaultWasmRoot,
} = {}) {
const wasmFile = linuxCncKinematicsWasmFile(moduleId);
if (!wasmFile) {
throw new Error(`unsupported LinuxCNC kinematics module: ${moduleId}`);
}
const resolvedModuleOptions = moduleOptions || {
wasmBinary: readFileSync(resolve(wasmRoot, wasmFile)),
print() {},
printErr() {},
};
const sdk = await createLinuxCncKinematicsSdk({ moduleId, moduleOptions: resolvedModuleOptions });
const switchRc = typeof sdk.switchKinematics === "function"
? sdk.switchKinematics(switchkinsType)
: 0;
return {
apiName: "web-rtcp-5axis-linuxcnc-kinematics-runtime",
moduleId,
wasmFile,
supportedModules: supportedLinuxCncKinematicsModules(),
loaded: true,
sourceMode: SOURCE_MODE,
semanticBoundary: SEMANTIC_BOUNDARY,
switchkinsType,
switchRc,
jointCount,
sdk,
readiness() {
return {
apiName: "web-rtcp-5axis-linuxcnc-kinematics-runtime-readiness",
moduleId,
wasmFile,
supportedModules: supportedLinuxCncKinematicsModules(),
loaded: true,
sourceMode: SOURCE_MODE,
semanticBoundary: SEMANTIC_BOUNDARY,
switchkinsType,
switchRc,
};
},
forward(joints, options = {}) {
return sdk.forward(joints, options);
},
inverse(pose, count = jointCount, options = {}) {
return sdk.inverse(pose, count, options);
},
frameForJoints(joints, options = {}) {
const jointValues = Array.from(joints, Number);
const forward = sdk.forward(jointValues, options.forwardOptions || {});
const inverse = sdk.inverse(
forward.pose,
options.jointCount || jointCount,
{ seedJoints: jointValues, ...(options.inverseOptions || {}) },
);
return {
moduleId,
switchkinsType,
forward,
inverse,
};
},
};
}
export function createLinuxCncKinematicsRuntimeDescriptor(runtime) {
if (!runtime?.loaded) return null;
return {
apiName: runtime.apiName,
moduleId: runtime.moduleId,
wasmFile: runtime.wasmFile,
supportedModules: runtime.supportedModules,
loaded: runtime.loaded,
sourceMode: runtime.sourceMode,
semanticBoundary: runtime.semanticBoundary,
switchkinsType: runtime.switchkinsType,
switchRc: runtime.switchRc,
};
}

View File

@@ -0,0 +1,196 @@
import { xyzacTrtProfile } from "../profiles/xyzac-trt.js";
const DEG_TO_RAD = Math.PI / 180;
export function buildRtcpFrame({
axisPose,
activeLine,
kinsType = "identity",
rtcpEnabled = false,
sourceMode = "fixture-ui-only",
profile = xyzacTrtProfile,
linuxCncKinematicsResult = null,
}) {
if (linuxCncKinematicsResult) {
return buildLinuxCncKinematicsFrame({
axisPose,
activeLine,
kinsType,
rtcpEnabled,
sourceMode,
profile,
linuxCncKinematicsResult,
});
}
const pose = normalizeAxisPose(axisPose);
const toolLength = 84.019;
const toolAxisVector = computeToolAxisVector(pose.a, pose.c);
const compensation = rtcpEnabled
? {
x: -toolAxisVector.x * toolLength,
y: -toolAxisVector.y * toolLength,
z: -toolAxisVector.z * toolLength,
}
: { x: 0, y: 0, z: 0 };
const tcpPose = {
x: pose.x + compensation.x,
y: pose.y + compensation.y,
z: pose.z + compensation.z,
a: pose.a,
c: pose.c,
};
return {
apiName: "web-rtcp-5axis-motion-frame",
profileId: profile.id,
sourceMode,
semanticBoundary:
sourceMode === "fixture-ui-only"
? "fixture_frame_ui_plumbing_not_linuxcnc_kinematics_proof"
: "linuxcnc_or_source_derived_runtime_required",
activeLine,
kinsType,
rtcpEnabled,
rtcpState: rtcpEnabled ? "on" : "off",
axisPose: pose,
jointPose: buildJointPose(pose),
tcpPose,
toolAxisVector,
compensation,
toolLength,
readiness: {
frameReady: true,
uiReady: true,
profileReady: true,
linuxCncKinematicsReady: false,
promotionAllowed: false,
},
};
}
function buildLinuxCncKinematicsFrame({
axisPose,
activeLine,
kinsType,
rtcpEnabled,
profile,
linuxCncKinematicsResult,
}) {
const forward = linuxCncKinematicsResult.forward || {};
const inverse = linuxCncKinematicsResult.inverse || {};
const wasmPose = normalizeLinuxCncPose(forward.pose);
const pose = normalizeAxisPose({
...axisPose,
...wasmPose,
});
const jointValues = Array.isArray(inverse.joints) ? inverse.joints : [];
const toolAxisVector = computeToolAxisVector(pose.a, pose.c);
return {
apiName: "web-rtcp-5axis-motion-frame",
profileId: profile.id,
sourceMode: "source-derived-kinematics-wasm",
semanticBoundary: "linuxcnc_kinematics_wasm_c_abi",
activeLine,
kinsType,
rtcpEnabled,
rtcpState: rtcpEnabled ? "on" : "off",
axisPose: pose,
jointPose: buildJointPoseFromLinuxCncJoints(jointValues, pose),
tcpPose: {
x: pose.x,
y: pose.y,
z: pose.z,
a: pose.a,
c: pose.c,
},
toolAxisVector,
compensation: { x: 0, y: 0, z: 0 },
toolLength: 84.019,
kinematicsModuleId: linuxCncKinematicsResult.moduleId,
kinematicsSwitchkinsType: linuxCncKinematicsResult.switchkinsType,
kinematicsForwardRc: forward.rc,
kinematicsInverseRc: inverse.rc,
kinematicsFlags: {
fflags: forward.fflags,
iflags: forward.iflags,
inverseFflags: inverse.fflags,
inverseIflags: inverse.iflags,
},
readiness: {
frameReady: true,
uiReady: true,
profileReady: true,
linuxCncKinematicsReady: forward.rc === 0 && inverse.rc === 0,
promotionAllowed: forward.rc === 0 && inverse.rc === 0,
fullLinuxCncProgramExecutionReady: false,
},
};
}
function normalizeAxisPose(axisPose) {
return {
x: Number(axisPose?.x ?? 0),
y: Number(axisPose?.y ?? 0),
z: Number(axisPose?.z ?? 0),
a: Number(axisPose?.a ?? 0),
b: Number(axisPose?.b ?? 0),
c: Number(axisPose?.c ?? 0),
};
}
function buildJointPose(pose) {
return [
{ joint: 0, axis: "X", value: pose.x },
{ joint: 1, axis: "Y", value: pose.y },
{ joint: 2, axis: "Z", value: pose.z },
{ joint: 3, axis: "A", value: pose.a },
{ joint: 4, axis: "C", value: pose.c },
];
}
function buildJointPoseFromLinuxCncJoints(joints, fallbackPose) {
const axes = ["X", "Y", "Z", "A", "C"];
const fallbackValues = [fallbackPose.x, fallbackPose.y, fallbackPose.z, fallbackPose.a, fallbackPose.c];
return axes.map((axis, joint) => ({
joint,
axis,
value: Number(joints[joint] ?? fallbackValues[joint] ?? 0),
}));
}
function normalizeLinuxCncPose(pose = {}) {
return {
x: Number(pose.x ?? pose.tran?.x ?? 0),
y: Number(pose.y ?? pose.tran?.y ?? 0),
z: Number(pose.z ?? pose.tran?.z ?? 0),
a: Number(pose.a ?? 0),
b: Number(pose.b ?? 0),
c: Number(pose.c ?? 0),
};
}
function computeToolAxisVector(aDegrees, cDegrees) {
const a = aDegrees * DEG_TO_RAD;
const c = cDegrees * DEG_TO_RAD;
const sinA = Math.sin(a);
const cosA = Math.cos(a);
const sinC = Math.sin(c);
const cosC = Math.cos(c);
return normalizeVector({
x: sinA * sinC,
y: -sinA * cosC,
z: cosA,
});
}
function normalizeVector(vector) {
const length = Math.hypot(vector.x, vector.y, vector.z) || 1;
return {
x: vector.x / length,
y: vector.y / length,
z: vector.z / length,
};
}