接入 LinuxCNC TP 运行反馈

This commit is contained in:
2026-06-21 23:29:56 +08:00
parent 626bcfe8e3
commit 3771b9eafe
44 changed files with 7881 additions and 326 deletions

View File

@@ -0,0 +1,73 @@
import assert from "node:assert/strict";
import { createFiveAxisSessionPayload, createMemorySessionStorage } from "../../app/src/runtime/five-axis-session.js";
import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc-interpreter-runtime.js";
import { createSimulationStore } from "../../app/src/state/store.js";
async function waitForInterpreterExecution(store) {
for (let attempt = 0; attempt < 20; attempt += 1) {
const state = store.getState();
if (!state.interpreterExecutionPending && state.programExecutionSourceMode === "linuxcnc-interpreter-wasm") {
return state;
}
await new Promise((resolve) => setTimeout(resolve, 0));
}
return store.getState();
}
const store = createSimulationStore();
const interpreterRuntime = await createLinuxCncInterpreterRuntime();
store.dispatch({ type: "ATTACH_INTERPRETER_RUNTIME", runtime: interpreterRuntime });
store.dispatch({ type: "TOGGLE_POWER" });
store.dispatch({ type: "HOME" });
store.dispatch({ type: "SET_MODE", mode: "auto" });
store.dispatch({ type: "SET_RTCP", enabled: true });
store.dispatch({
type: "LOAD_PROGRAM",
filename: "session-demo.ngc",
content: [
"G90 G17",
"G0 X0 Y0 Z0",
"G1 X4 Y5 A6 C7 F100",
"M2",
].join("\n"),
});
await waitForInterpreterExecution(store);
store.dispatch({ type: "STEP" });
const beforeSave = store.getState();
const payload = createFiveAxisSessionPayload(beforeSave);
assert.equal(payload.apiName, "web-rtcp-5axis-session-payload");
assert.equal(payload.machineProfile, "xyzac-trt");
assert.equal(payload.programExecution.sourceMode, "linuxcnc-interpreter-wasm");
assert.equal(payload.rtcpState, "on");
assert.equal(payload.programRuntimeFeedback.sourceMode, "linuxcnc-tp-runtime-sample");
assert.equal(payload.programRuntimeFeedback.semanticBoundary, "linuxcnc_tp_run_cycle_feedback_without_hardware");
const storage = createMemorySessionStorage();
const saved = await store.saveSession({ storage });
assert.equal(saved.snapshot.format, "web-rtcp-5axis-session-snapshot");
assert.equal(saved.path, "web-rtcp-5axis-sim-plan/sessions/gmoccapy-web-session/web-rtcp-5axis-session.json");
store.dispatch({ type: "SET_RTCP", enabled: false });
store.dispatch({ type: "STOP" });
store.dispatch({ type: "SET_MODE", mode: "manual" });
store.dispatch({ type: "JOG", axis: "x", direction: 1, increment: 25 });
assert.notEqual(store.getState().rtcpState, beforeSave.rtcpState);
assert.notEqual(store.getState().axisPose.x, beforeSave.axisPose.x);
const restored = await store.restoreSession({ storage });
const afterRestore = store.getState();
assert.equal(restored.path, saved.path);
assert.equal(afterRestore.sessionPersistence.status, "restored");
assert.equal(afterRestore.rtcpState, beforeSave.rtcpState);
assert.equal(afterRestore.kinsType, beforeSave.kinsType);
assert.equal(afterRestore.axisPose.x, beforeSave.axisPose.x);
assert.equal(afterRestore.activeProgram, "session-demo.ngc");
assert.equal(afterRestore.programExecution.sourceMode, "linuxcnc-interpreter-wasm");
assert.equal(afterRestore.programExecution.summary.motionEventCount, beforeSave.programExecution.summary.motionEventCount);
assert.equal(afterRestore.programRuntimeFeedback.semanticBoundary, beforeSave.programRuntimeFeedback.semanticBoundary);
assert.equal(afterRestore.programRuntimeFeedback.axisPose.x, beforeSave.programRuntimeFeedback.axisPose.x);
console.log("five_axis_session_smoke=ok");

View File

@@ -0,0 +1,108 @@
import assert from "node:assert/strict";
import { createFullLinuxCncExecutionBoundary } from "../../app/src/runtime/full-execution-boundary.js";
const blocked = createFullLinuxCncExecutionBoundary({});
assert.equal(blocked.apiName, "web-rtcp-5axis-full-linuxcnc-execution-boundary");
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.blockers.some((blocker) => blocker.includes("native LinuxCNC task")), true);
const canonicalState = {
machineProfile: "xyzac-trt",
linuxCncBoundaryAdapter: {
linuxCncKinematicsReady: true,
linuxCncInterpreterReady: true,
},
rtcpFrame: {
semanticBoundary: "linuxcnc_kinematics_wasm_c_abi",
readiness: { linuxCncKinematicsReady: true },
},
interpreterRuntimeReadiness: {
loaded: true,
semanticBoundary: "linuxcnc_interpreter_wasm_canonical_events",
},
programExecutionSourceMode: "linuxcnc-interpreter-wasm",
programExecution: {
sourceMode: "linuxcnc-interpreter-wasm",
summary: {
motionEventCount: 3,
canonicalEventCount: 8,
},
},
};
const canonical = createFullLinuxCncExecutionBoundary(canonicalState);
assert.equal(canonical.readyForUiSimulation, true);
assert.equal(canonical.machineFileBackedRemapReady, false);
assert.equal(canonical.semanticBoundary, "linuxcnc_interpreter_canonical_ready_planner_task_hal_blocked");
assert.equal(canonical.satisfied.includes("canonical-motion-events"), true);
assert.equal(canonical.plannerRuntimeReady, false);
assert.equal(canonical.missing.includes("machine-file backed five-axis remap run"), true);
const canonicalWithPlanner = createFullLinuxCncExecutionBoundary({
...canonicalState,
programExecution: {
...canonicalState.programExecution,
plannerTiming: {
plannerRuntimeReady: true,
semanticBoundary: "linuxcnc_tp_queue_runtime_timing_from_canonical_motion",
},
summary: {
...canonicalState.programExecution.summary,
plannerRuntimeReady: true,
},
},
});
assert.equal(canonicalWithPlanner.plannerRuntimeReady, true);
assert.equal(canonicalWithPlanner.satisfied.includes("linuxcnc-tp-queue-runtime-timing"), true);
assert.equal(canonicalWithPlanner.nativeTaskReady, false);
assert.equal(canonicalWithPlanner.nativeHalSyncReady, false);
const remap = 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: 12,
},
machineFileExecution: {
sourceMode: "linuxcnc-machine-file-remap-wasm",
summary: {
machineFileExecutionReady: true,
},
resultText: [
"fiveaxis_ini_open=1",
"fiveaxis_remaps_ready=1",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
].join("\n"),
},
});
assert.equal(remap.machineFileBackedRemapReady, true);
assert.equal(remap.remapRuntimeReady, true);
assert.equal(remap.halSwitchkinsEvidenceReady, true);
assert.equal(remap.plannerRuntimeReady, true);
assert.equal(remap.nativeTaskReady, false);
assert.equal(remap.nativeHalSyncReady, false);
assert.equal(remap.fullLinuxCncProgramExecutionReady, false);
assert.equal(remap.promotionAllowed, false);
assert.equal(remap.semanticBoundary, "linuxcnc_machine_file_remap_ready_planner_task_hal_blocked");
assert.equal(remap.satisfied.includes("fiveaxis-remap-machine-file-run"), true);
assert.equal(remap.evidence.machineFileFlags.length, 3);
console.log("full_execution_boundary_smoke=ok");

