// UI predicates from axis.tcl::update_state, independent of task acceptance. export function axisSourceUi({ task_state, interp_state, taskfile = false, queued_mdi_commands = 0, max_queued_mdi_commands = 10, motion_mode = "FREE", kinematics_type = "BOTH", tool = 0, spindle_direction = 0, on_any_limit = false, paused = false } = {}) { const on = task_state === "STATE_ON"; const idle = interp_state === "INTERP_IDLE"; const manual = on && idle; const touch = manual && (motion_mode !== "FREE" || kinematics_type === "IDENTITY"); const predicates = { estop: true, power: task_state !== "STATE_ESTOP", load: idle, reload: idle && Boolean(taskfile), run: manual, step: on && Boolean(taskfile), pause: on && ["INTERP_READING", "INTERP_WAITING"].includes(interp_state), resume: on && interp_state === "INTERP_PAUSED", pauseResume: on && !idle, abort: on && !idle, manual, home: manual, jog: manual, spindle: manual, coolant: manual, mdi: on && queued_mdi_commands < max_queued_mdi_commands, touchOff: touch, toolTouchOff: touch && tool > 0, spindleAdjust: manual && spindle_direction !== 0, overrideLimits: Boolean(on_any_limit), override: true, jogScale: true, maxVelocity: true, spindleOverride: true, }; return { gates: Object.fromEntries(Object.entries(predicates).map(([action, enabled]) => [action, { action, enabled, reason: enabled ? "axis_source_ui_available" : `axis_source_ui_${action}_disabled` }])), relief: { estop: task_state === "STATE_ESTOP", power: on, run: !idle, pauseResume: Boolean(paused), abort: idle }, }; }