完善五轴 RTCP 仿真与验证资料

This commit is contained in:
2026-07-01 21:49:58 -04:00
parent ac4e855b2b
commit d0d58998ac
159 changed files with 6771594 additions and 339 deletions

View File

@@ -8,6 +8,8 @@ assert.equal(blocked.phase, "blocked");
assert.equal(blocked.promotionAllowed, false);
assert.equal(blocked.fullLinuxCncProgramExecutionReady, false);
assert.equal(blocked.missing.includes("linuxcnc kinematics WASM frame"), true);
assert.equal(blocked.missing.includes("tool DB Web/WASM simulation process"), true);
assert.equal(blocked.missing.includes("controlled user-M Web/WASM simulation process"), true);
assert.equal(blocked.blockers.some((blocker) => blocker.includes("LinuxCNC task runtime")), true);
const canonicalState = {
@@ -105,6 +107,45 @@ assert.equal(remap.semanticBoundary, "linuxcnc_machine_file_remap_ready_planner_
assert.equal(remap.satisfied.includes("fiveaxis-remap-machine-file-run"), true);
assert.equal(remap.evidence.machineFileFlags.length, 3);
const gmoccapyMachineFile = createFullLinuxCncExecutionBoundary({
...canonicalState,
programExecution: {
...canonicalState.programExecution,
plannerTiming: {
plannerRuntimeReady: true,
semanticBoundary: "linuxcnc_tp_queue_runtime_timing_from_canonical_motion",
},
summary: {
...canonicalState.programExecution.summary,
plannerRuntimeReady: true,
},
},
machineFileStaging: {
status: "staged",
fileCount: 18,
},
machineFileExecution: {
sourceMode: "linuxcnc-machine-file-remap-wasm",
summary: {
machineFileExecutionReady: true,
remapRuntimeReady: false,
},
resultText: [
"fiveaxis_parse_remap_1=5",
"fiveaxis_parse_remap_2=5",
"fiveaxis_remaps_ready=0",
"fiveaxis_file_reached_exit=1",
"fiveaxis_linuxcnc_remap_file_execute=0",
].join("\n"),
},
});
assert.equal(gmoccapyMachineFile.machineFileBackedRemapReady, true);
assert.equal(gmoccapyMachineFile.remapRuntimeReady, false);
assert.equal(gmoccapyMachineFile.semanticBoundary, "linuxcnc_machine_file_remap_ready_planner_task_hal_blocked");
assert.equal(gmoccapyMachineFile.satisfied.includes("fiveaxis-remap-machine-file-run"), true);
assert.equal(gmoccapyMachineFile.evidence.machineFileFlags.length, 1);
const taskHalPromoted = createFullLinuxCncExecutionBoundary({
...canonicalState,
programExecution: remap.programExecution || {
@@ -153,6 +194,18 @@ const taskHalPromoted = createFullLinuxCncExecutionBoundary({
halChangedPinCount: 4,
},
},
toolDbReadiness: {
toolDbProcessReady: true,
toolDbProcessScope: "web_simulation_only",
hostToolDbProcessReady: false,
toolCount: 10,
},
controlledUserMReadiness: {
externalUserMProcessReady: true,
externalUserMProcessScope: "web_simulation_only",
hostExternalUserMProcessReady: false,
arbitraryUserMExecution: false,
},
});
assert.equal(taskHalPromoted.semanticBoundary, "linuxcnc_task_motion_hal_wasm_simulation_runtime");
@@ -162,8 +215,15 @@ assert.equal(taskHalPromoted.fullLinuxCncProgramExecutionReady, true);
assert.equal(taskHalPromoted.promotionAllowed, true);
assert.equal(taskHalPromoted.hardwareDrive, false);
assert.equal(taskHalPromoted.hostRealtimeKernel, false);
assert.equal(taskHalPromoted.externalUserMProcessReady, false);
assert.equal(taskHalPromoted.toolDbProcessReady, false);
assert.equal(taskHalPromoted.externalUserMProcessReady, true);
assert.equal(taskHalPromoted.externalUserMProcessScope, "web_simulation_only");
assert.equal(taskHalPromoted.toolDbProcessReady, true);
assert.equal(taskHalPromoted.toolDbProcessScope, "web_simulation_only");
assert.equal(taskHalPromoted.hostExternalUserMProcessReady, false);
assert.equal(taskHalPromoted.hostToolDbProcessReady, false);
assert.equal(taskHalPromoted.arbitraryUserMExecution, false);
assert.equal(taskHalPromoted.satisfied.includes("tool-db-web-simulation"), true);
assert.equal(taskHalPromoted.satisfied.includes("controlled-user-m-web-simulation"), true);
assert.equal(taskHalPromoted.evidence.taskCycle, 2);
console.log("full_execution_boundary_smoke=ok");

View File

@@ -107,7 +107,12 @@ assert.deepEqual(taskModeControls.map((control) => [control.id, control.halpin])
["rbt-auto", "gmoccapy.v-button.button-4"],
]);
assert.deepEqual(fiveAxisProfiles.map(({ id }) => id), ["xyzac-trt", "xyzbc-trt", "gmoccapy-xyzab"]);
assert.deepEqual(fiveAxisProfiles.map(({ id }) => id), [
"xyzac-trt",
"xyzbc-trt",
"gmoccapy-xyzac-trt",
"gmoccapy-xyzab",
]);
const sourceSummary = createProfileSourceReferenceSummary("gmoccapy-xyzab");
assert.equal(sourceSummary.profileId, "gmoccapy-xyzab");

View File

@@ -125,6 +125,29 @@ for (const filename of DIRECT_PROGRAM_EXAMPLES) {
assert.equal(execution.summary.motionEventCount > 0, true, `${filename} motion`);
assert.equal(execution.plannerTiming.samples.length > 0, true, `${filename} planner samples`);
}
const boatMachineFilePlan = selectMachineFileProgram(
staged.plan,
staged.save,
`${GMOCAPY_TRT_EXAMPLE_SOURCE_PREFIX}boat-xyzac.ngc`,
);
const boatMachineFileExecution = runtime.runMachineFileProgram({
plan: boatMachineFilePlan,
files: staged.save.files,
executionMode: "fiveAxisRemap",
});
const boatRapidAfterToolChange = boatMachineFileExecution.motion.find((event) => (
event.type === "STRAIGHT_TRAVERSE" && event.line === 12
));
assert.equal(boatMachineFileExecution.sourceMode, "linuxcnc-machine-file-remap-wasm");
assert.equal(boatMachineFileExecution.summary.machineFileExecutionReady, true);
assert.equal(boatMachineFileExecution.summary.motionEventCount, 1833);
assert.equal(boatMachineFileExecution.plannerTiming.samples.length > 0, true);
assert.ok(boatRapidAfterToolChange, "boat-xyzac line 12 rapid move");
assert.equal(boatRapidAfterToolChange.axes.x, -49.65);
assert.equal(boatRapidAfterToolChange.axes.y, -23.015);
assert.equal(boatRapidAfterToolChange.axes.z, 5);
for (const { filename, subroutine, rotaryAxis } of NGC_GUI_SUBROUTINE_EXAMPLES) {
const source = staged.save.files.find((file) => file.sourceRel.endsWith(`/examples/${filename}`));
assert.ok(source, `${filename} staged`);

View File

@@ -1,4 +1,6 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc-interpreter-runtime.js";
@@ -103,4 +105,17 @@ assert.equal(switchkinsExecution.summary.plannerRuntimeReady, true);
assert.equal(switchkinsExecution.plannerTiming.totalSeconds > 0, true);
assert.equal(switchkinsExecution.summary.fullLinuxCncProgramExecutionReady, false);
const gmoccapyImpellerProgram = readFileSync(fileURLToPath(new URL(
"../../../linuxcnc/configs/sim/gmoccapy/non_trivial_kinematics/table-rotary-tilting/examples/impeller-7bl-xyzac.ngc",
import.meta.url,
)), "utf8");
const gmoccapyImpellerExecution = runtime.runProgram(gmoccapyImpellerProgram);
const firstG93FeedMotion = gmoccapyImpellerExecution.motion.find((event) => event.type === "STRAIGHT_FEED");
assert.equal(gmoccapyImpellerExecution.summary.ready, true);
assert.equal(gmoccapyImpellerExecution.motion[0].feedMode, "inverse-time");
assert.equal(firstG93FeedMotion.line, 9);
assert.equal(Math.abs(firstG93FeedMotion.feedRate - 635.745) < 0.001, true);
assert.notEqual(firstG93FeedMotion.feedRate, 318);
console.log("linuxcnc_interpreter_runtime_smoke=ok");

View File

@@ -1,52 +1,126 @@
import assert from "node:assert/strict";
import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
import { buildRtcpFrame } from "../../app/src/runtime/rtcp-frame.js";
const runtime = await createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" });
const readiness = runtime.readiness();
const DEG_TO_RAD = Math.PI / 180;
assert.equal(runtime.apiName, "web-rtcp-5axis-linuxcnc-kinematics-runtime");
assert.equal(runtime.moduleId, "xyzac-trt");
assert.equal(runtime.loaded, true);
assert.equal(runtime.sourceMode, "source-derived-kinematics-wasm");
assert.equal(runtime.semanticBoundary, "linuxcnc_kinematics_wasm_c_abi");
assert.equal(runtime.executionContext, "direct");
assert.equal(runtime.wasmFile, "linuxcnc_xyzac_trt_kinematics.wasm");
assert.equal(readiness.loaded, true);
assert.equal(readiness.executionContext, "direct");
assert.equal(readiness.supportedModules.includes("xyzac-trt"), true);
assert.equal(runtime.switchRc, 0);
const cases = [
{
moduleId: "xyzac-trt",
wasmFile: "linuxcnc_xyzac_trt_kinematics.wasm",
joints: [10, 20, 30, 25, 40],
fourthAxis: "A",
fourthKey: "a",
axisPose: { x: 10, y: 20, z: 30, a: 25, b: 0, c: 40 },
kinsType: "tcp-xyzac",
expectedToolAxis: xyzacToolAxis(25, 40),
},
{
moduleId: "xyzbc-trt",
wasmFile: "linuxcnc_xyzbc_trt_kinematics.wasm",
joints: [10, 20, 30, 35, 40],
fourthAxis: "B",
fourthKey: "b",
axisPose: { x: 10, y: 20, z: 30, a: 0, b: 35, c: 40 },
kinsType: "tcp-xyzbc",
expectedToolAxis: xyzbcToolAxis(35, 40),
},
];
const linuxCncKinematicsResult = runtime.frameForJoints([10, 20, 30, 25, 40]);
assert.equal(linuxCncKinematicsResult.moduleId, "xyzac-trt");
assert.equal(linuxCncKinematicsResult.forward.rc, 0);
assert.equal(linuxCncKinematicsResult.inverse.rc, 0);
assert.deepEqual(
linuxCncKinematicsResult.inverse.joints.map((value) => Math.round(value * 1e6) / 1e6),
[10, 20, 30, 25, 40],
);
for (const testCase of cases) {
const runtime = await createLinuxCncKinematicsRuntime({
moduleId: testCase.moduleId,
switchkinsType: 1,
});
const readiness = runtime.readiness();
const frame = buildRtcpFrame({
axisPose: { x: 10, y: 20, z: 30, a: 25, b: 0, c: 40 },
activeLine: 777,
kinsType: "tcp-xyzac",
rtcpEnabled: true,
linuxCncKinematicsResult,
});
assert.equal(runtime.apiName, "web-rtcp-5axis-linuxcnc-kinematics-runtime");
assert.equal(runtime.moduleId, testCase.moduleId);
assert.equal(runtime.loaded, true);
assert.equal(runtime.sourceMode, "source-derived-kinematics-wasm");
assert.equal(runtime.semanticBoundary, "linuxcnc_kinematics_wasm_c_abi");
assert.equal(runtime.executionContext, "direct");
assert.equal(runtime.wasmFile, testCase.wasmFile);
assert.equal(runtime.switchkinsType, 1);
assert.equal(readiness.loaded, true);
assert.equal(readiness.executionContext, "direct");
assert.equal(readiness.supportedModules.includes("xyzac-trt"), true);
assert.equal(readiness.supportedModules.includes("xyzbc-trt"), true);
assert.equal(runtime.switchRc, 0);
assert.equal(frame.sourceMode, "source-derived-kinematics-wasm");
assert.equal(frame.semanticBoundary, "linuxcnc_kinematics_wasm_c_abi");
assert.equal(frame.readiness.linuxCncKinematicsReady, true);
assert.equal(frame.readiness.promotionAllowed, true);
assert.equal(frame.readiness.fullLinuxCncProgramExecutionReady, false);
assert.equal(frame.kinematicsModuleId, "xyzac-trt");
assert.equal(frame.kinematicsForwardRc, 0);
assert.equal(frame.kinematicsInverseRc, 0);
assert.equal(frame.jointPose[3].value, 25);
assert.equal(frame.jointPose[4].value, 40);
assert.equal(frame.tcpPose.x, linuxCncKinematicsResult.forward.pose.x);
assert.equal(frame.tcpPose.y, linuxCncKinematicsResult.forward.pose.y);
assert.equal(frame.tcpPose.z, linuxCncKinematicsResult.forward.pose.z);
const linuxCncKinematicsResult = runtime.frameForJoints(testCase.joints);
assert.equal(linuxCncKinematicsResult.moduleId, testCase.moduleId);
assert.equal(linuxCncKinematicsResult.switchkinsType, 1);
assert.equal(linuxCncKinematicsResult.forward.rc, 0);
assert.equal(linuxCncKinematicsResult.inverse.rc, 0);
assert.deepEqual(roundJoints(linuxCncKinematicsResult.inverse.joints), testCase.joints);
const frame = buildRtcpFrame({
axisPose: testCase.axisPose,
activeLine: 777,
kinsType: testCase.kinsType,
rtcpEnabled: true,
profile: getFiveAxisProfile(testCase.moduleId),
linuxCncKinematicsResult,
});
assert.equal(frame.sourceMode, "source-derived-kinematics-wasm");
assert.equal(frame.semanticBoundary, "linuxcnc_kinematics_wasm_c_abi");
assert.equal(frame.readiness.linuxCncKinematicsReady, true);
assert.equal(frame.readiness.promotionAllowed, true);
assert.equal(frame.readiness.fullLinuxCncProgramExecutionReady, false);
assert.equal(frame.profileId, testCase.moduleId);
assert.equal(frame.kinematicsModuleId, testCase.moduleId);
assert.equal(frame.kinematicsSwitchkinsType, 1);
assert.equal(frame.kinematicsForwardRc, 0);
assert.equal(frame.kinematicsInverseRc, 0);
assert.equal(frame.jointPose[3].axis, testCase.fourthAxis);
assert.equal(frame.jointPose[3].value, testCase.joints[3]);
assert.equal(frame.jointPose[4].axis, "C");
assert.equal(frame.jointPose[4].value, testCase.joints[4]);
assert.equal(frame.tcpPose.x, linuxCncKinematicsResult.forward.pose.x);
assert.equal(frame.tcpPose.y, linuxCncKinematicsResult.forward.pose.y);
assert.equal(frame.tcpPose.z, linuxCncKinematicsResult.forward.pose.z);
assert.equal(frame.tcpPose.a, linuxCncKinematicsResult.forward.pose.a);
assert.equal(frame.tcpPose.b, linuxCncKinematicsResult.forward.pose.b);
assert.equal(frame.tcpPose.c, linuxCncKinematicsResult.forward.pose.c);
assert.equal(frame.tcpPose[testCase.fourthKey], testCase.axisPose[testCase.fourthKey]);
assertVectorNear(frame.toolAxisVector, testCase.expectedToolAxis);
console.log(`${testCase.moduleId.replace(/-/g, "_")}_kinematics_runtime=ok`);
}
console.log("linuxcnc_kinematics_runtime_smoke=ok");
console.log("xyzbc_b_axis_tcp_pose_preserved=1");
function roundJoints(joints) {
return joints.map((value) => Math.round(value * 1e6) / 1e6);
}
function xyzacToolAxis(aDegrees, cDegrees) {
const tilt = aDegrees * DEG_TO_RAD;
const c = cDegrees * DEG_TO_RAD;
return {
x: Math.sin(tilt) * Math.sin(c),
y: -Math.sin(tilt) * Math.cos(c),
z: Math.cos(tilt),
};
}
function xyzbcToolAxis(bDegrees, cDegrees) {
const tilt = bDegrees * DEG_TO_RAD;
const c = cDegrees * DEG_TO_RAD;
return {
x: Math.sin(tilt) * Math.cos(c),
y: Math.sin(tilt) * Math.sin(c),
z: Math.cos(tilt),
};
}
function assertVectorNear(actual, expected, tolerance = 1e-12) {
assert.ok(Math.abs(actual.x - expected.x) < tolerance);
assert.ok(Math.abs(actual.y - expected.y) < tolerance);
assert.ok(Math.abs(actual.z - expected.z) < tolerance);
}

View File

@@ -75,6 +75,14 @@ assert.equal(state.machineFileStaging.save.summary.gcodeFileCount, 16);
assert.equal(state.machineFileStaging.save.summary.kinds.remap >= 3, true);
assert.equal(state.machineFileStaging.gcodeSources.length >= 4, true);
assert.equal(state.machineFileStaging.selectedGcodeSourceRel, null);
assert.equal(state.toolDbReadiness.toolDbProcessReady, true);
assert.equal(state.toolDbReadiness.toolDbProcessScope, "web_simulation_only");
assert.equal(state.toolDbReadiness.hostToolDbProcessReady, false);
assert.equal(state.toolDbReadiness.toolCount, 10);
assert.equal(state.toolDbSimulation.toolTable.entries[1].toolNumber, 2);
assert.equal(state.toolDbSimulation.toolTable.entries[1].offset.z, 15);
assert.equal(state.controlledUserMReadiness.externalUserMProcessReady, true);
assert.equal(state.controlledUserMReadiness.externalUserMProcessScope, "web_simulation_only");
store.dispatch({ type: "RUN_MACHINE_FILE_PROGRAM" });
assert.equal(
@@ -102,6 +110,8 @@ assert.equal(sourceState.activeProgram.endsWith("impeller-7bl-xyzac.ngc"), true)
assert.equal(sourceState.programLines[0].includes("Impeller 5-axis"), true);
assert.equal(sourceState.machineFileStaging.selectedGcodeSourceRel.endsWith("impeller-7bl-xyzac.ngc"), true);
assert.equal(sourceState.machineFileStaging.plan.wasmProgramPath.endsWith("/demos/impeller-7bl-xyzac.ngc"), true);
assert.equal(sourceState.toolDbReadiness.toolDbProcessReady, true);
assert.equal(sourceState.controlledUserMReadiness.externalUserMProcessReady, true);
store.dispatch({ type: "RUN_MACHINE_FILE_PROGRAM" });
const runState = await waitForMachineFileExecution(store);
@@ -109,8 +119,9 @@ assert.equal(runState.machineFileExecution.sourceMode, "linuxcnc-machine-file-re
assert.equal(runState.machineFileExecution.semanticBoundary, "linuxcnc_fiveaxis_remap_wasm_machine_file_execution");
assert.equal(runState.machineFileExecution.summary.machineFileExecutionReady, true);
assert.equal(runState.machineFileExecution.summary.remapRuntimeReady, true);
assert.equal(runState.machineFileExecution.summary.plannerRuntimeReady, false);
assert.equal(runState.machineFileExecution.summary.plannerRuntimeReady, true);
assert.equal(runState.machineFileExecution.summary.fullLinuxCncProgramExecutionReady, false);
assert.equal(runState.machineFileExecution.plannerTiming.samples.length > 0, true);
assert.equal(runState.machineFileExecution.resultText.includes("fiveaxis_ini_open=1"), true);
assert.equal(runState.machineFileExecution.resultText.includes("fiveaxis_remaps_ready=1"), true);
assert.equal(runState.machineFileExecution.resultText.includes("fiveaxis_file_reached_exit=1"), true);
@@ -120,7 +131,7 @@ assert.equal(runState.fullExecutionBoundary.apiName, "web-rtcp-5axis-full-linuxc
assert.equal(runState.fullExecutionBoundary.machineFileBackedRemapReady, true);
assert.equal(runState.fullExecutionBoundary.remapRuntimeReady, true);
assert.equal(runState.fullExecutionBoundary.halSwitchkinsEvidenceReady, true);
assert.equal(runState.fullExecutionBoundary.plannerRuntimeReady, false);
assert.equal(runState.fullExecutionBoundary.plannerRuntimeReady, true);
assert.equal(runState.fullExecutionBoundary.nativeTaskReady, false);
assert.equal(runState.fullExecutionBoundary.nativeHalSyncReady, false);
assert.equal(runState.fullExecutionBoundary.fullLinuxCncProgramExecutionReady, false);
@@ -131,7 +142,7 @@ assert.equal(
);
assert.equal(runState.fullExecutionBoundary.satisfied.includes("fiveaxis-remap-machine-file-run"), true);
assert.equal(runState.fullExecutionBoundary.satisfied.includes("switchkins-hal-bridge-evidence"), true);
assert.equal(runState.fullExecutionBoundary.blockers.some((blocker) => blocker.includes("trajectory planner")), true);
assert.equal(runState.fullExecutionBoundary.satisfied.includes("linuxcnc-tp-queue-runtime-timing"), true);
globalThis.__WEB_RTCP_FORCE_OPFS_UNAVAILABLE__ = true;
const fallbackStore = createSimulationStore();
@@ -142,6 +153,7 @@ assert.equal(fallbackStage.save.storageCapability.opfsUnavailable, true);
assert.equal(fallbackStage.save.storageCapability.reason, "forced_unavailable");
assert.equal(fallbackState.machineFileStaging.status, "staged");
assert.equal(fallbackState.machineFileStaging.storageMode, "memory-fallback");
assert.equal(fallbackState.toolDbReadiness.toolDbProcessReady, true);
assert.equal(fallbackState.operatorMessage.includes("staged"), true);
delete globalThis.__WEB_RTCP_FORCE_OPFS_UNAVAILABLE__;

View File

@@ -64,6 +64,14 @@ assert.equal(audit.nativeHostAndHardware.hardwareDrive, false);
assert.equal(audit.nativeHostAndHardware.hostRealtimeKernel, false);
assert.equal(audit.nativeHostAndHardware.externalUserMProcessReady, false);
assert.equal(audit.nativeHostAndHardware.toolDbProcessReady, false);
assert.equal(audit.nativeHostAndHardware.hostExternalUserMProcessReady, false);
assert.equal(audit.nativeHostAndHardware.hostToolDbProcessReady, false);
assert.equal(audit.nativeHostAndHardware.arbitraryUserMExecution, false);
assert.equal(audit.toolUserWebSimulation.externalUserMProcessReady, true);
assert.equal(audit.toolUserWebSimulation.externalUserMProcessScope, "web_simulation_only");
assert.equal(audit.toolUserWebSimulation.toolDbProcessReady, true);
assert.equal(audit.toolUserWebSimulation.toolDbProcessScope, "web_simulation_only");
assert.equal(audit.toolUserWebSimulation.ready, true);
assert.equal(audit.sourceManifest.ready, true);
assert.equal(audit.sourceManifest.taskSourceCount > 0, true);
assert.equal(audit.sourceManifest.halSourceCount > 0, true);
@@ -71,8 +79,10 @@ assert.equal(audit.sourceManifest.motionSourceCount > 0, true);
assert.equal(audit.gates.task_hal_web_simulation_boundary_consistent, 1);
assert.equal(audit.gates.hardware_drive, 0);
assert.equal(audit.gates.host_realtime_kernel, 0);
assert.equal(audit.gates.external_user_m_process_ready, 0);
assert.equal(audit.gates.tool_db_process_ready, 0);
assert.equal(audit.gates.external_user_m_process_ready, 1);
assert.equal(audit.gates.tool_db_process_ready, 1);
assert.equal(audit.gates.host_external_user_m_process_ready, 0);
assert.equal(audit.gates.host_tool_db_process_ready, 0);
assert.equal(audit.gates.promotion_scope, "web_simulation_only");
mkdirSync(artifactDir, { recursive: true });
@@ -88,8 +98,10 @@ console.log("task_hal_web_simulation_boundary_consistent=1");
console.log(`native_task_hal_host_probe_status=${audit.nativeHostAndHardware.nativeProbeStatus}`);
console.log("hardware_drive=0");
console.log("host_realtime_kernel=0");
console.log("external_user_m_process_ready=0");
console.log("tool_db_process_ready=0");
console.log("external_user_m_process_ready=1");
console.log("tool_db_process_ready=1");
console.log("host_external_user_m_process_ready=0");
console.log("host_tool_db_process_ready=0");
console.log("promotion_scope=web_simulation_only");
function createPromotedSimulationState() {
@@ -156,6 +168,18 @@ function createPromotedSimulationState() {
halChangedPinCount: 4,
},
},
toolDbReadiness: {
toolDbProcessReady: true,
toolDbProcessScope: "web_simulation_only",
hostToolDbProcessReady: false,
toolCount: 10,
},
controlledUserMReadiness: {
externalUserMProcessReady: true,
externalUserMProcessScope: "web_simulation_only",
hostExternalUserMProcessReady: false,
arbitraryUserMExecution: false,
},
};
}

View File

@@ -30,7 +30,12 @@ assert.equal(xyzacTrtProfile.halPins.includes("motion.switchkins-type"), true);
assert.equal(xyzacTrtProfile.halPins.includes("xyzac-trt-kins.tool-offset"), true);
assert.equal(xyzacTrtProfile.promotionAllowed, false);
assert.equal(xyzacTrtProfile.linuxCncKinematicsReady, false);
assert.deepEqual(fiveAxisProfiles.map(({ id }) => id), ["xyzac-trt", "xyzbc-trt", "gmoccapy-xyzab"]);
assert.deepEqual(fiveAxisProfiles.map(({ id }) => id), [
"xyzac-trt",
"xyzbc-trt",
"gmoccapy-xyzac-trt",
"gmoccapy-xyzab",
]);
const xyzbcTrtProfile = getFiveAxisProfile("xyzbc-trt");
assert.equal(xyzbcTrtProfile.id, "xyzbc-trt");
@@ -59,6 +64,18 @@ assert.equal(xyzbcSourceSummary.referenceCount >= 7, true);
assert.ok(xyzbcSourceSummary.references.some((reference) => reference.path.endsWith("xyzbc-trt.ini")));
assert.ok(xyzbcSourceSummary.references.some((reference) => reference.path.endsWith("xyzbc-trt-kins.c")));
const gmoccapyXyzacTrtProfile = getFiveAxisProfile("gmoccapy-xyzac-trt");
assert.equal(gmoccapyXyzacTrtProfile.id, "gmoccapy-xyzac-trt");
assert.equal(gmoccapyXyzacTrtProfile.display.display, "gmoccapy");
assert.equal(gmoccapyXyzacTrtProfile.kinematics, "xyzac-trt-kins");
assert.equal(gmoccapyXyzacTrtProfile.kinematicsModuleId, "xyzac-trt");
assert.equal(gmoccapyXyzacTrtProfile.rtcpProof, true);
assert.equal(gmoccapyXyzacTrtProfile.kinematicsParameters.fixedTrtDefault, true);
const gmoccapyTrtSourceSummary = createProfileSourceReferenceSummary("gmoccapy-xyzac-trt");
assert.equal(gmoccapyTrtSourceSummary.referenceCount >= 10, true);
assert.ok(gmoccapyTrtSourceSummary.references.some((reference) => reference.path.endsWith("non_trivial_kinematics/table-rotary-tilting/xyzac-trt.ini")));
assert.ok(gmoccapyTrtSourceSummary.references.some((reference) => reference.path.endsWith("examples/impeller-7bl-xyzac.ngc")));
const gmoccapyXyzabProfile = getFiveAxisProfile("gmoccapy-xyzab");
assert.equal(gmoccapyXyzabProfile.id, "gmoccapy-xyzab");
assert.deepEqual(gmoccapyXyzabProfile.coordinates, ["X", "Y", "Z", "A", "B"]);

View File

@@ -268,7 +268,7 @@ const store = createSimulationStore();
let state;
assert.deepEqual(
store.getState().availableProfiles.map((profile) => profile.id),
["xyzac-trt", "xyzbc-trt", "gmoccapy-xyzab"],
["xyzac-trt", "xyzbc-trt", "gmoccapy-xyzac-trt", "gmoccapy-xyzab"],
);
store.dispatch({ type: "SET_PROFILE", profileId: "xyzbc-trt" });
assert.equal(store.getState().machineProfile, "xyzbc-trt");
@@ -282,6 +282,9 @@ state = store.getState();
assert.equal(state.kinsType, "tcp-xyzbc");
assert.equal(state.rtcpFrame.kinematicsModuleId, "xyzbc-trt");
assert.equal(state.rtcpFrame.jointPose[3].axis, "B");
assert.equal(Number.isFinite(state.tcpPose.b), true);
assert.equal(state.rtcpFrame.tcpPose.b, state.tcpPose.b);
assert.equal(state.rtcpFrame.tcpPose.b, state.rtcpFrame.axisPose.b);
store.dispatch({ type: "SET_PROFILE", profileId: "xyzac-trt" });
store.dispatch({ type: "RESET" });

View File

@@ -0,0 +1,142 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { createMemorySessionStorage } from "../../app/src/runtime/five-axis-session.js";
import {
applyToolCommandSequence,
createToolDbReadiness,
createToolDbSimulation,
editToolEntry,
parseLinuxCncToolTable,
queryToolEntry,
saveToolDbSimulation,
} from "../../app/src/runtime/tool-db-simulation.js";
import {
createControlledUserMReadiness,
createControlledUserMSimulation,
runControlledUserM,
runControlledUserMProgramScan,
} from "../../app/src/runtime/controlled-user-m-simulation.js";
import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
const profile = getFiveAxisProfile("xyzac-trt");
const toolTableText = await readFile(
new URL("../../../linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.tbl", import.meta.url),
"utf8",
);
const parsed = parseLinuxCncToolTable(toolTableText, {
sourceRel: profile.toolTablePath,
});
assert.equal(parsed.apiName, "web-rtcp-5axis-tool-table");
assert.equal(parsed.toolCount, 10);
assert.equal(parsed.entries[0].toolNumber, 1);
assert.equal(parsed.entries[0].pocket, 1);
assert.equal(parsed.entries[1].offset.z, 15);
assert.equal(parsed.entries[1].diameter, 8);
assert.equal(parsed.semanticBoundary, "linuxcnc_tool_table_text_parsed_for_web_simulation");
let toolDb = createToolDbSimulation({
toolTable: parsed,
profile,
storageMode: "memory",
});
assert.equal(toolDb.processReady, true);
assert.equal(toolDb.processScope, "web_simulation_only");
assert.equal(toolDb.hostProcessReady, false);
assert.equal(createToolDbReadiness(toolDb).toolDbProcessReady, true);
assert.equal(createToolDbReadiness(toolDb).hostProcessExecution, false);
toolDb = applyToolCommandSequence(toolDb, [
{ code: "T", toolNumber: 2 },
{ code: "M6" },
{ code: "G43", h: 2 },
]);
assert.equal(toolDb.toolInSpindle, 2);
assert.equal(toolDb.toolFromPocket, 2);
assert.equal(toolDb.activeToolOffset.toolNumber, 2);
assert.equal(toolDb.activeToolOffset.offset.z, 15);
assert.equal(toolDb.iocontrol.toolNumber, 2);
assert.equal(toolDb.events.some((event) => event.code === "M6"), true);
toolDb = applyToolCommandSequence(toolDb, [{ code: "M61", q: 4 }]);
assert.equal(toolDb.toolInSpindle, 4);
assert.equal(toolDb.iocontrol.toolNumber, 4);
toolDb = editToolEntry(toolDb, {
toolNumber: 4,
pocket: 44,
diameter: 12.5,
offset: { z: 42.25 },
comment: "edited by web simulation",
});
const editedTool = queryToolEntry(toolDb, { toolNumber: 4 });
assert.equal(editedTool.pocket, 44);
assert.equal(editedTool.diameter, 12.5);
assert.equal(editedTool.offset.z, 42.25);
const storage = createMemorySessionStorage();
const save = await saveToolDbSimulation(toolDb, {
storage,
storageMode: "memory",
});
assert.equal(save.semanticBoundary, "tool_table_saved_in_web_storage_boundary");
assert.equal(save.text.includes("T4 P44 D+12.500000 Z+42.250000 ;edited by web simulation"), true);
assert.equal(storage.files.get(save.path), save.text);
let userM = createControlledUserMSimulation();
assert.equal(createControlledUserMReadiness(userM).externalUserMProcessReady, true);
assert.equal(createControlledUserMReadiness(userM).hostProcessExecution, false);
let result = runControlledUserM(userM, "M428", { profile });
userM = result.simulation;
assert.equal(result.event.allowed, true);
assert.equal(result.statePatch.kinsType, "tcp-xyzac");
assert.equal(result.statePatch.rtcpState, "on");
assert.equal(result.halPatch["motion.switchkins-type"], 1);
result = runControlledUserM(userM, "M429", { profile });
userM = result.simulation;
assert.equal(result.event.allowed, true);
assert.equal(result.statePatch.kinsType, "identity");
assert.equal(result.statePatch.rtcpState, "off");
assert.equal(result.halPatch["motion.switchkins-type"], 0);
result = runControlledUserM(userM, "M430", { profile });
userM = result.simulation;
assert.equal(result.event.allowed, true);
assert.equal(result.statePatch.kinsType, "userk");
assert.equal(result.halPatch["motion.switchkins-type"], 2);
result = runControlledUserM(userM, "M128", { profile });
userM = result.simulation;
assert.equal(result.event.allowed, true);
assert.equal(result.event.hostProcessExecution, false);
result = runControlledUserM(userM, "M199", { profile });
userM = result.simulation;
assert.equal(result.event.allowed, false);
assert.equal(result.event.semanticBoundary, "arbitrary_external_user_m_blocked");
assert.equal(result.event.hostProcessExecution, false);
assert.equal(userM.blockedEvents.length, 1);
const scan = runControlledUserMProgramScan(
userM,
[
"M428",
"G0 X1",
"M129",
"M199",
"M429",
].join("\n"),
{ profile, sourceRel: "operator-test.ngc" },
);
assert.equal(scan.events.length, 4);
assert.equal(scan.events.filter((event) => event.allowed).length, 3);
assert.equal(scan.events.filter((event) => !event.allowed).length, 1);
console.log("tool_db_user_m_simulation_smoke=ok");
console.log("tool_db_process_ready_web_simulation_only=1");
console.log("external_user_m_process_ready_web_simulation_only=1");
console.log("arbitrary_user_m_blocked=1");
console.log("host_process_execution=0");

View File

@@ -0,0 +1,150 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { createMemorySessionStorage } from "../../app/src/runtime/five-axis-session.js";
import {
applyToolCommandSequence,
createMemoryToolDbStorage,
createToolDbReadiness,
createToolDbSimulation,
editToolEntry,
extractToolCommandSequenceFromProgram,
listToolEntries,
parseLinuxCncToolTable,
queryToolEntry,
saveToolDbSimulation,
} from "../../app/src/runtime/tool-db-simulation.js";
import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
import { createSimulationStore } from "../../app/src/state/store.js";
const profile = getFiveAxisProfile("xyzac-trt");
const toolTableText = await readFile(
new URL("../../../linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.tbl", import.meta.url),
"utf8",
);
const parsed = parseLinuxCncToolTable(toolTableText, {
sourceRel: profile.toolTablePath,
});
assert.equal(parsed.randomToolChanger, false);
assert.equal(parsed.toolCount, 10);
assert.equal(parsed.pockets[0].toolNumber, 0);
assert.equal(parsed.entries[0].idx, 1);
assert.equal(parsed.entries[0].toolNumber, 1);
assert.equal(parsed.entries[0].pocket, 1);
assert.equal(parsed.entries[1].idx, 2);
assert.equal(parsed.entries[1].toolNumber, 2);
assert.equal(parsed.entries[1].pocket, 2);
assert.equal(parsed.entries[1].offset.z, 15);
assert.equal(parsed.entries[1].diameter, 8);
let toolDb = createToolDbSimulation({
toolTable: parsed,
profile,
});
assert.equal(createToolDbReadiness(toolDb).toolDbProcessReady, true);
assert.equal(createToolDbReadiness(toolDb).dbProgramReady, false);
assert.equal(toolDb.hostProcessExecution, false);
assert.equal(toolDb.toolTable.pockets[0].toolNumber, 0);
assert.equal(listToolEntries(toolDb).length, 10);
assert.equal(queryToolEntry(toolDb, { idx: 2 }).toolNumber, 2);
assert.equal(queryToolEntry(toolDb, { toolNumber: 2 }).idx, 2);
assert.equal(queryToolEntry(toolDb, { pocket: 2 }).diameter, 8);
toolDb = applyToolCommandSequence(toolDb, [
{ code: "T", toolNumber: 2 },
{ code: "M6" },
{ code: "G43", h: 2 },
]);
assert.equal(toolDb.toolInSpindle, 2);
assert.equal(toolDb.toolFromPocket, 2);
assert.equal(toolDb.currentPocket, 2);
assert.equal(toolDb.toolTable.pockets[0].toolNumber, 2);
assert.equal(toolDb.toolTable.pockets[0].offset.z, 15);
assert.equal(queryToolEntry(toolDb, { pocket: 2 }).idx, 2);
assert.equal(toolDb.emcioStatus.tool.pocketPrepped, -1);
assert.equal(toolDb.iocontrol.toolNumber, 2);
assert.equal(toolDb.iocontrol.toolFromPocket, 2);
assert.equal(toolDb.activeToolOffset.toolNumber, 2);
assert.equal(toolDb.activeToolOffset.offset.z, 15);
assert.equal(toolDb.interpreterParameters[5400], 2);
assert.equal(toolDb.interpreterParameters[5403], 15);
assert.equal(toolDb.interpreterParameters[5410], 8);
assert.equal(toolDb.events.some((event) => event.code === "READ_TOOL_INPUTS_PREPARED"), true);
assert.equal(toolDb.events.some((event) => event.code === "READ_TOOL_INPUTS_CHANGED"), true);
const compactToolCommands = extractToolCommandSequenceFromProgram("T2M6G43H2");
assert.deepEqual(compactToolCommands.map((command) => command.code), ["T", "M6", "G43"]);
assert.equal(compactToolCommands[2].toolNumber, 2);
toolDb = editToolEntry(toolDb, {
toolNumber: 4,
pocket: 44,
diameter: 12.5,
length: 42.25,
comment: "edited by web simulation",
});
const edited = queryToolEntry(toolDb, { toolNumber: 4 });
assert.equal(edited.idx, 4);
assert.equal(edited.pocket, 44);
assert.equal(edited.offset.z, 42.25);
assert.equal(edited.diameter, 12.5);
toolDb = editToolEntry(toolDb, {
idx: 6,
toolNumber: 66,
pocket: 66,
diameter: 6.6,
toolLength: 16.6,
});
const renumbered = queryToolEntry(toolDb, { toolNumber: 66 });
assert.equal(renumbered.idx, 6);
assert.equal(renumbered.pocket, 66);
assert.equal(renumbered.offset.z, 16.6);
assert.equal(renumbered.diameter, 6.6);
const storage = createMemoryToolDbStorage();
const save = await saveToolDbSimulation(toolDb, {
storage,
storageMode: "memory",
});
assert.equal(save.storageMode, "memory");
assert.equal(storage.files.get(save.path), save.text);
assert.equal(save.text.includes("T4 P44 D+12.500000 Z+42.250000 ;edited by web simulation"), true);
assert.equal(save.text.includes("T66 P66 D+6.600000 Z+16.600000"), true);
const fallbackSave = await saveToolDbSimulation(toolDb);
assert.equal(fallbackSave.storageMode, "memory-fallback");
assert.equal(fallbackSave.storageCapability.opfsUnavailable, true);
const store = createSimulationStore();
const machineStorage = createMemorySessionStorage();
await store.stageMachineFiles({ storage: machineStorage });
assert.equal(store.getState().toolDbReadiness.toolDbProcessReady, true);
assert.equal(store.queryToolDb({ toolNumber: 2 }).offset.z, 15);
assert.equal(store.queryToolDb({ all: true }).length, 10);
store.editToolDb({
toolNumber: 3,
pocket: 33,
diameter: 9.75,
toolLength: 21.5,
});
assert.equal(store.queryToolDb({ toolNumber: 3 }).pocket, 33);
assert.equal(store.queryToolDb({ toolNumber: 3 }).offset.z, 21.5);
const storeSaveStorage = createMemoryToolDbStorage();
const storeSave = await store.saveToolDb({
storage: storeSaveStorage,
storageMode: "memory",
});
assert.equal(storeSave.text.includes("T3 P33 D+9.750000 Z+21.500000"), true);
assert.equal(storeSaveStorage.files.get(storeSave.path), storeSave.text);
console.log("tool_db_web_simulation_smoke=ok");
console.log("staged_tool_tbl_loaded=1");
console.log("tool_db_query_edit_save=1");
console.log("tool_db_t_m6_g43_state=1");
console.log("tool_db_memory_fallback_save=1");
console.log("host_tool_db_process=0");