View File

@@ -0,0 +1,109 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
createLinuxCncInterpSdk,
createLinuxCncKinematicsSdk,
planSimConfigStaging,
} from "../../../wasm-port/runtime/sdk/src/index.js";
import { parseLinuxCncIni } from "../../app/src/runtime/linuxcnc-ini-runtime.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const workspaceRoot = resolve(__dirname, "../../..");
const wasmPortRoot = resolve(workspaceRoot, "wasm-port");
const vendorRoot = resolve(wasmPortRoot, "vendor/linuxcnc");
const trtConfigRel = "configs/sim/axis/vismach/5axis/table-rotary-tilting";
function near(actual, expected, tolerance = 1e-7) {
return Math.abs(actual - expected) <= tolerance;
}
function assertPoseComponent(pose, key, expected, label) {
assert.equal(near(Number(pose[key] ?? 0), expected), true, `${label}.${key}: ${pose[key]} != ${expected}`);
}
async function verifyTrtKinematics(moduleId, joints, expectedAxis) {
const wasmFile = `linuxcnc_${moduleId.replaceAll("-", "_")}_kinematics.wasm`;
const kins = await createLinuxCncKinematicsSdk({
moduleId,
moduleOptions: {
wasmBinary: readFileSync(resolve(wasmPortRoot, "build/wasm/kinematics", wasmFile)),
print() {},
printErr() {},
},
});
assert.equal(kins.apiName, "linuxcnc-kinematics-wasm-sdk");
assert.equal(kins.switchable(), 1);
assert.equal(kins.switchKinematics(0), 0);
const forward = kins.forward(joints);
assert.equal(forward.rc, 0, `${moduleId} forward`);
const inverse = kins.inverse(forward.pose, 5, { seedJoints: joints });
assert.equal(inverse.rc, 0, `${moduleId} inverse`);
inverse.joints.slice(0, 5).forEach((joint, index) => {
assert.equal(near(joint, joints[index]), true, `${moduleId} inverse joint ${index}`);
});
assert.equal(kins.switchKinematics(1), 0);
const identity = kins.forward(joints);
assert.equal(identity.rc, 0, `${moduleId} identity forward`);
assertPoseComponent(identity.pose, "x", joints[0], `${moduleId} identity`);
assertPoseComponent(identity.pose, "y", joints[1], `${moduleId} identity`);
assertPoseComponent(identity.pose, "z", joints[2], `${moduleId} identity`);
assertPoseComponent(identity.pose, expectedAxis, joints[3], `${moduleId} identity`);
assertPoseComponent(identity.pose, "c", joints[4], `${moduleId} identity`);
}
await verifyTrtKinematics("xyzac-trt", [10, 20, 30, 25, 40], "a");
await verifyTrtKinematics("xyzbc-trt", [10, 20, 30, 35, 40], "b");
const manifestText = readFileSync(resolve(wasmPortRoot, "tools/source-manifest.txt"), "utf8");
for (const iniFile of ["xyzac-trt.ini", "xyzbc-trt.ini"]) {
const iniText = readFileSync(resolve(vendorRoot, trtConfigRel, iniFile), "utf8");
const iniConfig = parseLinuxCncIni(iniText, {
path: `${trtConfigRel}/${iniFile}`,
profileId: iniFile.startsWith("xyzbc") ? "xyzbc-trt" : "xyzac-trt",
});
assert.equal(iniConfig.validation.ready, true, `${iniFile} INI ready`);
assert.equal(iniConfig.kinematics.name.endsWith("-trt-kins"), true, `${iniFile} kins`);
assert.deepEqual(iniConfig.halui.mdiCommands, ["M429", "M428", "M430"], `${iniFile} HALUI MDI`);
const plan = planSimConfigStaging({
manifestText,
machineRel: `axis/vismach/5axis/table-rotary-tilting`,
iniFile,
iniText,
});
const staged = plan.files.map((file) => file.sourceRel);
assert.equal(staged.includes(`${trtConfigRel}/${iniFile}`), true, `${iniFile} staged`);
assert.equal(staged.some((file) => file.endsWith("/remap_subs/428remap.ngc")), true, `${iniFile} M428 remap staged`);
assert.equal(staged.some((file) => file.endsWith("/remap_subs/429remap.ngc")), true, `${iniFile} M429 remap staged`);
assert.equal(staged.some((file) => file.endsWith("/remap_subs/430remap.ngc")), true, `${iniFile} M430 remap staged`);
assert.equal(staged.some((file) => file.endsWith(".tbl")), true, `${iniFile} tool table staged`);
}
const interp = await createLinuxCncInterpSdk({
wasmBinary: readFileSync(resolve(wasmPortRoot, "build/wasm/core/linuxcnc_interp.wasm")),
print() {},
printErr() {},
});
const directFiveAxisProgram = [
"G90 G17",
"G0 X0 Y0 Z0 A0 C0",
"G1 X10 Y2 Z-1 A15 C30 F120",
"G1 X0 Y0 Z0 A0 C0 F120",
"M2",
"",
].join("\n");
const output = interp.runProgram(directFiveAxisProgram);
assert.equal(output.includes("canon_event=STRAIGHT_TRAVERSE"), true);
assert.equal(output.includes("canon_event=STRAIGHT_FEED"), true);
assert.equal(output.includes("a=15"), true);
assert.equal(output.includes("c=30"), true);
console.log("full_linuxcnc_5axis_source_node_smoke=ok");

