Add RUN precondition gate for 5-axis simulation
This commit is contained in:
@@ -4,6 +4,9 @@ import { fileURLToPath } from "node:url";
|
||||
import { dirname, resolve } from "node:path";
|
||||
|
||||
import { createLinuxCncTaskHalSdk } from "../../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js";
|
||||
import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
|
||||
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
|
||||
import { parseLinuxCncIni } from "../../app/src/runtime/linuxcnc-ini-runtime.js";
|
||||
import { wrapTaskHalSdk } from "../../app/src/runtime/linuxcnc-task-hal-runtime.js";
|
||||
import { createSimulationStore } from "../../app/src/state/store.js";
|
||||
|
||||
@@ -20,21 +23,37 @@ const sdk = await createLinuxCncTaskHalSdk({
|
||||
},
|
||||
});
|
||||
const runtime = wrapTaskHalSdk(sdk);
|
||||
const store = createSimulationStore({
|
||||
programLines: [
|
||||
"G0 X1 Y2 Z-3 A10 C20",
|
||||
"G1 X2 Y3 Z-4 A11 C21",
|
||||
"M428",
|
||||
"G1 X3 Y4 Z-5 A12 C22",
|
||||
],
|
||||
activeProgram: "task-hal-store-smoke.ngc",
|
||||
const profile = getFiveAxisProfile("xyzac-trt");
|
||||
const iniText = readFileSync(
|
||||
resolve(rootDir, "wasm-port/vendor/linuxcnc", profile.iniPath),
|
||||
"utf8",
|
||||
);
|
||||
const iniConfig = parseLinuxCncIni(iniText, {
|
||||
path: profile.iniPath,
|
||||
profileId: profile.id,
|
||||
});
|
||||
const store = createSimulationStore();
|
||||
|
||||
store.dispatch({ type: "ATTACH_INI_CONFIG", profileId: profile.id, iniConfig });
|
||||
store.dispatch({
|
||||
type: "ATTACH_KINEMATICS_RUNTIME",
|
||||
runtime: await createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" }),
|
||||
});
|
||||
store.dispatch({ type: "ATTACH_TASK_HAL_RUNTIME", runtime });
|
||||
assert.equal(store.getState().taskHalRuntimeReadiness.taskRuntimeReady, true);
|
||||
assert.equal(store.getState().taskHalRuntimeReadiness.motionRuntimeReady, true);
|
||||
assert.equal(store.getState().taskHalRuntimeReadiness.halRuntimeReady, true);
|
||||
|
||||
await store.stageMachineFiles();
|
||||
store.dispatch({
|
||||
type: "LOAD_LINUXCNC_GCODE_SOURCE",
|
||||
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins_test_1.ngc",
|
||||
});
|
||||
await waitForState(store, (state) => (
|
||||
state.machineFileStaging.selectedGcodeSourceRel?.endsWith("xyzac_switchkins_test_1.ngc") &&
|
||||
state.taskHalSession?.programPath?.endsWith("xyzac_switchkins_test_1.ngc")
|
||||
));
|
||||
|
||||
store.dispatch({ type: "TOGGLE_POWER" });
|
||||
await waitForTaskHal(store);
|
||||
store.dispatch({ type: "HOME" });
|
||||
@@ -47,11 +66,14 @@ let state = store.getState();
|
||||
assert.equal(state.taskHalStatus.summary.taskRuntimeReady, true);
|
||||
assert.equal(state.taskHalStatus.summary.halSyncReady, true);
|
||||
assert.equal(state.taskHalStatus.ui.activeLine >= 1, true);
|
||||
assert.equal(state.axisPose.x >= 1, true);
|
||||
assert.equal(state.programExecutionSourceMode, "linuxcnc-task-motion-hal-wasm");
|
||||
assert.equal(state.fullExecutionBoundary.nativeTaskReady, true);
|
||||
assert.equal(state.fullExecutionBoundary.nativeHalSyncReady, true);
|
||||
|
||||
await store.initializeTaskHalSession({ openProgram: true });
|
||||
store.dispatch({ type: "TOGGLE_POWER" });
|
||||
await waitForTaskHal(store);
|
||||
store.dispatch({ type: "HOME" });
|
||||
store.dispatch({ type: "SET_MODE", mode: "mdi" });
|
||||
await waitForTaskHal(store);
|
||||
store.dispatch({ type: "RUN_MDI", command: "M428" });
|
||||
@@ -111,6 +133,15 @@ async function waitForTaskHal(store) {
|
||||
throw new Error("task/HAL store command did not settle");
|
||||
}
|
||||
|
||||
async function waitForState(store, predicate) {
|
||||
for (let attempt = 0; attempt < 60; attempt += 1) {
|
||||
const state = store.getState();
|
||||
if (predicate(state)) return state;
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
throw new Error("store state condition did not settle");
|
||||
}
|
||||
|
||||
function assertNear(actual, expected, message) {
|
||||
assert.equal(Math.abs(Number(actual) - Number(expected)) < 1e-9, true, `${message}: ${actual} !== ${expected}`);
|
||||
}
|
||||
|
||||
133
web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
Normal file
133
web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
Normal file
@@ -0,0 +1,133 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
||||
import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
|
||||
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
|
||||
import {
|
||||
applyIniConfigToProfile,
|
||||
parseLinuxCncIni,
|
||||
} from "../../app/src/runtime/linuxcnc-ini-runtime.js";
|
||||
import { stageProfileMachineFiles } from "../../app/src/runtime/linuxcnc-machine-file-staging.js";
|
||||
import { buildTaskHalSessionFromMachineFiles } from "../../app/src/runtime/linuxcnc-task-hal-runtime.js";
|
||||
import {
|
||||
createSimulationStore,
|
||||
validateRunPreconditions,
|
||||
} from "../../app/src/state/store.js";
|
||||
|
||||
const cases = [
|
||||
{
|
||||
profileId: "xyzac-trt",
|
||||
machineName: "sim-xyzac-trt-kins (switchkins)",
|
||||
coordinates: "XYZAC",
|
||||
kinematicsName: "xyzac-trt-kins",
|
||||
kinematicsModuleId: "xyzac-trt",
|
||||
wasmFile: "linuxcnc_xyzac_trt_kinematics.wasm",
|
||||
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins_test_1.ngc",
|
||||
},
|
||||
{
|
||||
profileId: "xyzbc-trt",
|
||||
machineName: "sim-xyzbc-trt-kins (switchkins)",
|
||||
coordinates: "XYZBC",
|
||||
kinematicsName: "xyzbc-trt-kins",
|
||||
kinematicsModuleId: "xyzbc-trt",
|
||||
wasmFile: "linuxcnc_xyzbc_trt_kinematics.wasm",
|
||||
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzbc.ngc",
|
||||
},
|
||||
];
|
||||
|
||||
for (const item of cases) {
|
||||
const baseProfile = getFiveAxisProfile(item.profileId);
|
||||
const iniText = await readLinuxCncFile(baseProfile.iniPath);
|
||||
const iniConfig = parseLinuxCncIni(iniText, {
|
||||
path: baseProfile.iniPath,
|
||||
profileId: item.profileId,
|
||||
});
|
||||
|
||||
assert.equal(iniConfig.profileId, item.profileId);
|
||||
assert.equal(iniConfig.machineName, item.machineName);
|
||||
assert.equal(iniConfig.traj.coordinates, item.coordinates);
|
||||
assert.equal(iniConfig.kinematics.name, item.kinematicsName);
|
||||
assert.equal(iniConfig.kinematicsModuleId, item.kinematicsModuleId);
|
||||
assert.equal(iniConfig.validation.ready, true);
|
||||
|
||||
const profile = applyIniConfigToProfile(baseProfile, iniConfig);
|
||||
assert.equal(profile.traj.coordinates, item.coordinates);
|
||||
assert.equal(profile.kinematicsModuleId, item.kinematicsModuleId);
|
||||
assert.equal(profile.axisLimits[item.coordinates[0]].max > 0, true);
|
||||
assert.equal(profile.jointConfig.length, item.coordinates.length);
|
||||
|
||||
const kinematicsRuntime = await createLinuxCncKinematicsRuntime({ moduleId: item.kinematicsModuleId });
|
||||
assert.equal(kinematicsRuntime.readiness().moduleId, item.kinematicsModuleId);
|
||||
assert.equal(kinematicsRuntime.wasmFile, item.wasmFile);
|
||||
assert.equal(kinematicsRuntime.frameForJoints([10, 20, 30, 5, 15]).moduleId, item.kinematicsModuleId);
|
||||
|
||||
const staged = await stageProfileMachineFiles(profile);
|
||||
const selectedPlan = staged.plan.selectedProgramSourceRel === item.sourceRel
|
||||
? staged.plan
|
||||
: {
|
||||
...staged.plan,
|
||||
wasmProgramPath: staged.save.files.find((file) => file.sourceRel === item.sourceRel)?.wasmPath,
|
||||
selectedProgramSourceRel: item.sourceRel,
|
||||
};
|
||||
const session = buildTaskHalSessionFromMachineFiles({
|
||||
profile,
|
||||
plan: selectedPlan,
|
||||
save: staged.save,
|
||||
selectedProgramRel: item.sourceRel,
|
||||
});
|
||||
assert.equal(session.profileId, item.profileId);
|
||||
assert.equal(session.iniPath.endsWith(`${item.profileId}.ini`), true);
|
||||
assert.equal(session.programSourceRel, item.sourceRel);
|
||||
assert.equal(session.programPath.endsWith(item.sourceRel.split("/").at(-1)), true);
|
||||
}
|
||||
|
||||
const initialStore = createSimulationStore();
|
||||
let check = validateRunPreconditions(initialStore.getState(), {
|
||||
requireTaskHalRuntime: false,
|
||||
requireTaskHalSession: false,
|
||||
});
|
||||
assert.equal(check.ok, false);
|
||||
assert.equal(check.operatorMessage, "run blocked: LinuxCNC INI not loaded");
|
||||
|
||||
const profile = getFiveAxisProfile("xyzac-trt");
|
||||
const iniText = await readLinuxCncFile(profile.iniPath);
|
||||
const iniConfig = parseLinuxCncIni(iniText, {
|
||||
path: profile.iniPath,
|
||||
profileId: profile.id,
|
||||
});
|
||||
const store = createSimulationStore();
|
||||
store.dispatch({ type: "ATTACH_INI_CONFIG", profileId: profile.id, iniConfig });
|
||||
store.dispatch({
|
||||
type: "ATTACH_KINEMATICS_RUNTIME",
|
||||
runtime: await createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" }),
|
||||
});
|
||||
await store.stageMachineFiles();
|
||||
store.dispatch({
|
||||
type: "LOAD_LINUXCNC_GCODE_SOURCE",
|
||||
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins_test_1.ngc",
|
||||
});
|
||||
|
||||
check = validateRunPreconditions(store.getState(), {
|
||||
requireTaskHalRuntime: false,
|
||||
requireTaskHalSession: false,
|
||||
});
|
||||
assert.equal(check.ok, true);
|
||||
assert.equal(check.profileId, "xyzac-trt");
|
||||
assert.equal(check.coordinates, "XYZAC");
|
||||
assert.equal(check.kinematicsModuleId, "xyzac-trt");
|
||||
assert.equal(check.selectedGcodeSourceRel.endsWith("xyzac_switchkins_test_1.ngc"), true);
|
||||
|
||||
check = validateRunPreconditions(store.getState(), {
|
||||
requireTaskHalRuntime: true,
|
||||
requireTaskHalSession: false,
|
||||
});
|
||||
assert.equal(check.ok, false);
|
||||
assert.equal(check.operatorMessage, "run blocked: task/HAL runtime not ready");
|
||||
|
||||
console.log("run_preconditions_ini_profile_smoke=ok");
|
||||
console.log("run_preconditions_kinematics_smoke=ok");
|
||||
console.log("run_preconditions_machine_file_smoke=ok");
|
||||
|
||||
async function readLinuxCncFile(sourceRel) {
|
||||
return readFile(new URL(`../../../wasm-port/vendor/linuxcnc/${sourceRel}`, import.meta.url), "utf8");
|
||||
}
|
||||
Reference in New Issue
Block a user