Complete 3axis tutorial native and Web/WASM parity tools

This commit is contained in:
2026-08-24 14:01:30 +08:00
parent 938654b19d
commit 7e90142b63
147 changed files with 17639 additions and 557 deletions

View File

@@ -11,6 +11,7 @@ export const AXIS_COMMANDS = Object.freeze({
jogStop: "JOG_STOP", home: "HOME", override: "SET_OVERRIDE", estop: "ESTOP", recover: "IO_RECOVER",
spindle: "SPINDLE_COMMAND", coolant: "COOLANT_COMMAND", toolPrepare: "TOOL_PREPARE",
toolChange: "TOOL_CHANGE", probe: "PROBE_CONFIG",
reset: "RESET",
});
export const AXIS_BOUNDARIES = Object.freeze({
@@ -35,8 +36,17 @@ export function normalizeAxisState(input = {}) {
const programStatus = string(program.program_status ?? input.program_status, "IDLE").toLowerCase();
const queue = number(task.queued_mdi_commands ?? input.queued_mdi_commands);
const queueLimit = Math.max(1, number(task.max_queued_mdi_commands ?? input.max_queued_mdi_commands, 10));
const homed = Array.isArray(motion.homed) ? motion.homed : (Array.isArray(input.homed) ? input.homed : []);
const homed = Array.isArray(motion.homed_axes)
? motion.homed_axes
: Array.isArray(motion.homed)
? motion.homed
: Array.isArray(input.homed)
? input.homed
: motion.homed === true
? [true, true, true]
: [];
const limits = input.on_any_limit ?? motion.on_any_limit ?? motion.limit ?? false;
const fault = rawTaskState === "FAULT" || Boolean(motion.fault || devices.fault || input.fault);
const tool = devices.tool ?? input.tool ?? {};
const toolNumber = number(tool.current ?? input.tool_in_spindle);
const taskFile = Boolean(input.taskfile ?? program.path ?? program.source_hash ?? program.loaded ?? (program.program_status && String(program.program_status).toLowerCase() !== "idle"));
@@ -44,7 +54,7 @@ export function normalizeAxisState(input = {}) {
return Object.freeze({
taskState, interpState, mode, motionMode, programStatus, queue, queueLimit,
taskFile, hasEditor, onAnyLimit: Boolean(limits), toolLoaded: toolNumber > 0,
allHomed: homed.length > 0 && homed.every(Boolean), homed,
allHomed: homed.length > 0 && homed.every(Boolean), homed, fault,
});
}
@@ -58,10 +68,13 @@ export function axisGate(action, input = {}) {
const trajectoryTouchOffAllowed = state.motionMode !== "FREE" || input.kinematicsIdentity === true;
let enabled = false;
let reason = "axis_gate_unknown_action";
if (state.fault && !["estop", "recover", "overrideLimits"].includes(action)) return Object.freeze({ action, enabled: false, reason: "axis_fault_active", taskState: state.taskState, interpState: state.interpState });
switch (action) {
case "estop": enabled = true; reason = estop ? "axis_estop_reset_available" : "axis_estop_available"; break;
case "recover": enabled = true; reason = "axis_recover_available"; break;
case "power": enabled = state.taskState !== "ESTOP"; reason = enabled ? "axis_power_available" : "axis_estop_active"; break;
case "override": enabled = machineOn && !estop; reason = enabled ? "axis_override_available" : estop ? "axis_estop_active" : "axis_machine_off"; break;
case "reset": enabled = machineOn && !running && !state.onAnyLimit; reason = enabled ? "axis_reset_available" : state.onAnyLimit ? "axis_limit_active" : "axis_reset_state_gate"; break;
case "open": enabled = idle; reason = enabled ? "axis_interp_idle" : "axis_interp_running"; break;
case "load": enabled = machineOn && idle; reason = enabled ? "axis_program_load_available" : !machineOn ? "axis_machine_off" : "axis_interp_running"; break;
case "reload": enabled = idle && state.taskFile; reason = !idle ? "axis_interp_running" : state.taskFile ? "axis_reload_available" : "axis_taskfile_missing"; break;
@@ -74,9 +87,9 @@ export function axisGate(action, input = {}) {
case "stop": case "abort": enabled = machineOn && running; reason = enabled ? "axis_stop_available" : "axis_stop_state_gate"; break;
case "restart": enabled = machineOn && idle && state.taskFile; reason = enabled ? "axis_restart_available" : !machineOn ? "axis_machine_off" : !state.taskFile ? "axis_taskfile_missing" : "axis_restart_state_gate"; break;
case "mdi": enabled = machineOn && state.queue < state.queueLimit && (idle || state.mode === "MDI"); reason = enabled ? "axis_mdi_available" : estop ? "axis_estop_active" : !machineOn ? "axis_machine_off" : state.queue >= state.queueLimit ? "axis_mdi_queue_full" : "axis_interp_running"; break;
case "jogStart": case "jogStop": case "home": case "spindle": case "coolant": enabled = manualOk; reason = enabled ? "axis_manual_available" : estop ? "axis_estop_active" : !machineOn ? "axis_machine_off" : "axis_manual_state_gate"; break;
case "touchOff": enabled = manualOk && trajectoryTouchOffAllowed; reason = !manualOk ? "axis_manual_state_gate" : !trajectoryTouchOffAllowed ? "axis_joint_mode_required" : "axis_touch_off_available"; break;
case "toolTouchOff": enabled = manualOk && trajectoryTouchOffAllowed && state.toolLoaded; reason = !state.toolLoaded ? "axis_tool_missing" : !trajectoryTouchOffAllowed ? "axis_joint_mode_required" : !manualOk ? "axis_manual_state_gate" : "axis_tool_touch_off_available"; break;
case "jogStart": case "jogStop": case "home": case "spindle": case "coolant": enabled = manualOk && state.mode === "MANUAL"; reason = enabled ? "axis_manual_available" : estop ? "axis_estop_active" : !machineOn ? "axis_machine_off" : state.mode !== "MANUAL" ? "axis_manual_mode_required" : "axis_manual_state_gate"; break;
case "touchOff": enabled = manualOk && state.mode === "MANUAL" && trajectoryTouchOffAllowed; reason = !manualOk ? "axis_manual_state_gate" : state.mode !== "MANUAL" ? "axis_manual_mode_required" : !trajectoryTouchOffAllowed ? "axis_joint_mode_required" : "axis_touch_off_available"; break;
case "toolTouchOff": enabled = manualOk && state.mode === "MANUAL" && trajectoryTouchOffAllowed && state.toolLoaded; reason = !state.toolLoaded ? "axis_tool_missing" : state.mode !== "MANUAL" ? "axis_manual_mode_required" : !trajectoryTouchOffAllowed ? "axis_joint_mode_required" : !manualOk ? "axis_manual_state_gate" : "axis_tool_touch_off_available"; break;
case "overrideLimits": enabled = state.onAnyLimit; reason = enabled ? "axis_limit_override_available" : "axis_no_active_limit"; break;
default: break;
}

View File

@@ -27,7 +27,8 @@ export class AxisOperationController {
async execute(kind, payload = {}, { state = {}, action = COMMAND_ACTIONS[kind] ?? null, gate = true } = {}) {
const operationId = this.nextOperationId(kind);
const gateResult = action ? axisGate(action, state) : { action: null, enabled: true, reason: "axis_unrestricted_internal_command" };
if (gate && !gateResult.enabled) {
const authoritativeGate = action && state?.gates?.[action];
if (gate && !gateResult.enabled && !authoritativeGate) {
this.trace.push(Object.freeze({ operation_id: operationId, kind, action, phase: "blocked", reason: gateResult.reason }));
throw new AxisGateError(gateResult);
}