View File

@@ -0,0 +1,57 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import {
applyIniConfigToProfile,
parseLinuxCncIni,
} from "../../app/src/runtime/linuxcnc-ini-runtime.js";
import { xyzacTrtProfile } from "../../app/src/profiles/xyzac-trt.js";
import { xyzbcTrtProfile } from "../../app/src/profiles/xyzbc-trt.js";
const xyzacIniText = await readFile(
new URL("../../../wasm-port/vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", import.meta.url),
"utf8",
);
const xyzbcIniText = await readFile(
new URL("../../../wasm-port/vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini", import.meta.url),
"utf8",
);
const xyzacIni = parseLinuxCncIni(xyzacIniText, {
path: xyzacTrtProfile.iniPath,
profileId: xyzacTrtProfile.id,
});
assert.equal(xyzacIni.apiName, "web-rtcp-5axis-linuxcnc-ini-config");
assert.equal(xyzacIni.validation.ready, true);
assert.equal(xyzacIni.machineName, "sim-xyzac-trt-kins (switchkins)");
assert.equal(xyzacIni.kinematics.name, "xyzac-trt-kins");
assert.equal(xyzacIni.kinematicsParameters.sparm, "identityfirst");
assert.equal(xyzacIni.kinematicsParameters.joints, 5);
assert.equal(xyzacIni.traj.coordinates, "XYZAC");
assert.equal(xyzacIni.axisLimits.A.max, 50);
assert.equal(xyzacIni.jointConfig[3].axis, "A");
assert.equal(xyzacIni.jointConfig[4].max, 36000);
assert.deepEqual(xyzacIni.halui.mdiCommands, ["M429", "M428", "M430"]);
assert.equal(xyzacIni.rs274ngc.remaps.map((remap) => remap.code).join(","), "M428,M429,M430");
assert.equal(xyzacIni.hal.initialSets.some((set) => set.pin === "y-offset" && set.value === 20), true);
const derivedXyzac = applyIniConfigToProfile(xyzacTrtProfile, xyzacIni);
assert.equal(derivedXyzac.machineName, xyzacIni.machineName);
assert.equal(derivedXyzac.traj.coordinates, "XYZAC");
assert.equal(derivedXyzac.axisLimits.X.min, -200);
assert.equal(derivedXyzac.jointConfig.length, 5);
assert.equal(derivedXyzac.linuxCncIniConfig.path.endsWith("xyzac-trt.ini"), true);
const xyzbcIni = parseLinuxCncIni(xyzbcIniText, {
path: xyzbcTrtProfile.iniPath,
profileId: xyzbcTrtProfile.id,
});
assert.equal(xyzbcIni.validation.ready, true);
assert.equal(xyzbcIni.kinematics.name, "xyzbc-trt-kins");
assert.equal(xyzbcIni.kinematicsModuleId, "xyzbc-trt");
assert.equal(xyzbcIni.traj.coordinates, "XYZBC");
assert.equal(xyzbcIni.axisLimits.B.max, 36000);
assert.equal(xyzbcIni.jointConfig[3].axis, "B");
assert.equal(xyzbcIni.kinematicsParameters.switchkinsTypes[1].webKinsType, "tcp-xyzbc");
console.log("linuxcnc_ini_runtime_smoke=ok");

View File

