Support XYZAC and XYZBC in shared web machine UI
This commit is contained in:
@@ -31,6 +31,7 @@ import { WasmXyzacIo } from "./wasm-xyzac-io.mjs";
|
||||
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";
|
||||
|
||||
const PROTOCOL_SCHEMA = 1;
|
||||
const CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1";
|
||||
@@ -135,6 +136,7 @@ export class SimSession {
|
||||
this.xyzacIo = null;
|
||||
this.xyzacDeviceIo = null;
|
||||
this.xyzacProgram = null;
|
||||
this.machineDefinition = null;
|
||||
this.homingHistory = [];
|
||||
this.xyzacProgramKinematicsMode = XYZAC_MODES.identity;
|
||||
this.xyzacRapidVelocity = 35;
|
||||
@@ -293,7 +295,7 @@ export class SimSession {
|
||||
source: wasmMotion.state.source ?? "wasm_motion_queue",
|
||||
tick: wasmMotion.status === "verified" ? wasmMotion.state.tick : this.tick,
|
||||
axes: { x: position.x, y: position.y, z: position.z },
|
||||
joints: kinematicsJoints ?? (home.status === "verified" && Array.isArray(home.state.joints) ? [...home.state.joints] : [position.x, position.y, position.z, position.a ?? 0, position.c ?? 0]),
|
||||
joints: kinematicsJoints ?? (home.status === "verified" && Array.isArray(home.state.joints) ? [...home.state.joints] : [position.x, position.y, position.z, position[this.machineDefinition?.coordinates === "XYZBC" ? "b" : "a"] ?? 0, position.c ?? 0]),
|
||||
velocity: wasmMotion.status === "verified" ? wasmMotion.state.velocity : 0,
|
||||
velocity_mm_min: Number(wasmMotion.state?.velocity_mm_min ?? 0),
|
||||
feed_command_mm_min: Number(sourceProgram?.feed ?? 0),
|
||||
@@ -310,7 +312,7 @@ export class SimSession {
|
||||
max_velocity: Number(wasmMotion.state?.velocity_limit ?? 0),
|
||||
homed: Boolean(home.state?.homed),
|
||||
homed_axes: Array.from({ length: 5 }, (_, index) => Boolean(Number(home.state?.homed_mask ?? 0) & (1 << index))),
|
||||
limits: { x: Boolean(home.state?.limit_error), y: Boolean(home.state?.limit_error), z: Boolean(home.state?.limit_error), a: Boolean(home.state?.limit_error), c: Boolean(home.state?.limit_error) },
|
||||
limits: { x: Boolean(home.state?.limit_error), y: Boolean(home.state?.limit_error), z: Boolean(home.state?.limit_error), [this.machineDefinition?.coordinates === "XYZBC" ? "b" : "a"]: Boolean(home.state?.limit_error), c: Boolean(home.state?.limit_error) },
|
||||
on_any_limit: Boolean(home.state?.limit_error),
|
||||
following_error: 0,
|
||||
motion_mode: this.xyzacProgram ? this.wasmHomeLimit.mode : (home.state?.homed ? "TELEOP" : "FREE"),
|
||||
@@ -356,7 +358,9 @@ export class SimSession {
|
||||
if (requirement.status !== "verified") return requirement;
|
||||
if (!requirement.required) return { status: "verified", count: 0 };
|
||||
const basePath = String(programPath ?? "").split("/").slice(0, -1).join("/");
|
||||
const rows = [["remap_subs/xyzac_switchkins_sub.ngc", "xyzac_switchkins_sub"], ["remap_subs/helix_ac.ngc", "helix_ac"]];
|
||||
const coordinates = this.machineDefinition?.coordinates ?? "XYZAC";
|
||||
const stem = coordinates.toLowerCase();
|
||||
const rows = [[`remap_subs/${stem}_switchkins_sub.ngc`, `${stem}_switchkins_sub`], [`remap_subs/helix_${stem.slice(3)}.ngc`, `helix_${stem.slice(3)}`]];
|
||||
for (const [suffix, name] of rows) {
|
||||
const path = `${basePath.replace(/\/demos$/, "")}/${suffix}`;
|
||||
const source = this.workspaceFiles[path];
|
||||
@@ -402,7 +406,7 @@ export class SimSession {
|
||||
? { schema: "wasm_authority_boundary.v1", source: "linuxcnc-wasm-worker", state_hash: fnv1a64({ motion: wasmMotion, kinematics: this.xyzacKinematics?.snapshot() ?? null, task }) }
|
||||
: this.authoritative.export();
|
||||
const axis = axisState({ task, program: program ?? {}, motion, devices, io: this.xyzacIo ? this.xyzacIo.export() : {}, device_io: deviceIo });
|
||||
const xyzac = this.xyzacKinematics ? { coordinates: "XYZAC", joint_count: 5, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true, active_type: this.xyzacKinematics.snapshot().active_type } : null;
|
||||
const xyzac = this.xyzacKinematics ? { coordinates: this.machineDefinition?.coordinates ?? "XYZAC", joint_count: 5, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true, active_type: this.xyzacKinematics.snapshot().active_type } : null;
|
||||
const gateInput = { task, program: program ?? {}, motion, devices, io: this.xyzacIo ? this.xyzacIo.export() : {}, device_io: deviceIo, xyzac, taskfile: Boolean(this.xyzacProgram ? this.xyzacProgram.snapshot().status !== 0 : this.program.loaded), kinematicsIdentity: this.motionAuthority === "wasm" };
|
||||
const gateActions = ["load", "run", "runLine", "step", "pause", "resume", "abort", "restart", "reload", "edit", "mdi", "jogStart", "jogStop", "home", "estop", "recover", "override", "overrideLimits", "reset", "spindle", "coolant", "touchOff", "toolTouchOff"];
|
||||
const ui = axisSourceUi({ task_state: axis.source_task_state, interp_state: axis.source_interp_state,
|
||||
@@ -459,7 +463,7 @@ export class SimSession {
|
||||
: { x: wasmPosition.x, y: wasmPosition.y, z: wasmPosition.z };
|
||||
const inverseJoints = this.xyzacKinematics?.snapshot?.()?.joints;
|
||||
const joints = this.motionAuthority === "wasm" && wasmMotion.status === "verified"
|
||||
? (Array.isArray(wasmMotion.state.joint_commanded) && wasmMotion.state.joint_commanded.length === 5 ? [...wasmMotion.state.joint_commanded] : inverseJoints?.length === 5 ? [...inverseJoints] : [position.x, position.y, position.z, position.a ?? 0, position.c ?? 0])
|
||||
? (Array.isArray(wasmMotion.state.joint_commanded) && wasmMotion.state.joint_commanded.length === 5 ? [...wasmMotion.state.joint_commanded] : inverseJoints?.length === 5 ? [...inverseJoints] : [position.x, position.y, position.z, position[this.machineDefinition?.coordinates === "XYZBC" ? "b" : "a"] ?? 0, position.c ?? 0])
|
||||
: authoritative.kinematics.joints;
|
||||
const preview = this.previewState?.status === "verified" ? this.previewState : null;
|
||||
// Keep the Vismach model identity in the same authoritative visual
|
||||
@@ -467,16 +471,17 @@ export class SimSession {
|
||||
// hash comes from the exact resource resolved during WASM startup, so a
|
||||
// renderer can audit that its geometry matches the loaded LinuxCNC case.
|
||||
const modelResource = this.configurationState?.exact_resources?.find?.((row) => row.role === "vismach_source");
|
||||
const modelSourcePath = "linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py";
|
||||
const machineDefinition = this.machineDefinition;
|
||||
const modelSourcePath = this.machineDefinition?.source_path ?? "linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py";
|
||||
const modelSourceText = this.workspaceFiles?.[modelSourcePath];
|
||||
const machineModel = this.xyzacProgram ? {
|
||||
schema: "linuxcnc-vismach-model.v1",
|
||||
id: "xyzac-trt",
|
||||
id: machineDefinition?.id ?? "xyzac-trt",
|
||||
source: "linuxcnc-wasm-worker",
|
||||
source_path: modelResource?.path ?? modelSourcePath,
|
||||
source_hash: modelResource?.data_hash ?? this.visualModelSourceHash ?? (typeof modelSourceText === "string" ? fnv1a64(modelSourceText) : null),
|
||||
coordinate_system: "XYZAC",
|
||||
joints: ["table-x", "saddle-y", "spindle-z", "tilt-a", "rotate-c"],
|
||||
coordinate_system: machineDefinition?.coordinates ?? "XYZAC",
|
||||
joints: machineDefinition?.coordinates ? ["table-x", "saddle-y", "spindle-z", `tilt-${machineDefinition.coordinates[3].toLowerCase()}`, "rotate-c"] : ["table-x", "saddle-y", "spindle-z", "tilt-a", "rotate-c"],
|
||||
components: ["base", "column", "head", "spindle", "tool", "table", "saddle", "zslide"],
|
||||
} : null;
|
||||
const visual = {
|
||||
@@ -498,10 +503,11 @@ export class SimSession {
|
||||
coordinate_offset: program?.coordinate_context?.offsets?.[(program?.coordinate_context?.system ?? 1) - 1]?.slice(0, 3) ?? null,
|
||||
...(machineModel ? { machine_model: machineModel } : {}),
|
||||
};
|
||||
const axisCoordinates = [position.x, position.y, position.z, position.a ?? 0, position.c ?? 0];
|
||||
const coordinateAxes = machineDefinition?.axes ?? ["x", "y", "z", "a", "c"];
|
||||
const axisCoordinates = coordinateAxes.map((axis) => position[axis] ?? 0);
|
||||
const coordinateContext = program?.coordinate_context;
|
||||
const activeOffsets = coordinateContext?.offsets?.[coordinateContext.system - 1] ?? Array(9).fill(0);
|
||||
const displayedOffsets = [0, 1, 2, 3, 5].map((index) => activeOffsets[index]);
|
||||
const displayedOffsets = coordinateAxes.map((axis) => activeOffsets[["x", "y", "z", "a", "b", "c", "u", "v", "w"].indexOf(axis)] ?? 0);
|
||||
const unsigned = {
|
||||
schema: "axis-panel-snapshot.v1",
|
||||
context_id: this.contextId,
|
||||
@@ -532,7 +538,7 @@ export class SimSession {
|
||||
task_authority: this.task.authority(),
|
||||
...(this.xyzacKinematics ? { kinematics: this.xyzacKinematics.snapshot() } : {}),
|
||||
...(includeStableRuntime && this.xyzacIo ? { io: this.xyzacIo.export() } : {}),
|
||||
...(xyzac ? { xyzac } : {}),
|
||||
...(xyzac ? { xyzac: { ...xyzac, coordinates: machineDefinition?.coordinates ?? xyzac.coordinates } } : {}),
|
||||
};
|
||||
return { ...unsigned, state_hash: fnv1a64(unsigned) };
|
||||
}
|
||||
@@ -572,7 +578,7 @@ export class SimSession {
|
||||
const type = Number(input.payload?.type);
|
||||
const action = input.kind === "VISUAL_CLEAR" ? "vismachClear" : type === 0 ? "switchkinsIdentity" : type === 1 ? "switchkinsXyzac" : type === 2 ? "switchkinsUserk" : null;
|
||||
if (!action) return { action: null, enabled: false, reason: "xyzac_kinematics_type_invalid" };
|
||||
return xyzacGate(action, { task, program, motion, devices, xyzac: this.xyzacKinematics ? { coordinates: "XYZAC", joint_count: 5, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true } : null });
|
||||
return xyzacGate(action, { task, program, motion, devices, xyzac: this.xyzacKinematics ? { coordinates: this.machineDefinition?.coordinates ?? "XYZAC", joint_count: 5, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true } : null });
|
||||
}
|
||||
const action = PANEL_GATE_ACTIONS[input.kind];
|
||||
if (!action) return { action: null, enabled: true, reason: "axis_internal_command" };
|
||||
@@ -675,7 +681,8 @@ export class SimSession {
|
||||
if (!this.xyzacProgram) return { status: "error", code: "xyzac_program_unavailable" };
|
||||
const payload = input.payload ?? {};
|
||||
const axis = String(payload.axis ?? "").trim().toUpperCase();
|
||||
if (!["X", "Y", "Z", "A", "C"].includes(axis)) return { status: "error", code: "touch_off_axis_invalid" };
|
||||
const coordinateAxes = this.machineDefinition?.axes ?? ["x", "y", "z", "a", "c"];
|
||||
if (!coordinateAxes.includes(axis.toLowerCase())) return { status: "error", code: "touch_off_axis_invalid" };
|
||||
const value = Number(payload.value);
|
||||
if (!Number.isFinite(value)) return { status: "error", code: "touch_off_value_invalid" };
|
||||
const selectedSystem = String(payload.system ?? "P1 G54").trim().toUpperCase();
|
||||
@@ -1586,8 +1593,9 @@ export class SimSession {
|
||||
let startupLoaded = false;
|
||||
const configuration = input.payload?.configuration ?? null;
|
||||
if (configuration) {
|
||||
this.machineDefinition = machineDefinition(configuration.entrypoint) ?? machineDefinition(configuration.kinematics?.type) ?? this.machineDefinition;
|
||||
this.workspaceFiles = { ...(configuration.files ?? configuration.workspace ?? configuration.workspace_files ?? {}) };
|
||||
const visualSourcePath = "linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py";
|
||||
const visualSourcePath = (this.machineDefinition ?? machineDefinition(configuration.entrypoint))?.source_path ?? "linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py";
|
||||
this.visualModelSourceHash = typeof this.workspaceFiles[visualSourcePath] === "string" ? fnv1a64(this.workspaceFiles[visualSourcePath]) : null;
|
||||
const startupWorkspace = configuration.workspace ?? configuration.files ?? configuration.workspace_files;
|
||||
if (startupWorkspace) {
|
||||
@@ -1633,10 +1641,12 @@ export class SimSession {
|
||||
};
|
||||
}
|
||||
}
|
||||
this.motionAuthority = configuration?.entrypoint?.includes("3axis-tutorial") || configuration?.kinematics?.type === "xyzac-trt" ? "wasm" : "js";
|
||||
if (configuration?.kinematics?.type === "xyzac-trt") {
|
||||
const configuredMachine = this.machineDefinition ?? machineDefinition(configuration?.kinematics?.type);
|
||||
const isTrt = configuredMachine?.runtime === "trt" || configuration?.kinematics?.type === "xyzac-trt" || configuration?.kinematics?.type === "xyzbc-trt";
|
||||
this.motionAuthority = configuration?.entrypoint?.includes("3axis-tutorial") || isTrt ? "wasm" : "js";
|
||||
if (isTrt) {
|
||||
this.xyzacKinematics?.destroy();
|
||||
this.xyzacKinematics = new WasmXyzacKinematics(this.core, { ...configuration.kinematics.offsets, limits: configuration.home_limits });
|
||||
this.xyzacKinematics = new WasmXyzacKinematics(this.core, { ...configuration.kinematics.offsets, coordinates: configuredMachine?.coordinates ?? configuration.kinematics.coordinates ?? "XYZAC", limits: configuration.home_limits });
|
||||
this.xyzacIo?.destroy();
|
||||
this.xyzacIo = new WasmXyzacIo(this.core, { spindleCount: 2, toolCapacity: 1001 });
|
||||
const initialPower = this.xyzacIo.command("POWER", { active: false });
|
||||
@@ -1652,7 +1662,7 @@ export class SimSession {
|
||||
// The selected XYZAC TRT case always uses the upstream LinuxCNC TP;
|
||||
// a caller must not silently downgrade it to the compatibility
|
||||
// motion queue by omitting the planner option.
|
||||
this.wasmMotion = configuration.kinematics.type === "xyzac-trt"
|
||||
this.wasmMotion = isTrt
|
||||
? 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);
|
||||
@@ -1662,9 +1672,10 @@ export class SimSession {
|
||||
this.xyzacRapidVelocity = Number(configuration.kinematics?.max_linear_velocity ?? 35);
|
||||
const linearLimit = Number(configuration.kinematics?.axis_linear_velocity_limit ?? 20);
|
||||
const angularLimit = Number(configuration.kinematics?.axis_angular_velocity_limit ?? 30);
|
||||
this.xyzacAxisVelocityLimits = { x: linearLimit, y: linearLimit, z: linearLimit, a: angularLimit, c: angularLimit };
|
||||
const rotaryAxis = (configuredMachine?.coordinates ?? "XYZAC")[3].toLowerCase();
|
||||
this.xyzacAxisVelocityLimits = { x: linearLimit, y: linearLimit, z: linearLimit, [rotaryAxis]: angularLimit, c: angularLimit };
|
||||
const accelerationLimit = Number(configuration.kinematics?.max_acceleration ?? 300);
|
||||
this.xyzacAxisAccelerationLimits = { x: accelerationLimit, y: accelerationLimit, z: accelerationLimit, a: accelerationLimit, c: accelerationLimit };
|
||||
this.xyzacAxisAccelerationLimits = { x: accelerationLimit, y: accelerationLimit, z: accelerationLimit, [rotaryAxis]: accelerationLimit, c: accelerationLimit };
|
||||
this.wasmMotion.setDynamics(Number(configuration.kinematics?.max_acceleration ?? 300), Number(configuration.kinematics?.max_jerk ?? 1000000000));
|
||||
this.xyzacProgram.setRapidVelocity(this.xyzacRapidVelocity);
|
||||
}
|
||||
@@ -1696,7 +1707,7 @@ export class SimSession {
|
||||
};
|
||||
}
|
||||
const configuredKinematics = input.payload?.kinematics ?? configuration?.kinematics?.type ?? null;
|
||||
if (configuredKinematics && !(this.motionAuthority === "wasm" && configuredKinematics === "xyzac-trt")) {
|
||||
if (configuredKinematics && !(this.motionAuthority === "wasm" && isTrt)) {
|
||||
const kinematics = this.authoritative.setKinematics(configuredKinematics);
|
||||
if (kinematics.status === "blocked") return finish(this.envelope("ERROR", input, { status: "blocked", code: kinematics.code, kinematics: this.authoritative.export() }), "failed");
|
||||
}
|
||||
@@ -1858,9 +1869,10 @@ export class SimSession {
|
||||
const currentState = this.wasmMotion.read().state;
|
||||
const current = currentState.position;
|
||||
this.xyzacProgramKinematicsMode = { 428: XYZAC_MODES.xyzac, 429: XYZAC_MODES.identity, 430: XYZAC_MODES.userk }[pendingControl];
|
||||
const rotaryAxis = this.machineDefinition?.coordinates === "XYZBC" ? "b" : "a";
|
||||
const joints = Array.isArray(currentState.joint_commanded) && currentState.joint_commanded.length === 5
|
||||
? currentState.joint_commanded
|
||||
: [current.x, current.y, current.z, current.a ?? 0, current.c ?? 0];
|
||||
: [current.x, current.y, current.z, current[rotaryAxis] ?? 0, current.c ?? 0];
|
||||
const selected = typeof this.xyzacKinematics.selectPhysical === "function"
|
||||
? this.xyzacKinematics.selectPhysical(this.xyzacProgramKinematicsMode, joints)
|
||||
: this.xyzacKinematics.select(this.xyzacProgramKinematicsMode, joints);
|
||||
@@ -2602,7 +2614,8 @@ export class SimSession {
|
||||
}
|
||||
if (input.kind === "FOLLOWING_ERROR") return finish(this.error(input, "motion_following_error", "wasm_following_error_unavailable"), "failed");
|
||||
const axis = String(input.payload?.axis ?? "").toLowerCase();
|
||||
if (!["x", "y", "z", "a", "c"].includes(axis)) return finish(this.error(input, "motion_axis", "motion_axis_invalid"), "failed");
|
||||
const availableAxes = this.machineDefinition?.axes ?? ["x", "y", "z", "a", "c"];
|
||||
if (!availableAxes.includes(axis)) return finish(this.error(input, "motion_axis", "motion_axis_invalid"), "failed");
|
||||
const direction = input.payload?.direction === -1 ? -1 : input.payload?.direction === 1 ? 1 : null;
|
||||
if (direction === null) return finish(this.error(input, "motion_jog", "motion_jog_range"), "failed");
|
||||
if (this.xyzacProgram) this.wasmHomeLimit.setMode("TELEOP");
|
||||
@@ -2618,7 +2631,7 @@ export class SimSession {
|
||||
// sample as a normal queued line and retain the same JOG_STOP abort
|
||||
// path. This compatibility branch is never used by XYZAC TRT.
|
||||
const jog = typeof this.wasmMotion.jog === "function"
|
||||
? this.wasmMotion.jog({ x: 0, y: 1, z: 2, a: 3, c: 5 }[axis], direction, distance, input.seq)
|
||||
? this.wasmMotion.jog({ x: 0, y: 1, z: 2, a: 3, b: 4, c: 5 }[axis], direction, distance, input.seq)
|
||||
: (() => {
|
||||
const current = this.wasmMotion.read();
|
||||
const start = current.state?.position ?? {};
|
||||
|
||||
Reference in New Issue
Block a user