Add INI-driven XYZAC and XYZBC parametric machine design

This commit is contained in:
2026-09-17 19:16:48 +08:00
parent 9232f29027
commit e4600b34e3
31 changed files with 2359 additions and 29 deletions

View File

@@ -32,6 +32,7 @@ import { WasmXyzacDeviceIo } from "./wasm-xyzac-device-io.mjs";
import { WasmProgramRuntime } from "./wasm-program-runtime.mjs";
import { axisGate, xyzacGate, xyzacGateMatrix } from "../app/axis-contract.mjs";
import { machineDefinition } from "../workspace/machine-definitions.mjs";
import { readTrtGeometry, TRT_GEOMETRY_KEYS } from "../workspace/trt-geometry.mjs";
const PROTOCOL_SCHEMA = 1;
const CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1";
@@ -196,7 +197,7 @@ export class SimSession {
// thousands of immutable segments on unrelated control responses makes a
// loaded program delay abort/power/home commands behind structured-clone
// work. Explicit preview transactions still publish the full replacement.
const publishXyzacToolpath = ["PROGRAM_LOAD", "PROGRAM_RESTART", "RECOVER", "SNAPSHOT_REQUEST", "VISUAL_CLEAR"].includes(input.kind);
const publishXyzacToolpath = ["LOAD_WORKSPACE", "PROGRAM_LOAD", "PROGRAM_RESTART", "RECOVER", "SNAPSHOT_REQUEST", "VISUAL_CLEAR"].includes(input.kind);
const panelSnapshot = motionDelta || traceOnlyStep ? null : this.panelSnapshot({
task,
program,
@@ -600,6 +601,9 @@ export class SimSession {
const program = this.xyzacProgram.snapshot();
const io = this.xyzacIo?.canonical?.() ?? {};
const wasmMotion = this.wasmMotion.read();
if (input.kind === "LOAD_WORKSPACE" && (io.ready || ["running", "paused"].includes(program.program_status) || Number(wasmMotion.state?.queue_depth) > 0)) {
return { enabled: false, reason: "trt_configuration_requires_power_off_idle" };
}
if (["HOME", "SET_MODE"].includes(input.kind) && Number(wasmMotion.state?.queue_depth) > 0) return { enabled: false, reason: "xyzac_motion_active" };
if (input.kind === "JOG_START" && !this.wasmHomeLimit.read().state.homed) return { enabled: false, reason: "xyzac_not_all_homed" };
if (input.kind === "PROGRAM_LOAD" && wasmMotion.status === "verified" && Number(wasmMotion.state.queue_depth ?? 0) > 0) {
@@ -830,6 +834,12 @@ export class SimSession {
return { ...compact, checkpoint_hash: fnv1a64(compact) };
}
validateRecoveryGeometry(state) {
if (!this.xyzacKinematics) return;
const current = this.xyzacKinematics.snapshot();
if (!state?.kinematics || state.kinematics.coordinates !== current.coordinates || TRT_GEOMETRY_KEYS.some((key) => Number(state.kinematics.offsets?.[key] ?? 0) !== current.offsets[key])) throw new Error("trt_recovery_geometry_mismatch");
}
restoreCompactPowerOff(record, transportSeq) {
if (!record || record.schema !== "xyzac-power-off-checkpoint.v1" || record.context_id !== this.contextId || !record.state) return { status: "blocked", code: "runtime_checkpoint_schema", reason: "power_off_checkpoint_schema_invalid" };
if (fnv1a64(record.state) !== record.state_hash || fnv1a64(Object.fromEntries(Object.entries(record).filter(([key]) => key !== "checkpoint_hash"))) !== record.checkpoint_hash) return { status: "blocked", code: "runtime_checkpoint_hash", reason: "power_off_checkpoint_hash_mismatch" };
@@ -839,6 +849,7 @@ export class SimSession {
const liveLedger = this.ledger;
const liveOperations = [...this.operations.values()];
try {
this.validateRecoveryGeometry(state);
if (!this.xyzacProgram || !state.program?.source_text) throw new Error("power_off_program_missing");
const loaded = this.xyzacProgram.load(state.program.source_text, state.program.path ?? null);
if (loaded.status !== "verified") throw new Error(loaded.code);
@@ -941,6 +952,7 @@ export class SimSession {
importTrace(trace) {
const validated = validateTrace(trace, { contextId: this.contextId });
this.validateRecoveryGeometry(trace.state);
this.timeline = validated.timeline;
this.created = Boolean(trace.state?.created);
if (trace.state?.ledger?.entries) this.ledger = new OperationLedger(trace.state.ledger.entries);
@@ -977,6 +989,7 @@ export class SimSession {
restoreCheckpoint(checkpoint, events = null, reconcile = undefined) {
try {
validateCheckpoint(checkpoint, { contextId: this.contextId });
this.validateRecoveryGeometry(checkpoint.state);
} catch (error) {
return { schema: "simulation-recovery.v1", status: "blocked", context_id: this.contextId, checkpoint_hash: checkpoint?.checkpoint_hash ?? null, blocked: [{ reason: String(error?.message ?? error) }] };
}
@@ -1594,7 +1607,15 @@ export class SimSession {
const rootPath = input.payload?.root_path;
if (!text(rootPath) || !rootPath.startsWith("/") || rootPath.includes("..")) return finish(this.error(input, "workspace_path", "invalid_workspace_path"), "failed");
let startupLoaded = false;
const configuration = input.payload?.configuration ?? null;
let configuration = input.payload?.configuration ?? null;
if (machineDefinition(configuration?.entrypoint) || machineDefinition(configuration?.kinematics?.type)) {
try {
const geometry = readTrtGeometry(configuration);
configuration = { ...configuration, kinematics: { ...configuration.kinematics, offsets: geometry.offsets } };
} catch (error) {
return finish(this.error(input, "trt_geometry", error.message), "failed");
}
}
if (configuration) {
this.machineDefinition = machineDefinition(configuration.entrypoint) ?? machineDefinition(configuration.kinematics?.type) ?? this.machineDefinition;
this.workspaceFiles = { ...(configuration.files ?? configuration.workspace ?? configuration.workspace_files ?? {}) };
@@ -1669,6 +1690,9 @@ export class SimSession {
? new WasmLinuxcncTp(this.core, { cycleTime: this.xyzacMotionCycleTime, velocityLimit: this.xyzacRapidVelocity, accelerationLimit: Number(configuration.kinematics?.max_acceleration ?? 300), limits: configuration.home_limits ?? {} })
: new WasmMotionQueue(this.core, { cycleTime: this.xyzacMotionCycleTime, velocityLimit: 1e9 });
this.xyzacProgram = new WasmProgramRuntime(this.core);
this.previewState = null;
this.pendingXyzacMotion = null;
this.clearLivePlot();
this.homingHistory = [];
this.xyzacProgramKinematicsMode = XYZAC_MODES.identity;
if (typeof this.wasmMotion.setKinematics === "function") this.wasmMotion.setKinematics(this.xyzacKinematics);