@@ -0,0 +1,80 @@
import assert from "node:assert/strict";
import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc-interpreter-runtime.js";
const runtime = await createLinuxCncInterpreterRuntime();
const readiness = runtime.readiness();
assert.equal(runtime.apiName, "web-rtcp-5axis-linuxcnc-interpreter-runtime");
assert.equal(runtime.loaded, true);
assert.equal(runtime.sourceMode, "linuxcnc-interpreter-wasm");
assert.equal(runtime.semanticBoundary, "linuxcnc_interpreter_wasm_canonical_events");
assert.equal(readiness.loaded, true);
assert.equal(readiness.runProgramReady, true);
assert.equal(readiness.remapRuntimeReady, false);
assert.equal(readiness.plannerRuntimeReady, true);
assert.equal(readiness.plannerSemanticBoundary, "linuxcnc_tp_queue_runtime_timing_from_canonical_motion");
const programText = [
"G90 G17",
"G0 X0 Y0 Z0",
"G1 X10 Y2 F100",
"G1 X12 Y4 A5 C7",
"M2",
].join("\n");
const execution = runtime.runProgram(programText);
assert.equal(execution.apiName, "web-rtcp-5axis-linuxcnc-interpreter-program-execution");
assert.equal(execution.sourceMode, "linuxcnc-interpreter-wasm");
assert.equal(execution.semanticBoundary, "linuxcnc_interpreter_wasm_canonical_events");
assert.equal(execution.summary.ready, true);
assert.equal(execution.summary.motionEventCount >= 3, true);
assert.equal(execution.summary.canonicalEventCount > execution.summary.motionEventCount, true);
assert.equal(execution.summary.motionTypes.includes("STRAIGHT_TRAVERSE"), true);
assert.equal(execution.summary.motionTypes.includes("STRAIGHT_FEED"), true);
assert.equal(execution.summary.finalAxes.x, 12);
assert.equal(execution.summary.finalAxes.y, 4);
assert.equal(execution.summary.finalAxes.a, 5);
assert.equal(execution.summary.finalAxes.c, 7);
assert.equal(execution.motion[2].feedRate, 100);
assert.equal(execution.summary.remapRuntimeReady, false);
assert.equal(execution.summary.plannerRuntimeReady, true);
assert.equal(execution.plannerTiming.semanticBoundary, "linuxcnc_tp_queue_runtime_timing_from_canonical_motion");
assert.equal(execution.plannerTiming.totalSeconds > 0, true);
assert.equal(execution.plannerTiming.motionCount, execution.motion.length);
assert.equal(execution.summary.fullLinuxCncProgramExecutionReady, false);
const switchkinsProgramText = [
"G90 G17",
"M428",
"G0 X0 Y0 Z0 A0 C0",
"G1 X10 Y2 Z-1 A15 C30 F120",
"M429",
"G1 X0 Y0 Z0 A0 C0 F120",
"M2",
].join("\n");
const switchkinsExecution = runtime.runProgram(switchkinsProgramText);
assert.equal(switchkinsExecution.summary.ready, true);
assert.equal(switchkinsExecution.summary.motionEventCount, 3);
assert.equal(switchkinsExecution.summary.switchkinsEventCount, 2);
assert.deepEqual(switchkinsExecution.summary.switchkinsCodes, ["M428", "M429"]);
assert.equal(
switchkinsExecution.summary.switchkinsRemapBoundary,
"linuxcnc_switchkins_remap_mcode_preserved_web_runtime_applied",
);
assert.equal(switchkinsExecution.switchkinsEvents[0].switchkinsType, 1);
assert.equal(switchkinsExecution.switchkinsEvents[1].switchkinsType, 0);
assert.equal(switchkinsExecution.motion[0].kinsType, "tcp");
assert.equal(switchkinsExecution.motion[1].axes.a, 15);
assert.equal(switchkinsExecution.motion[1].axes.c, 30);
assert.equal(switchkinsExecution.motion[1].switchkinsCode, "M428");
assert.equal(switchkinsExecution.motion[2].kinsType, "identity");
assert.equal(switchkinsExecution.motion[2].switchkinsCode, "M429");
assert.equal(switchkinsExecution.summary.remapRuntimeReady, false);
assert.equal(switchkinsExecution.summary.plannerRuntimeReady, true);
assert.equal(switchkinsExecution.plannerTiming.totalSeconds > 0, true);
assert.equal(switchkinsExecution.summary.fullLinuxCncProgramExecutionReady, false);
console.log("linuxcnc_interpreter_runtime_smoke=ok");

View File

@@ -11,8 +11,10 @@ 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);

View File

@@ -0,0 +1,128 @@
import assert from "node:assert/strict";
import { createMemorySessionStorage } from "../../app/src/runtime/five-axis-session.js";
import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc-interpreter-runtime.js";
import {
createMachineFileStagingPlan,
selectMachineFileProgram,
stageProfileMachineFiles,
} from "../../app/src/runtime/linuxcnc-machine-file-staging.js";
import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
import { createSimulationStore } from "../../app/src/state/store.js";
async function waitForMachineFileExecution(store) {
for (let attempt = 0; attempt < 20; attempt += 1) {
const state = store.getState();
if (!state.interpreterExecutionPending && state.machineFileExecution) {
return state;
}
await new Promise((resolve) => setTimeout(resolve, 0));
}
return store.getState();
}
const profile = getFiveAxisProfile("xyzac-trt");
const storage = createMemorySessionStorage();
const plan = await createMachineFileStagingPlan({ profile });
assert.equal(plan.apiName, "web-rtcp-5axis-machine-file-staging-plan");
assert.equal(plan.profileId, "xyzac-trt");
assert.equal(plan.semanticBoundary, "linuxcnc_sim_config_file_staging_plan_only");
assert.equal(plan.files.some((file) => file.sourceRel.endsWith("xyzac-trt.ini")), true);
assert.equal(plan.files.some((file) => file.sourceRel.endsWith("xyzac-trt.tbl")), true);
assert.equal(plan.files.some((file) => file.sourceRel.endsWith("remap_subs/428remap.ngc")), true);
assert.equal(plan.files.some((file) => file.sourceRel.endsWith("remap_subs/429remap.ngc")), true);
assert.equal(plan.files.some((file) => file.sourceRel.endsWith("demos/xyzac_switchkins.ngc")), true);
assert.equal(plan.summary.remapFileCount >= 3, true);
assert.equal(plan.summary.demoFileCount >= 1, true);
const staged = await stageProfileMachineFiles(profile, { storage });
assert.equal(staged.save.apiName, "web-rtcp-5axis-machine-file-staging-save");
assert.equal(staged.save.status, "saved");
assert.equal(staged.save.fileCount, staged.plan.files.length);
assert.equal(staged.save.semanticBoundary, "opfs_machine_file_text_staging_only");
assert.equal(staged.save.summary.kinds.remap >= 3, true);
assert.equal(staged.save.gcodeSources.some((source) => source.filename === "impeller-7bl-xyzac.ngc"), true);
assert.equal(staged.save.gcodeSources.some((source) => source.filename === "boat-xyzac.ngc"), true);
assert.equal(staged.save.files.every((file) => file.opfsPath.startsWith("web-rtcp-5axis-sim-plan/machines/xyzac-trt/")), true);
assert.throws(
() => selectMachineFileProgram(staged.plan, staged.save, "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc"),
/must come from LinuxCNC source demos/,
);
const iniOpfsPath = staged.save.files.find((file) => file.sourceRel.endsWith("xyzac-trt.ini")).opfsPath;
assert.equal(storage.files.get(iniOpfsPath).includes("KINEMATICS = xyzac-trt-kins"), true);
const store = createSimulationStore();
store.dispatch({
type: "ATTACH_INTERPRETER_RUNTIME",
runtime: await createLinuxCncInterpreterRuntime(),
});
const storeStage = await store.stageMachineFiles({ storage });
const state = store.getState();
assert.equal(storeStage.save.fileCount, staged.save.fileCount);
assert.equal(state.machineFileStaging.status, "staged");
assert.equal(state.machineFileStaging.profileId, "xyzac-trt");
assert.equal(state.machineFileStaging.fileCount, staged.save.fileCount);
assert.equal(state.machineFileStaging.save.summary.kinds.remap >= 3, true);
assert.equal(state.machineFileStaging.gcodeSources.length >= 4, true);
assert.equal(state.machineFileStaging.selectedGcodeSourceRel, null);
store.dispatch({ type: "RUN_MACHINE_FILE_PROGRAM" });
assert.equal(
store.getState().operatorMessage,
"machine-file run blocked: select a LinuxCNC source-directory 5-axis G-code program",
);
store.dispatch({
type: "LOAD_LINUXCNC_GCODE_SOURCE",
sourceRel: "operator-demo.ngc",
});
assert.equal(
store.getState().operatorMessage,
"LinuxCNC G-code source not staged: operator-demo.ngc",
);
store.dispatch({
type: "LOAD_LINUXCNC_GCODE_SOURCE",
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/impeller-7bl-xyzac.ngc",
});
await new Promise((resolve) => setTimeout(resolve, 0));
const sourceState = store.getState();
assert.equal(sourceState.programSource, "linuxcnc-vendored-5axis-gcode");
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);
store.dispatch({ type: "RUN_MACHINE_FILE_PROGRAM" });
const runState = await waitForMachineFileExecution(store);
assert.equal(runState.machineFileExecution.sourceMode, "linuxcnc-machine-file-remap-wasm");
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.fullLinuxCncProgramExecutionReady, false);
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);
assert.equal(runState.machineFileExecution.machineFilePlan.selectedProgramFilename, "impeller-7bl-xyzac.ngc");
assert.equal(runState.machineFileExecution.machineFilePlan.wasmProgramPath.endsWith("/demos/impeller-7bl-xyzac.ngc"), true);
assert.equal(runState.fullExecutionBoundary.apiName, "web-rtcp-5axis-full-linuxcnc-execution-boundary");
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.nativeTaskReady, false);
assert.equal(runState.fullExecutionBoundary.nativeHalSyncReady, false);
assert.equal(runState.fullExecutionBoundary.fullLinuxCncProgramExecutionReady, false);
assert.equal(runState.fullExecutionBoundary.promotionAllowed, false);
assert.equal(
runState.fullExecutionBoundary.semanticBoundary,
"linuxcnc_machine_file_remap_ready_planner_task_hal_blocked",
);
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);
console.log("machine_file_staging_smoke=ok");

View File

@@ -1,6 +1,7 @@
import assert from "node:assert/strict";
import { xyzacTrtProfile } from "../../app/src/profiles/xyzac-trt.js";
import { getFiveAxisProfile, fiveAxisProfiles } from "../../app/src/profiles/index.js";
import { createPyvcpHalBindingSummary, xyzacTrtPyvcpPanelSchema } from "../../app/src/panel-schema/xyzac-trt-pyvcp.js";
import { createLinuxCncBoundaryAdapter, createLinuxCncBoundaryReadiness } from "../../app/src/runtime/linuxcnc-boundary-adapter.js";
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
@@ -29,6 +30,20 @@ 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"]);
const xyzbcTrtProfile = getFiveAxisProfile("xyzbc-trt");
assert.equal(xyzbcTrtProfile.id, "xyzbc-trt");
assert.equal(xyzbcTrtProfile.iniPath.endsWith("xyzbc-trt.ini"), true);
assert.deepEqual(xyzbcTrtProfile.coordinates, ["X", "Y", "Z", "B", "C"]);
assert.equal(xyzbcTrtProfile.kinematics, "xyzbc-trt-kins");
assert.equal(xyzbcTrtProfile.kinematicsModuleId, "xyzbc-trt");
assert.equal(xyzbcTrtProfile.machineName, "sim-xyzbc-trt-kins (switchkins)");
assert.equal(xyzbcTrtProfile.traj.coordinates, "XYZBC");
assert.equal(xyzbcTrtProfile.axisLimits.B.max, 36000);
assert.equal(xyzbcTrtProfile.hal.halcmd.feedbackNets.some((net) => net.target === "xyzbc-trt-gui.tilt-b"), true);
assert.equal(xyzbcTrtProfile.halPins.includes("xyzbc-trt-kins.x-offset"), true);
assert.equal(xyzbcTrtProfile.panelSchema.id, "xyzbc-trt-switchkins-pyvcp");
const sourceSummary = createProfileSourceReferenceSummary("xyzac-trt");
assert.equal(sourceSummary.referenceCount >= 8, true);
@@ -39,6 +54,11 @@ assert.equal(sourceSummary.semanticBoundary, "profile_source_map_only_not_runtim
assert.ok(sourceSummary.references.some((reference) => reference.path.endsWith("xyzac-trt.ini")));
assert.ok(sourceSummary.references.some((reference) => reference.path.endsWith("trtfuncs.c")));
const xyzbcSourceSummary = createProfileSourceReferenceSummary("xyzbc-trt");
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 panelSummary = createPyvcpHalBindingSummary(xyzacTrtPyvcpPanelSchema);
assert.equal(panelSummary.schemaId, "xyzac-trt-switchkins-pyvcp");
assert.equal(panelSummary.controlCount, 5);

View File

@@ -1,8 +1,22 @@
import assert from "node:assert/strict";
import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc-interpreter-runtime.js";
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
import { parseLinuxCncIni } from "../../app/src/runtime/linuxcnc-ini-runtime.js";
import { buildRtcpFrame } from "../../app/src/runtime/rtcp-frame.js";
import { createSimulationStore } from "../../app/src/state/store.js";
import { readFile } from "node:fs/promises";
async function waitForInterpreterExecution(store) {
for (let attempt = 0; attempt < 20; attempt += 1) {
const state = store.getState();
if (!state.interpreterExecutionPending && state.programExecutionSourceMode === "linuxcnc-interpreter-wasm") {
return state;
}
await new Promise((resolve) => setTimeout(resolve, 0));
}
return store.getState();
}
const identityFrame = buildRtcpFrame({
axisPose: { x: 43, y: -32.15, z: -11.306, a: 0, b: 0, c: 0 },
@@ -34,6 +48,7 @@ assert.ok(Number.isFinite(tcpFrame.toolAxisVector.z));
assert.equal(tcpFrame.jointPose.length, 5);
const runtime = await createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" });
const interpreterRuntime = await createLinuxCncInterpreterRuntime();
const linuxCncKinematicsResult = runtime.frameForJoints([10, 20, 30, 25, 40]);
const linuxCncFrame = buildRtcpFrame({
axisPose: { x: 10, y: 20, z: 30, a: 25, b: 0, c: 40 },
@@ -65,6 +80,37 @@ assert.equal(fixtureInitialStore.getState().linuxCncBoundaryAdapter.profileSumma
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryAdapter.profileSummary.coordinates, "XYZAC");
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryReadiness.ready, false);
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryReadiness.promotionAllowed, false);
assert.equal(fixtureInitialStore.getState().fullExecutionBoundary.fullLinuxCncProgramExecutionReady, false);
assert.equal(fixtureInitialStore.getState().fullExecutionBoundary.promotionAllowed, false);
assert.equal(fixtureInitialStore.getState().fullExecutionBoundary.phase, "blocked");
assert.equal(fixtureInitialStore.getState().linuxCncTaskPolicy.semanticBoundary, "linuxcnc_task_state_mode_command_gate");
assert.equal(
fixtureInitialStore.getState().linuxCncTaskPolicy.sourceReferences.some((reference) => reference.path === "linuxcnc/src/emc/task/emctaskmain.cc"),
true,
);
assert.equal(fixtureInitialStore.getState().linuxCncTaskPolicy.canRunAuto, false);
const xyzacIniText = await readFile(
new URL("../../../wasm-port/vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", import.meta.url),
"utf8",
);
const xyzacIniConfig = parseLinuxCncIni(xyzacIniText, {
path: fixtureInitialStore.getState().profile.iniPath,
profileId: fixtureInitialStore.getState().machineProfile,
});
fixtureInitialStore.dispatch({ type: "ATTACH_INI_CONFIG", iniConfig: xyzacIniConfig });
assert.equal(fixtureInitialStore.getState().iniConfigReadiness.loaded, true);
assert.equal(fixtureInitialStore.getState().iniConfigReadiness.ready, true);
assert.equal(fixtureInitialStore.getState().profile.axisLimits.X.max, 200);
fixtureInitialStore.dispatch({ type: "TOGGLE_POWER" });
fixtureInitialStore.dispatch({ type: "HOME" });
fixtureInitialStore.dispatch({ type: "SET_MODE", mode: "mdi" });
fixtureInitialStore.dispatch({ type: "RUN_MDI", command: "G90 X999 Y-999 Z999 A999 C99999" });
assert.equal(fixtureInitialStore.getState().axisPose.x, 200);
assert.equal(fixtureInitialStore.getState().axisPose.y, -100);
assert.equal(fixtureInitialStore.getState().axisPose.z, 120);
assert.equal(fixtureInitialStore.getState().axisPose.a, 50);
assert.equal(fixtureInitialStore.getState().axisPose.c, 36000);
const kinematicsStore = createSimulationStore();
kinematicsStore.dispatch({ type: "ATTACH_KINEMATICS_RUNTIME", runtime });
@@ -74,6 +120,9 @@ assert.equal(kinematicsState.linuxCncBoundaryAdapter.interpreterRuntimeReady, fa
assert.equal(kinematicsState.linuxCncBoundaryReadiness.ready, true);
assert.equal(kinematicsState.linuxCncBoundaryReadiness.fullLinuxCncProgramExecutionReady, false);
assert.equal(kinematicsState.linuxCncBoundaryReadiness.missing.includes("interpreter/remap runtime"), true);
assert.equal(kinematicsState.fullExecutionBoundary.satisfied.includes("linuxcnc-kinematics-wasm"), true);
assert.equal(kinematicsState.fullExecutionBoundary.missing.includes("linuxcnc interpreter WASM runtime"), true);
assert.equal(kinematicsState.kinematicsExecutionContext, "direct");
assert.equal(kinematicsState.rtcpFrame.sourceMode, "source-derived-kinematics-wasm");
assert.equal(kinematicsState.rtcpFrame.semanticBoundary, "linuxcnc_kinematics_wasm_c_abi");
assert.equal(kinematicsState.rtcpFrame.readiness.linuxCncKinematicsReady, true);
@@ -86,27 +135,159 @@ assert.equal(kinematicsState.lastKinematicsResult.moduleId, "xyzac-trt");
assert.equal(kinematicsState.dro.tcpX, kinematicsState.rtcpFrame.tcpPose.x);
kinematicsStore.dispatch({ type: "TOGGLE_POWER" });
kinematicsStore.dispatch({ type: "HOME" });
kinematicsStore.dispatch({ type: "SET_MODE", mode: "auto" });
kinematicsStore.dispatch({ type: "STEP" });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.runState, "stepping");
assert.equal(kinematicsState.rtcpFrame.sourceMode, "source-derived-kinematics-wasm");
assert.equal(kinematicsState.rtcpFrame.readiness.fullLinuxCncProgramExecutionReady, false);
kinematicsStore.dispatch({ type: "ATTACH_INTERPRETER_RUNTIME", runtime: interpreterRuntime });
kinematicsStore.dispatch({
type: "LOAD_PROGRAM",
filename: "linuxcnc-canonical-demo.ngc",
content: [
"G90 G17",
"G0 X0 Y0 Z0",
"G1 X10 Y2 F100",
"G1 X12 Y4 A5 C7",
"M2",
].join("\n"),
});
kinematicsState = await waitForInterpreterExecution(kinematicsStore);
assert.equal(kinematicsState.linuxCncBoundaryAdapter.linuxCncInterpreterReady, true);
assert.equal(kinematicsState.linuxCncBoundaryReadiness.linuxCncInterpreterReady, true);
assert.equal(kinematicsState.programExecutionSourceMode, "linuxcnc-interpreter-wasm");
assert.equal(kinematicsState.programExecution.summary.motionEventCount >= 3, true);
assert.equal(kinematicsState.programExecutionTiming.motionCount >= 3, true);
assert.equal(kinematicsState.programExecutionTiming.sampleCount >= 3, true);
assert.equal(kinematicsState.programExecutionTiming.totalSeconds > 0, true);
assert.equal(kinematicsState.programExecutionTiming.semanticBoundary, "linuxcnc_tp_queue_runtime_timing_from_canonical_motion");
assert.equal(kinematicsState.programExecution.summary.plannerRuntimeReady, true);
assert.equal(kinematicsState.fullExecutionBoundary.plannerRuntimeReady, true);
assert.equal(kinematicsState.programExecution.summary.fullLinuxCncProgramExecutionReady, false);
assert.equal(kinematicsState.fullExecutionBoundary.readyForUiSimulation, true);
assert.equal(kinematicsState.fullExecutionBoundary.machineFileBackedRemapReady, false);
assert.equal(kinematicsState.fullExecutionBoundary.fullLinuxCncProgramExecutionReady, false);
assert.equal(
kinematicsState.fullExecutionBoundary.semanticBoundary,
"linuxcnc_interpreter_canonical_ready_planner_task_hal_blocked",
);
kinematicsStore.dispatch({ type: "STEP" });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.programExecutionMotionIndex, 1);
assert.equal(kinematicsState.activeLine, kinematicsState.programExecution.motion[1].line);
assert.equal(kinematicsState.programRuntimeFeedback.sourceMode, "linuxcnc-tp-runtime-sample");
assert.equal(kinematicsState.programRuntimeFeedback.semanticBoundary, "linuxcnc_tp_run_cycle_feedback_without_hardware");
assert.equal(kinematicsState.axisPose.x, kinematicsState.programRuntimeFeedback.axisPose.x);
assert.notEqual(kinematicsState.axisPose.x, kinematicsState.programExecution.motion[1].axes.x);
assert.equal(kinematicsState.programRuntimeFeedback.queueDepth >= 0, true);
assert.equal(kinematicsState.programElapsedSeconds > 0, true);
assert.equal(kinematicsState.programRemainingSeconds >= 0, true);
assert.equal(kinematicsState.feed.currentVelocity > 0, true);
kinematicsStore.dispatch({
type: "LOAD_PROGRAM",
filename: "linuxcnc-switchkins-rtcp-demo.ngc",
content: [
"G90 G17",
"M428",
"G0 X0 Y0 Z0 A0 C0",
"G1 X10 Y2 Z-1 A15 C30 F120",
"M429",
"G1 X0 Y0 Z0 A0 C0 F120",
"M2",
].join("\n"),
});
kinematicsState = await waitForInterpreterExecution(kinematicsStore);
assert.equal(kinematicsState.programExecutionSourceMode, "linuxcnc-interpreter-wasm");
assert.equal(kinematicsState.programExecution.summary.switchkinsEventCount, 2);
assert.equal(kinematicsState.programExecution.motion[0].switchkinsType, 1);
assert.equal(kinematicsState.kinsType, "tcp-xyzac");
assert.equal(kinematicsState.rtcpState, "on");
assert.equal(kinematicsState.rtcpFrame.kinematicsSwitchkinsType, 1);
kinematicsStore.dispatch({ type: "STEP" });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.programExecutionMotionIndex, 1);
assert.equal(kinematicsState.programRuntimeFeedback.semanticBoundary, "linuxcnc_tp_run_cycle_feedback_without_hardware");
assert.equal(kinematicsState.axisPose.a > 0 && kinematicsState.axisPose.a < 15, true);
assert.equal(kinematicsState.axisPose.c > 0 && kinematicsState.axisPose.c < 30, true);
assert.equal(kinematicsState.programRuntimeFeedback.distanceToGo > 0, true);
assert.equal(kinematicsState.kinsType, "tcp-xyzac");
assert.equal(kinematicsState.rtcpState, "on");
assert.equal(kinematicsState.rtcpFrame.kinematicsSwitchkinsType, 1);
const switchkinsStepSampleIndex = kinematicsState.programExecutionSampleIndex;
kinematicsStore.dispatch({ type: "RESUME" });
kinematicsStore.dispatch({ type: "RUN" });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.programExecutionSampleIndex > switchkinsStepSampleIndex, true);
assert.equal(kinematicsState.programExecutionMotionIndex, 1);
assert.equal(kinematicsState.programRuntimeFeedback.semanticBoundary, "linuxcnc_tp_run_cycle_feedback_without_hardware");
assert.equal(kinematicsState.programRuntimeFeedback.distanceToGo > 0, true);
assert.equal(kinematicsState.kinsType, "tcp-xyzac");
assert.equal(kinematicsState.rtcpState, "on");
assert.equal(kinematicsState.rtcpFrame.kinematicsSwitchkinsType, 1);
kinematicsStore.dispatch({ type: "PAUSE" });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.runState, "paused");
assert.equal(kinematicsState.machine.interpState, "paused");
assert.equal(kinematicsState.machine.taskPaused, true);
kinematicsStore.dispatch({ type: "RUN" });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.operatorMessage, "run blocked: resume paused program first");
assert.equal(kinematicsState.runState, "paused");
kinematicsStore.dispatch({ type: "RESUME" });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.runState, "running");
assert.equal(kinematicsState.machine.interpState, "reading");
assert.equal(kinematicsState.machine.taskPaused, false);
kinematicsStore.dispatch({ type: "STOP" });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.runState, "stopped");
assert.equal(kinematicsState.machine.interpState, "idle");
kinematicsStore.dispatch({ type: "SET_FRAME_SOURCE", sourceMode: "fixture-ui-only" });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.rtcpFrame.sourceMode, "fixture-ui-only");
assert.equal(kinematicsState.rtcpFrame.readiness.linuxCncKinematicsReady, false);
const store = createSimulationStore();
let state;
assert.equal(store.getState().availableProfiles.length, 2);
store.dispatch({ type: "SET_PROFILE", profileId: "xyzbc-trt" });
assert.equal(store.getState().machineProfile, "xyzbc-trt");
assert.equal(store.getState().profile.traj.coordinates, "XYZBC");
const xyzbcRuntime = await createLinuxCncKinematicsRuntime({ moduleId: "xyzbc-trt" });
store.dispatch({ type: "ATTACH_KINEMATICS_RUNTIME", runtime: xyzbcRuntime });
store.dispatch({ type: "SET_RTCP", enabled: true });
state = store.getState();
assert.equal(state.kinsType, "tcp-xyzbc");
assert.equal(state.rtcpFrame.kinematicsModuleId, "xyzbc-trt");
assert.equal(state.rtcpFrame.jointPose[3].axis, "B");
store.dispatch({ type: "SET_PROFILE", profileId: "xyzac-trt" });
store.dispatch({ type: "RUN" });
assert.equal(store.getState().activeLine, 501);
assert.equal(store.getState().operatorMessage, "run blocked: power or estop state");
assert.equal(store.getState().operatorMessage, "run blocked: machine must be on");
store.dispatch({ type: "TOGGLE_POWER" });
assert.equal(store.getState().machine.powerOn, true);
store.dispatch({ type: "HOME" });
assert.equal(store.getState().machine.allHomed, true);
store.dispatch({ type: "SET_MODE", mode: "auto" });
assert.equal(store.getState().linuxCncTaskPolicy.canRunAuto, true);
assert.equal(store.getState().linuxCncTaskPolicy.canExecuteMdi, false);
store.dispatch({ type: "SET_RTCP", enabled: true });
let state = store.getState();
state = store.getState();
assert.equal(state.rtcpState, "on");
assert.equal(state.kinsType, "tcp-xyzac");
assert.equal(state.rtcpFrame.readiness.linuxCncKinematicsReady, false);
@@ -120,24 +301,55 @@ assert.equal(state.activeLine, previousLine + 1);
assert.equal(state.runState, "stepping");
assert.notEqual(state.tcpPose.x, previousTcpX);
store.dispatch({ type: "STOP" });
store.dispatch({ type: "SET_MODE", mode: "manual" });
store.dispatch({ type: "JOG", axis: "x", direction: 1, increment: 2 });
state = store.getState();
assert.equal(state.machine.mode, "jog");
assert.equal(state.machine.mode, "manual");
assert.equal(state.runState, "jogging");
assert.equal(Math.round(state.axisPose.x), Math.round(state.rtcpFrame.axisPose.x));
store.dispatch({ type: "SET_MODE", mode: "mdi" });
store.dispatch({ type: "RUN_MDI", command: "G0 X1" });
state = store.getState();
assert.equal(state.machine.mode, "mdi");
assert.equal(state.machine.mdiCommand, "G0 X1");
assert.equal(state.runState, "mdi");
assert.equal(state.axisPose.x, 1);
assert.equal(state.programSource, "operator-mdi");
assert.equal(state.programLines[0], "G0 X1");
assert.equal(state.mdiHistory[0], "G0 X1");
const mdiRelativeBase = store.getState().axisPose;
store.dispatch({ type: "RUN_MDI", command: "G91 X2 Y-3 F1200 M3 S2400 M8" });
state = store.getState();
assert.equal(state.machine.mdiDistanceMode, "relative");
assert.equal(state.axisPose.x, mdiRelativeBase.x + 2);
assert.equal(state.axisPose.y, mdiRelativeBase.y - 3);
assert.equal(state.feed.feedRate, 1200);
assert.equal(state.spindle.enabled, true);
assert.equal(state.spindle.rpm, 2400);
assert.equal(state.coolant.flood, true);
store.dispatch({ type: "RUN_MDI", command: "M428" });
state = store.getState();
assert.equal(state.kinsType, "tcp-xyzac");
assert.equal(state.rtcpState, "on");
store.dispatch({ type: "RUN_MDI", command: "M429 M9 M5" });
state = store.getState();
assert.equal(state.kinsType, "identity");
assert.equal(state.rtcpState, "off");
assert.equal(state.coolant.flood, false);
assert.equal(state.coolant.mist, false);
assert.equal(state.spindle.enabled, false);
store.dispatch({
type: "LOAD_PROGRAM",
filename: "operator-demo.ngc",
content: "G0 X0 Y0\nG1 X10 F100\nM30\n",
});
state = store.getState();
state = await waitForInterpreterExecution(store);
assert.equal(state.activeProgram, "operator-demo.ngc");
assert.equal(state.programSource, "operator-file");
assert.equal(state.programStartLine, 1);
@@ -169,13 +381,15 @@ store.dispatch({ type: "ADJUST_SPINDLE_OVERRIDE", delta: 10 });
state = store.getState();
assert.equal(state.spindle.override, 110);
const floodBeforeToggle = store.getState().coolant.flood;
store.dispatch({ type: "TOGGLE_COOLANT", kind: "flood" });
state = store.getState();
assert.equal(state.coolant.flood, false);
assert.equal(state.coolant.flood, !floodBeforeToggle);
const mistBeforeToggle = store.getState().coolant.mist;
store.dispatch({ type: "TOGGLE_COOLANT", kind: "mist" });
state = store.getState();
assert.equal(state.coolant.mist, true);
assert.equal(state.coolant.mist, !mistBeforeToggle);
store.dispatch({ type: "SET_VIEW", view: "x" });
state = store.getState();
@@ -195,6 +409,7 @@ store.dispatch({ type: "TOGGLE_FULLSCREEN" });
state = store.getState();
assert.equal(state.preview.fullscreen, true);
store.dispatch({ type: "SET_MODE", mode: "manual" });
store.dispatch({ type: "HOME" });
state = store.getState();
assert.equal(state.axisPose.x, 43);
@@ -209,6 +424,8 @@ assert.equal(state.runState, "estopped");
store.dispatch({ type: "RESET" });
state = store.getState();
assert.equal(state.machine.estopActive, false);
assert.equal(state.operatorMessage, "machine reset complete");
assert.equal(state.machine.powerOn, false);
assert.equal(state.machine.taskState, "estop-reset");
assert.equal(state.operatorMessage, "estop reset; machine off");
console.log("rtcp_store_smoke=ok");