feat: sync axis task state parity work
This commit is contained in:
@@ -254,11 +254,24 @@ export function normalizeTaskHalStatus(status = {}) {
|
||||
taskPaused: status.task?.taskPaused === true,
|
||||
singleStepping: status.task?.singleStepping === true,
|
||||
taskCycle: Number(status.task?.taskCycle ?? status.taskCycle ?? 0),
|
||||
programOpen: status.task?.programOpen === true,
|
||||
programFile: status.task?.file || status.task?.openProgram || null,
|
||||
programStartLine: Number(status.task?.programStartLine || 0),
|
||||
planId: Number(status.task?.planId || 0),
|
||||
motionPlanLoaded: status.task?.motionPlanLoaded === true,
|
||||
errorText: status.task?.errorText || null,
|
||||
allHomed: status.task?.allHomed === true,
|
||||
homed: normalizeTaskHalHomed(status.task?.homed, status.task?.allHomed === true),
|
||||
noForceHoming: status.task?.noForceHoming === true,
|
||||
homing: status.task?.homing === true,
|
||||
homeState: String(status.task?.homeState || "UNHOMED").toLowerCase(),
|
||||
motionEnabled: String(status.task?.state || "").toUpperCase() === "ON" || motion.enabled === true,
|
||||
resumeInhibit: status.task?.resumeInhibit === true,
|
||||
servoCycle: Number(status.motionStatus?.cycle ?? status.task?.servoCycle ?? 0),
|
||||
motionQueueDepth,
|
||||
motionPaused: motion.paused === true,
|
||||
motionStepping: motion.stepping === true,
|
||||
motionId: Number(motion.motionId || 0),
|
||||
motionId: Number(motion.motionId ?? motion.id ?? 0),
|
||||
idForStep: Number(motion.idForStep || 0),
|
||||
halChangedPinCount: Array.isArray(status.halSnapshot?.changedPins)
|
||||
? status.halSnapshot.changedPins.length
|
||||
@@ -285,6 +298,13 @@ export function normalizeTaskHalStatus(status = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTaskHalHomed(value, allHomed = false) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(Boolean);
|
||||
}
|
||||
return allHomed ? [true, true, true, true, true] : [false, false, false, false, false];
|
||||
}
|
||||
|
||||
function shouldUseSourceLineSegments(planSegments, lineSegments) {
|
||||
if (lineSegments.length < 3) return false;
|
||||
const plannedLines = new Set(planSegments.map((segment) => Number(segment.line)).filter(Number.isFinite));
|
||||
|
||||
@@ -36,13 +36,16 @@ export function normalizeLinuxCncTaskMode(mode) {
|
||||
return LINUXCNC_TASK_MODES.has(mode) ? mode : "manual";
|
||||
}
|
||||
|
||||
export function normalizeLinuxCncTaskState(machine = {}) {
|
||||
if (LINUXCNC_TASK_STATES.has(machine.taskState)) return machine.taskState;
|
||||
if (machine.estopActive) return "estop";
|
||||
if (machine.powerOn) return "on";
|
||||
export function deriveLinuxCncTaskState(machine = {}) {
|
||||
if (machine.estopActive === true || machine.taskState === "estop") return "estop";
|
||||
if (machine.motionEnabled === true || machine.powerOn === true || machine.taskState === "on") return "on";
|
||||
return "estop-reset";
|
||||
}
|
||||
|
||||
export function normalizeLinuxCncTaskState(machine = {}) {
|
||||
return deriveLinuxCncTaskState(machine);
|
||||
}
|
||||
|
||||
export function normalizeLinuxCncInterpState(machine = {}, runState = "idle") {
|
||||
if (LINUXCNC_INTERP_STATES.has(machine.interpState)) return machine.interpState;
|
||||
if (runState === "running") return "reading";
|
||||
@@ -54,8 +57,15 @@ export function createLinuxCncTaskPolicyStatus(state) {
|
||||
const taskState = normalizeLinuxCncTaskState(state.machine);
|
||||
const taskMode = normalizeLinuxCncTaskMode(state.machine.mode);
|
||||
const interpState = normalizeLinuxCncInterpState(state.machine, state.runState);
|
||||
const allHomed = Boolean(state.machine.allHomed);
|
||||
const statusHomed = state.taskHalStatus?.ui?.homed || state.taskHalStatus?.task?.homed;
|
||||
const homed = normalizeHomedArray(state.machine.homed, statusHomed, state.profile?.jointConfig?.length);
|
||||
const allHomed = Boolean(state.machine.allHomed || homed.length > 0 && homed.every(Boolean));
|
||||
const noForceHoming = Boolean(state.machine.noForceHoming);
|
||||
const homing = Boolean(
|
||||
state.machine.homing ||
|
||||
state.taskHalStatus?.ui?.homing ||
|
||||
state.taskHalStatus?.task?.homing,
|
||||
);
|
||||
const machineFileStaging = state.machineFileStaging || {};
|
||||
const profileId = state.profile?.id || state.machineProfile || null;
|
||||
const taskHalReadiness = state.taskHalRuntimeReadiness || {};
|
||||
@@ -70,6 +80,8 @@ export function createLinuxCncTaskPolicyStatus(state) {
|
||||
const machineFileOpened = profileId === "gmoccapy-xyzab"
|
||||
? Boolean(machineFileStaging.selectedGcodeSourceRel)
|
||||
: Boolean(machineFileStaging.selectedGcodeSourceRel || state.activeProgram);
|
||||
const taskProgramOpen = Boolean(state.taskHalStatus?.ui?.programOpen || state.taskHalStatus?.task?.programOpen);
|
||||
const taskMotionPlanLoaded = Boolean(state.taskHalStatus?.ui?.motionPlanLoaded || state.taskHalStatus?.task?.motionPlanLoaded);
|
||||
const interpIdle = interpState === "idle";
|
||||
const motionPaused = state.machine?.motionPaused === true ||
|
||||
state.taskHalStatus?.ui?.motionPaused === true ||
|
||||
@@ -77,6 +89,19 @@ export function createLinuxCncTaskPolicyStatus(state) {
|
||||
const taskPaused = state.machine?.taskPaused === true ||
|
||||
state.taskHalStatus?.ui?.taskPaused === true ||
|
||||
state.taskHalStatus?.task?.taskPaused === true;
|
||||
const motionEnabled = taskState === "on" ||
|
||||
state.machine?.motionEnabled === true ||
|
||||
state.taskHalStatus?.ui?.motionEnabled === true ||
|
||||
state.taskHalStatus?.motionStatus?.motion?.enabled === true;
|
||||
const singleStepping = state.machine?.singleStepping === true ||
|
||||
state.taskHalStatus?.ui?.singleStepping === true ||
|
||||
state.taskHalStatus?.task?.singleStepping === true;
|
||||
const motionStepping = state.machine?.motionStepping === true ||
|
||||
state.taskHalStatus?.ui?.motionStepping === true ||
|
||||
state.taskHalStatus?.motionStatus?.motion?.stepping === true;
|
||||
const resumeInhibit = state.machine?.resumeInhibit === true ||
|
||||
state.taskHalStatus?.ui?.resumeInhibit === true ||
|
||||
state.taskHalStatus?.task?.resumeInhibit === true;
|
||||
const feedbackPaused = state.programRuntimeFeedback?.paused === true;
|
||||
const paused = motionPaused || taskPaused || feedbackPaused || interpState === "paused" || state.runState === "paused";
|
||||
const running = taskMode === "auto" &&
|
||||
@@ -91,28 +116,40 @@ export function createLinuxCncTaskPolicyStatus(state) {
|
||||
runState: state.runState || "idle",
|
||||
motionPaused,
|
||||
taskPaused,
|
||||
motionEnabled,
|
||||
singleStepping,
|
||||
motionStepping,
|
||||
resumeInhibit,
|
||||
feedbackPaused,
|
||||
paused,
|
||||
homed,
|
||||
allHomed,
|
||||
noForceHoming,
|
||||
homing,
|
||||
iniLoaded,
|
||||
machineFileStaged,
|
||||
machineFileOpened,
|
||||
taskProgramOpen,
|
||||
taskMotionPlanLoaded,
|
||||
taskHalRuntimeReady,
|
||||
interpIdle,
|
||||
referenceOnly,
|
||||
profileId,
|
||||
powerOn: taskState === "on",
|
||||
estopActive: taskState === "estop",
|
||||
canPowerToggle: taskState !== "estop",
|
||||
powerAction: taskState === "estop-reset" ? "on" : taskState === "on" ? "off" : "disabled",
|
||||
canMove: taskState === "on",
|
||||
canJog: taskState === "on" && taskMode === "manual" && !running,
|
||||
canHome: taskState === "on" && taskMode === "manual" && !running,
|
||||
canRunAuto: taskState === "on" && taskMode === "auto" && (allHomed || noForceHoming),
|
||||
canJog: taskState === "on" && taskMode === "manual" && interpIdle && !homing && !running,
|
||||
canHome: taskState === "on" && taskMode === "manual" && interpIdle && !homing && !running,
|
||||
canRunAuto: taskState === "on" && taskMode === "auto" && !homing && (allHomed || noForceHoming),
|
||||
canRunAutoStrict: taskState === "on" && taskMode === "auto" && (allHomed || noForceHoming) &&
|
||||
interpIdle && iniLoaded && machineFileStaged && machineFileOpened && taskHalRuntimeReady,
|
||||
!homing && interpIdle && iniLoaded && machineFileStaged && machineFileOpened && taskHalRuntimeReady,
|
||||
canStepStrict: taskState === "on" && taskMode === "auto" && !homing &&
|
||||
(allHomed || noForceHoming) && machineFileOpened && !resumeInhibit,
|
||||
canExecuteMdi: taskState === "on" && taskMode === "mdi" && (allHomed || noForceHoming),
|
||||
canPause: taskState === "on" && taskMode === "auto" && (interpState === "reading" || interpState === "waiting"),
|
||||
canResume: taskState === "on" && (taskMode === "auto" || taskMode === "mdi") && paused,
|
||||
canResume: taskState === "on" && (taskMode === "auto" || taskMode === "mdi") && paused && !resumeInhibit,
|
||||
canAbort: true,
|
||||
canSpindle: taskState === "on",
|
||||
canCoolant: taskState === "on",
|
||||
@@ -128,24 +165,22 @@ export function gateLinuxCncTaskAction(state, action) {
|
||||
|
||||
switch (type) {
|
||||
case "TOGGLE_POWER":
|
||||
if (status.taskState === "estop") return block(status, "power blocked: reset ESTOP first");
|
||||
if (status.powerAction === "disabled") return block(status, "power blocked: invalid task state");
|
||||
return allow(status);
|
||||
case "SET_MODE":
|
||||
return gateMode(status, requestedMode);
|
||||
case "JOG":
|
||||
if (status.taskState !== "on") return block(status, "jog blocked: machine must be on");
|
||||
if (status.taskMode !== "manual") return block(status, "jog blocked: switch to manual mode first");
|
||||
if (status.taskMode === "auto" &&
|
||||
(status.runState === "running" || status.interpState === "reading" || status.interpState === "waiting")) {
|
||||
return block(status, "jog blocked: interpreter must be idle");
|
||||
}
|
||||
if (status.homing) return block(status, "jog blocked: homing is active");
|
||||
if (status.interpState !== "idle") return block(status, "jog blocked: interpreter must be idle");
|
||||
return allow(status);
|
||||
case "HOME":
|
||||
if (status.taskState !== "on") return block(status, "home blocked: machine must be on");
|
||||
if (status.taskMode !== "manual") return block(status, "home blocked: switch to manual mode first");
|
||||
if (status.taskMode === "auto" &&
|
||||
(status.runState === "running" || status.interpState === "reading" || status.interpState === "waiting")) {
|
||||
return block(status, "home blocked: interpreter must be idle");
|
||||
}
|
||||
if (status.homing) return block(status, "home blocked: homing is active");
|
||||
if (status.interpState !== "idle") return block(status, "home blocked: interpreter must be idle");
|
||||
return allow(status);
|
||||
case "RUN_MDI":
|
||||
{
|
||||
@@ -163,8 +198,15 @@ export function gateLinuxCncTaskAction(state, action) {
|
||||
case "RUN_FRAME":
|
||||
if (status.taskState !== "on") return block(status, `${type.toLowerCase()} blocked: machine must be on`);
|
||||
if (status.taskMode !== "auto") return block(status, `${type.toLowerCase()} blocked: switch to auto mode first`);
|
||||
if (status.homing) return block(status, `${type.toLowerCase()} blocked: homing is active`);
|
||||
if (!status.allHomed && !status.noForceHoming) return block(status, `${type.toLowerCase()} blocked: home machine first`);
|
||||
if (type === "RUN" && status.interpState === "paused") return block(status, "run blocked: resume paused program first");
|
||||
if (type === "RUN" && status.interpState !== "idle") return block(status, "run blocked: interpreter must be idle");
|
||||
if (type === "RUN" && status.taskHalRuntimeReady && !status.machineFileOpened) {
|
||||
return block(status, "run blocked: no G-code program opened");
|
||||
}
|
||||
if (type === "STEP" && !status.machineFileOpened) return block(status, "step blocked: no G-code program opened");
|
||||
if (type === "STEP" && status.resumeInhibit) return block(status, "step blocked: resume is inhibited");
|
||||
if (type === "RUN" && status.profileId === "gmoccapy-xyzab") {
|
||||
const strictGate = gateGmoccapyXyzabRun(status);
|
||||
if (!strictGate.allowed) return strictGate;
|
||||
@@ -193,6 +235,7 @@ export function gateLinuxCncTaskAction(state, action) {
|
||||
}
|
||||
case "RESUME":
|
||||
if (status.taskState !== "on") return block(status, "resume blocked: machine must be on");
|
||||
if (status.resumeInhibit) return block(status, "resume blocked: resume is inhibited");
|
||||
if (status.taskMode !== "auto" && status.taskMode !== "mdi" && status.runState !== "paused") {
|
||||
return block(status, "resume blocked: task mode must be auto or MDI");
|
||||
}
|
||||
@@ -259,12 +302,26 @@ function gateMode(status, requestedMode) {
|
||||
if (status.taskState !== "on") {
|
||||
return block(status, `mode blocked: machine must be on before ${targetMode.toUpperCase()}`);
|
||||
}
|
||||
if ((targetMode === "mdi" || targetMode === "auto") && !status.allHomed && !status.noForceHoming) {
|
||||
if ((targetMode === "mdi" || targetMode === "auto") &&
|
||||
!status.homing &&
|
||||
!status.allHomed &&
|
||||
!status.noForceHoming) {
|
||||
return block(status, `mode blocked: home machine before ${targetMode.toUpperCase()}`);
|
||||
}
|
||||
return allow(status);
|
||||
}
|
||||
|
||||
function normalizeHomedArray(machineHomed, statusHomed, jointCount = 0) {
|
||||
const source = Array.isArray(statusHomed)
|
||||
? statusHomed
|
||||
: Array.isArray(machineHomed)
|
||||
? machineHomed
|
||||
: [];
|
||||
const count = Math.max(Number(jointCount || 0), source.length);
|
||||
if (count <= 0) return source.map(Boolean);
|
||||
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
|
||||
}
|
||||
|
||||
function allow(status) {
|
||||
return {
|
||||
allowed: true,
|
||||
|
||||
@@ -43,6 +43,7 @@ import { createLinuxCncParityMatrix } from "../runtime/linuxcnc-parity-matrix.js
|
||||
import { gmoccapyHalModel, resolveGmoccapyHardwareButton } from "../runtime/gmoccapy-hal-model.js";
|
||||
import {
|
||||
createLinuxCncTaskPolicyStatus,
|
||||
deriveLinuxCncTaskState,
|
||||
gateLinuxCncTaskAction,
|
||||
normalizeLinuxCncTaskMode,
|
||||
} from "./linuxcnc-task-policy.js";
|
||||
@@ -166,15 +167,22 @@ const initialState = {
|
||||
machine: {
|
||||
powerOn: false,
|
||||
estopActive: false,
|
||||
motionEnabled: false,
|
||||
taskState: "estop-reset",
|
||||
mode: "manual",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
resumeInhibit: false,
|
||||
manualPanel: "manual",
|
||||
allHomed: false,
|
||||
homed: [false, false, false, false, false],
|
||||
noForceHoming: false,
|
||||
homing: false,
|
||||
homeState: "unhomed",
|
||||
selectedJoint: 0,
|
||||
jogAxis: "x",
|
||||
jogIncrement: 1,
|
||||
@@ -410,6 +418,7 @@ export function createSimulationStore(seed = {}) {
|
||||
: seed.programAxisPreviewPath;
|
||||
state = {
|
||||
...state,
|
||||
machine: normalizeMachineForLinuxCncTask(state.machine, state.runState),
|
||||
programAxisPreviewPath: initialAxisPreviewPath,
|
||||
preview: {
|
||||
...state.preview,
|
||||
@@ -1309,13 +1318,18 @@ export function createSimulationStore(seed = {}) {
|
||||
...state.machine,
|
||||
powerOn: false,
|
||||
estopActive: true,
|
||||
motionEnabled: false,
|
||||
taskState: "estop",
|
||||
manualPanel: "manual",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "estopped",
|
||||
taskHalPauseLock: null,
|
||||
kinsType: "identity",
|
||||
rtcpState: "off",
|
||||
feed: {
|
||||
@@ -1330,8 +1344,29 @@ export function createSimulationStore(seed = {}) {
|
||||
spindle: {
|
||||
...stoppedSpindleState(state.spindle),
|
||||
},
|
||||
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
|
||||
operatorMessage: "emergency stop active",
|
||||
});
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_SET_STATE", state: "ESTOP" },
|
||||
], {
|
||||
operatorMessage: "task/HAL emergency stop active",
|
||||
preserveMachine: {
|
||||
...state.machine,
|
||||
powerOn: false,
|
||||
estopActive: true,
|
||||
motionEnabled: false,
|
||||
taskState: "estop",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
}).catch(() => {});
|
||||
}
|
||||
break;
|
||||
case "RESET":
|
||||
setState({
|
||||
@@ -1339,14 +1374,19 @@ export function createSimulationStore(seed = {}) {
|
||||
...state.machine,
|
||||
powerOn: false,
|
||||
estopActive: false,
|
||||
motionEnabled: false,
|
||||
taskState: "estop-reset",
|
||||
manualPanel: "manual",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
resetCount: state.machine.resetCount + 1,
|
||||
},
|
||||
runState: "idle",
|
||||
taskHalPauseLock: null,
|
||||
kinsType: "identity",
|
||||
rtcpState: "off",
|
||||
coolant: {
|
||||
@@ -1357,8 +1397,29 @@ export function createSimulationStore(seed = {}) {
|
||||
spindle: {
|
||||
...stoppedSpindleState(state.spindle),
|
||||
},
|
||||
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
|
||||
operatorMessage: "estop reset; machine off",
|
||||
});
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_SET_STATE", state: "ESTOP_RESET" },
|
||||
], {
|
||||
operatorMessage: "task/HAL estop reset; machine off",
|
||||
preserveMachine: {
|
||||
...state.machine,
|
||||
powerOn: false,
|
||||
estopActive: false,
|
||||
motionEnabled: false,
|
||||
taskState: "estop-reset",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
}).catch(() => {});
|
||||
}
|
||||
break;
|
||||
case "SET_MODE":
|
||||
{
|
||||
@@ -1375,27 +1436,46 @@ export function createSimulationStore(seed = {}) {
|
||||
? "manual"
|
||||
: null;
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
const manualModePatch = mode === "manual"
|
||||
? {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
}
|
||||
: {};
|
||||
const requestedMachine = {
|
||||
...state.machine,
|
||||
mode,
|
||||
manualPanel,
|
||||
...manualModePatch,
|
||||
};
|
||||
setState({
|
||||
machine: requestedMachine,
|
||||
operatorMessage: `task/HAL mode ${mode} requested`,
|
||||
});
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_SET_MODE", mode: mode.toUpperCase() },
|
||||
], {
|
||||
operatorMessage: `task/HAL mode ${mode}`,
|
||||
preserveMachine: {
|
||||
...state.machine,
|
||||
mode,
|
||||
manualPanel,
|
||||
},
|
||||
preserveMachine: requestedMachine,
|
||||
}).catch(() => {});
|
||||
break;
|
||||
}
|
||||
setState({
|
||||
machine: {
|
||||
...state.machine,
|
||||
mode,
|
||||
manualPanel,
|
||||
interpState: mode === "manual" ? "idle" : state.machine.interpState,
|
||||
interpResumeState: mode === "manual" ? "idle" : state.machine.interpResumeState,
|
||||
taskPaused: mode === "manual" ? false : state.machine.taskPaused,
|
||||
},
|
||||
machine: {
|
||||
...state.machine,
|
||||
mode,
|
||||
manualPanel,
|
||||
interpState: mode === "manual" ? "idle" : state.machine.interpState,
|
||||
interpResumeState: mode === "manual" ? "idle" : state.machine.interpResumeState,
|
||||
taskPaused: mode === "manual" ? false : state.machine.taskPaused,
|
||||
motionPaused: mode === "manual" ? false : state.machine.motionPaused,
|
||||
singleStepping: mode === "manual" ? false : state.machine.singleStepping,
|
||||
motionStepping: mode === "manual" ? false : state.machine.motionStepping,
|
||||
},
|
||||
runState: mode === "manual" && state.runState === "running" ? "stopped" : state.runState,
|
||||
operatorMessage: `mode ${mode}`,
|
||||
});
|
||||
@@ -1473,6 +1553,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
axisPose: nextAxisPose,
|
||||
runState: "jogging",
|
||||
@@ -1496,6 +1579,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
operatorMessage: `task/HAL jog ${axis.toUpperCase()} ${direction > 0 ? "+" : "-"}${increment}`,
|
||||
}).catch(() => {});
|
||||
@@ -1580,6 +1666,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
axisPose: initialAxisPose,
|
||||
runState: "idle",
|
||||
@@ -1615,13 +1704,15 @@ export function createSimulationStore(seed = {}) {
|
||||
...state.machine,
|
||||
mode: "auto",
|
||||
manualPanel: null,
|
||||
interpState: "reading",
|
||||
interpResumeState: "reading",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "running",
|
||||
taskHalPauseLock: null,
|
||||
operatorMessage: "task/HAL program run requested",
|
||||
operatorMessage: "task/HAL program run requested; waiting for status",
|
||||
});
|
||||
runValidatedTaskHalProgramRun().catch(() => {});
|
||||
break;
|
||||
@@ -1644,6 +1735,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: playback.complete ? "idle" : "reading",
|
||||
interpResumeState: playback.complete ? "idle" : "reading",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: playback.complete ? "complete" : "running",
|
||||
taskHalPauseLock: null,
|
||||
@@ -1687,6 +1781,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
@@ -1705,6 +1802,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "stopped",
|
||||
taskHalPauseLock: null,
|
||||
@@ -1764,6 +1864,8 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "paused",
|
||||
taskPaused: true,
|
||||
motionPaused: true,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
};
|
||||
const pauseLock = createTaskHalPauseLock(state, "task-hal-plan-pause");
|
||||
setState({
|
||||
@@ -1803,6 +1905,8 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "paused",
|
||||
taskPaused: true,
|
||||
motionPaused: true,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "paused",
|
||||
taskHalPauseLock: pauseLock,
|
||||
@@ -1853,10 +1957,12 @@ export function createSimulationStore(seed = {}) {
|
||||
interpResumeState: resumeState,
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
};
|
||||
setState({
|
||||
machine: resumedMachine,
|
||||
runState: resumeState === "reading" ? "running" : "idle",
|
||||
runState: resumeState === "reading" || resumeState === "waiting" ? "running" : "idle",
|
||||
taskHalPauseLock: null,
|
||||
operatorMessage: "task/HAL resume requested",
|
||||
});
|
||||
@@ -1885,8 +1991,10 @@ export function createSimulationStore(seed = {}) {
|
||||
interpResumeState: resumeState,
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: resumeState === "reading" ? "running" : "idle",
|
||||
runState: resumeState === "reading" || resumeState === "waiting" ? "running" : "idle",
|
||||
taskHalPauseLock: null,
|
||||
operatorMessage: "program resumed",
|
||||
});
|
||||
@@ -1921,6 +2029,8 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "paused",
|
||||
taskPaused: true,
|
||||
motionPaused: true,
|
||||
singleStepping: true,
|
||||
motionStepping: true,
|
||||
};
|
||||
setState({
|
||||
machine: steppedMachine,
|
||||
@@ -1965,6 +2075,8 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "paused",
|
||||
taskPaused: true,
|
||||
motionPaused: true,
|
||||
singleStepping: true,
|
||||
motionStepping: true,
|
||||
},
|
||||
runState: "stepping",
|
||||
taskHalPauseLock: null,
|
||||
@@ -2002,6 +2114,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: playback.complete ? "idle" : "reading",
|
||||
interpResumeState: playback.complete ? "idle" : "reading",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "running",
|
||||
...playbackPatch,
|
||||
@@ -2022,16 +2137,24 @@ export function createSimulationStore(seed = {}) {
|
||||
break;
|
||||
}
|
||||
const homeAxisPose = homeAxisPoseForState(state);
|
||||
const homedFalse = createHomedArrayForState(state, false);
|
||||
const homedTrue = createHomedArrayForState(state, true);
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
setState({
|
||||
machine: {
|
||||
...state.machine,
|
||||
mode: "manual",
|
||||
manualPanel: "manual",
|
||||
allHomed: true,
|
||||
allHomed: false,
|
||||
homed: homedFalse,
|
||||
homing: true,
|
||||
homeState: "homing",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "idle",
|
||||
axisPose: homeAxisPose,
|
||||
@@ -2044,9 +2167,11 @@ export function createSimulationStore(seed = {}) {
|
||||
operatorMessage: "task/HAL machine homed",
|
||||
preserveAxisPose: homeAxisPose,
|
||||
preserveMachine: {
|
||||
...state.machine,
|
||||
manualPanel: "manual",
|
||||
allHomed: true,
|
||||
homed: homedTrue,
|
||||
homing: false,
|
||||
homeState: "homed",
|
||||
},
|
||||
}).catch(() => {});
|
||||
break;
|
||||
@@ -2057,9 +2182,15 @@ export function createSimulationStore(seed = {}) {
|
||||
mode: "manual",
|
||||
manualPanel: "manual",
|
||||
allHomed: true,
|
||||
homed: homedTrue,
|
||||
homing: false,
|
||||
homeState: "homed",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "idle",
|
||||
axisPose: homeAxisPose,
|
||||
@@ -2073,6 +2204,9 @@ export function createSimulationStore(seed = {}) {
|
||||
machine: {
|
||||
...state.machine,
|
||||
allHomed: false,
|
||||
homed: createHomedArrayForState(state, false),
|
||||
homing: false,
|
||||
homeState: "unhomed",
|
||||
},
|
||||
operatorMessage: "machine unhomed",
|
||||
});
|
||||
@@ -2695,11 +2829,13 @@ export function createSimulationStore(seed = {}) {
|
||||
...state.machine,
|
||||
mode: "auto",
|
||||
manualPanel: null,
|
||||
interpState: "reading",
|
||||
interpResumeState: "reading",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "running",
|
||||
taskHalPauseLock: null,
|
||||
operatorMessage: "RUN ready: motion plan source ready; preparing task/HAL session",
|
||||
});
|
||||
@@ -3806,7 +3942,13 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
: rawTaskState;
|
||||
const taskMode = statusPaused
|
||||
? programControlModeForMachine(state.machine)
|
||||
: normalizeLinuxCncTaskMode(preserveMachine?.mode || ui.taskMode || task.mode || state.machine.mode);
|
||||
: normalizeLinuxCncTaskMode(
|
||||
preserveMachine?.mode ||
|
||||
(preserveMachine?.allHomed && state.machine.mode !== "manual" ? state.machine.mode : null) ||
|
||||
ui.taskMode ||
|
||||
task.mode ||
|
||||
state.machine.mode,
|
||||
);
|
||||
const manualPanel = taskMode === "manual"
|
||||
? (preserveMachine?.manualPanel || state.machine.manualPanel || "manual")
|
||||
: null;
|
||||
@@ -3815,12 +3957,40 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
: Object.hasOwn(preserveMachine || {}, "interpState")
|
||||
? normalizeTaskHalInterpState(preserveMachine.interpState)
|
||||
: statusInterpState;
|
||||
const allHomed = Boolean(preserveMachine?.allHomed ?? state.machine.allHomed);
|
||||
const allHomed = Boolean(
|
||||
task.allHomed === true ||
|
||||
ui.allHomed === true ||
|
||||
preserveMachine?.allHomed ||
|
||||
state.machine.allHomed,
|
||||
);
|
||||
const homed = normalizeHomedArrayForState(
|
||||
state,
|
||||
ui.homed || task.homed || preserveMachine?.homed || state.machine.homed,
|
||||
allHomed,
|
||||
);
|
||||
const homing = Object.hasOwn(task, "homing")
|
||||
? task.homing === true
|
||||
: Object.hasOwn(ui, "homing")
|
||||
? ui.homing === true
|
||||
: Boolean(preserveMachine?.homing);
|
||||
const homeState = normalizeTaskHalHomeState(
|
||||
ui.homeState ||
|
||||
task.homeState ||
|
||||
preserveMachine?.homeState ||
|
||||
state.machine.homeState ||
|
||||
(allHomed ? "homed" : "unhomed"),
|
||||
);
|
||||
const activeLine = state.programStartLine + Math.max(Number(ui.activeLine || 1) - 1, 0);
|
||||
const kinsType = resolveTaskHalKinsType(state, status, activeLine);
|
||||
const motionPaused = statusMotionPaused || preserveMachine?.motionPaused === true;
|
||||
const taskPaused = statusTaskPaused || preserveMachine?.taskPaused === true;
|
||||
const paused = interpState === "paused" || motionPaused || taskPaused;
|
||||
const singleStepping = ui.singleStepping === true ||
|
||||
task.singleStepping === true ||
|
||||
preserveMachine?.singleStepping === true;
|
||||
const motionStepping = ui.motionStepping === true ||
|
||||
motion.stepping === true ||
|
||||
preserveMachine?.motionStepping === true;
|
||||
const interpResumeState = normalizeTaskHalInterpState(
|
||||
ui.interpResumeState ||
|
||||
task.interpResumeState ||
|
||||
@@ -3955,6 +4125,7 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
...state.machine,
|
||||
powerOn: taskState === "on",
|
||||
estopActive: taskState === "estop",
|
||||
motionEnabled: taskState === "on",
|
||||
taskState,
|
||||
mode: taskMode,
|
||||
manualPanel,
|
||||
@@ -3962,7 +4133,12 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
interpResumeState,
|
||||
taskPaused: paused,
|
||||
motionPaused,
|
||||
singleStepping,
|
||||
motionStepping,
|
||||
allHomed,
|
||||
homed,
|
||||
homing,
|
||||
homeState,
|
||||
noForceHoming: Boolean(state.machine.noForceHoming),
|
||||
},
|
||||
runState,
|
||||
@@ -4035,17 +4211,26 @@ function programControlModeForMachine(machine = {}) {
|
||||
}
|
||||
|
||||
function createPowerToggleMachinePatch(machine = {}, turningOn = false) {
|
||||
const poweredOffHomed = Array.isArray(machine.homed)
|
||||
? machine.homed.map(() => false)
|
||||
: [false, false, false, false, false];
|
||||
return {
|
||||
...machine,
|
||||
powerOn: turningOn,
|
||||
estopActive: false,
|
||||
taskState: turningOn ? "on" : "off",
|
||||
motionEnabled: turningOn,
|
||||
taskState: turningOn ? "on" : "estop-reset",
|
||||
manualPanel: "manual",
|
||||
allHomed: turningOn ? Boolean(machine.allHomed) : false,
|
||||
homed: turningOn ? normalizeHomedArrayForMachine(machine, Boolean(machine.allHomed)) : poweredOffHomed,
|
||||
homing: false,
|
||||
homeState: turningOn ? (machine.allHomed ? "homed" : machine.homeState || "unhomed") : "unhomed",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4094,6 +4279,9 @@ function createStoppedProgramStatePatch(state, {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: reason === "aborted" ? "stopped" : reason,
|
||||
taskHalPauseLock: null,
|
||||
@@ -4108,6 +4296,7 @@ function createStoppedProgramStatePatch(state, {
|
||||
...state.feed,
|
||||
currentVelocity: 0,
|
||||
},
|
||||
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
|
||||
operatorMessage,
|
||||
};
|
||||
}
|
||||
@@ -4615,7 +4804,6 @@ function normalizeTaskHalTaskState(value) {
|
||||
const state = String(value || "").toLowerCase().replaceAll("_", "-");
|
||||
if (state === "on") return "on";
|
||||
if (state === "estop") return "estop";
|
||||
if (state === "off") return "off";
|
||||
return "estop-reset";
|
||||
}
|
||||
|
||||
@@ -4627,6 +4815,41 @@ function normalizeTaskHalInterpState(value) {
|
||||
return "idle";
|
||||
}
|
||||
|
||||
function normalizeTaskHalHomeState(value) {
|
||||
const state = String(value || "").toLowerCase().replaceAll("_", "-");
|
||||
if (state === "homing") return "homing";
|
||||
if (state === "homed") return "homed";
|
||||
if (state === "no-force-homing") return "no-force-homing";
|
||||
return "unhomed";
|
||||
}
|
||||
|
||||
function jointCountForState(state = {}) {
|
||||
return Math.max(
|
||||
Number(state.profile?.jointConfig?.length || 0),
|
||||
Number(state.profile?.joints?.length || 0),
|
||||
Array.isArray(state.machine?.homed) ? state.machine.homed.length : 0,
|
||||
5,
|
||||
);
|
||||
}
|
||||
|
||||
function createHomedArrayForState(state = {}, value = false) {
|
||||
return Array.from({ length: jointCountForState(state) }, () => Boolean(value));
|
||||
}
|
||||
|
||||
function normalizeHomedArrayForState(state = {}, source = [], allHomed = false) {
|
||||
const count = jointCountForState(state);
|
||||
if (allHomed) return Array.from({ length: count }, () => true);
|
||||
if (!Array.isArray(source)) return Array.from({ length: count }, () => false);
|
||||
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
|
||||
}
|
||||
|
||||
function normalizeHomedArrayForMachine(machine = {}, allHomed = false, fallbackCount = 5) {
|
||||
const source = Array.isArray(machine.homed) ? machine.homed : [];
|
||||
const count = Math.max(source.length, fallbackCount);
|
||||
if (allHomed) return Array.from({ length: count }, () => true);
|
||||
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
|
||||
}
|
||||
|
||||
function kinsTypeFromSwitchkinsTypeValue(state, value) {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric) || numeric === 0) return "identity";
|
||||
@@ -4639,33 +4862,37 @@ function canMoveMachine(state) {
|
||||
}
|
||||
|
||||
function normalizeMachineForLinuxCncTask(machine = {}, runState = "idle") {
|
||||
const taskState = machine.estopActive
|
||||
? "estop"
|
||||
: machine.taskState === "on" || machine.powerOn
|
||||
? "on"
|
||||
: machine.taskState === "off"
|
||||
? "off"
|
||||
: "estop-reset";
|
||||
const mode = normalizeLinuxCncTaskMode(machine.mode);
|
||||
const interpState = machine.interpState
|
||||
const baseMachine = { ...initialState.machine, ...machine };
|
||||
const taskState = deriveLinuxCncTaskState(baseMachine);
|
||||
const mode = normalizeLinuxCncTaskMode(baseMachine.mode);
|
||||
const interpState = baseMachine.interpState
|
||||
|| (runState === "running" ? "reading" : runState === "paused" || runState === "stepping" ? "paused" : "idle");
|
||||
const manualPanel = mode === "manual"
|
||||
? (machine.manualPanel === "jog" ? "jog" : "manual")
|
||||
? (baseMachine.manualPanel === "jog" ? "jog" : "manual")
|
||||
: null;
|
||||
const homed = normalizeHomedArrayForMachine(baseMachine, Boolean(baseMachine.allHomed));
|
||||
const allHomed = Boolean(baseMachine.allHomed || homed.every(Boolean));
|
||||
|
||||
return {
|
||||
...machine,
|
||||
...baseMachine,
|
||||
taskState,
|
||||
mode,
|
||||
manualPanel,
|
||||
interpState,
|
||||
interpResumeState: machine.interpResumeState || (interpState === "paused" ? "reading" : interpState),
|
||||
taskPaused: Boolean(machine.taskPaused || interpState === "paused"),
|
||||
motionPaused: Boolean(machine.motionPaused || machine.taskPaused || interpState === "paused"),
|
||||
interpResumeState: baseMachine.interpResumeState || (interpState === "paused" ? "reading" : interpState),
|
||||
taskPaused: Boolean(baseMachine.taskPaused || interpState === "paused"),
|
||||
motionPaused: Boolean(baseMachine.motionPaused || baseMachine.taskPaused || interpState === "paused"),
|
||||
singleStepping: Boolean(baseMachine.singleStepping),
|
||||
motionStepping: Boolean(baseMachine.motionStepping),
|
||||
resumeInhibit: Boolean(baseMachine.resumeInhibit),
|
||||
motionEnabled: taskState === "on",
|
||||
powerOn: taskState === "on",
|
||||
estopActive: taskState === "estop",
|
||||
allHomed: Boolean(machine.allHomed),
|
||||
noForceHoming: Boolean(machine.noForceHoming),
|
||||
allHomed,
|
||||
homed,
|
||||
noForceHoming: Boolean(baseMachine.noForceHoming),
|
||||
homing: Boolean(baseMachine.homing),
|
||||
homeState: allHomed ? "homed" : baseMachine.homeState || "unhomed",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -254,11 +254,24 @@ export function normalizeTaskHalStatus(status = {}) {
|
||||
taskPaused: status.task?.taskPaused === true,
|
||||
singleStepping: status.task?.singleStepping === true,
|
||||
taskCycle: Number(status.task?.taskCycle ?? status.taskCycle ?? 0),
|
||||
programOpen: status.task?.programOpen === true,
|
||||
programFile: status.task?.file || status.task?.openProgram || null,
|
||||
programStartLine: Number(status.task?.programStartLine || 0),
|
||||
planId: Number(status.task?.planId || 0),
|
||||
motionPlanLoaded: status.task?.motionPlanLoaded === true,
|
||||
errorText: status.task?.errorText || null,
|
||||
allHomed: status.task?.allHomed === true,
|
||||
homed: normalizeTaskHalHomed(status.task?.homed, status.task?.allHomed === true),
|
||||
noForceHoming: status.task?.noForceHoming === true,
|
||||
homing: status.task?.homing === true,
|
||||
homeState: String(status.task?.homeState || "UNHOMED").toLowerCase(),
|
||||
motionEnabled: String(status.task?.state || "").toUpperCase() === "ON" || motion.enabled === true,
|
||||
resumeInhibit: status.task?.resumeInhibit === true,
|
||||
servoCycle: Number(status.motionStatus?.cycle ?? status.task?.servoCycle ?? 0),
|
||||
motionQueueDepth,
|
||||
motionPaused: motion.paused === true,
|
||||
motionStepping: motion.stepping === true,
|
||||
motionId: Number(motion.motionId || 0),
|
||||
motionId: Number(motion.motionId ?? motion.id ?? 0),
|
||||
idForStep: Number(motion.idForStep || 0),
|
||||
halChangedPinCount: Array.isArray(status.halSnapshot?.changedPins)
|
||||
? status.halSnapshot.changedPins.length
|
||||
@@ -285,6 +298,13 @@ export function normalizeTaskHalStatus(status = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTaskHalHomed(value, allHomed = false) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(Boolean);
|
||||
}
|
||||
return allHomed ? [true, true, true, true, true] : [false, false, false, false, false];
|
||||
}
|
||||
|
||||
function shouldUseSourceLineSegments(planSegments, lineSegments) {
|
||||
if (lineSegments.length < 3) return false;
|
||||
const plannedLines = new Set(planSegments.map((segment) => Number(segment.line)).filter(Number.isFinite));
|
||||
|
||||
@@ -36,13 +36,16 @@ export function normalizeLinuxCncTaskMode(mode) {
|
||||
return LINUXCNC_TASK_MODES.has(mode) ? mode : "manual";
|
||||
}
|
||||
|
||||
export function normalizeLinuxCncTaskState(machine = {}) {
|
||||
if (LINUXCNC_TASK_STATES.has(machine.taskState)) return machine.taskState;
|
||||
if (machine.estopActive) return "estop";
|
||||
if (machine.powerOn) return "on";
|
||||
export function deriveLinuxCncTaskState(machine = {}) {
|
||||
if (machine.estopActive === true || machine.taskState === "estop") return "estop";
|
||||
if (machine.motionEnabled === true || machine.powerOn === true || machine.taskState === "on") return "on";
|
||||
return "estop-reset";
|
||||
}
|
||||
|
||||
export function normalizeLinuxCncTaskState(machine = {}) {
|
||||
return deriveLinuxCncTaskState(machine);
|
||||
}
|
||||
|
||||
export function normalizeLinuxCncInterpState(machine = {}, runState = "idle") {
|
||||
if (LINUXCNC_INTERP_STATES.has(machine.interpState)) return machine.interpState;
|
||||
if (runState === "running") return "reading";
|
||||
@@ -54,8 +57,15 @@ export function createLinuxCncTaskPolicyStatus(state) {
|
||||
const taskState = normalizeLinuxCncTaskState(state.machine);
|
||||
const taskMode = normalizeLinuxCncTaskMode(state.machine.mode);
|
||||
const interpState = normalizeLinuxCncInterpState(state.machine, state.runState);
|
||||
const allHomed = Boolean(state.machine.allHomed);
|
||||
const statusHomed = state.taskHalStatus?.ui?.homed || state.taskHalStatus?.task?.homed;
|
||||
const homed = normalizeHomedArray(state.machine.homed, statusHomed, state.profile?.jointConfig?.length);
|
||||
const allHomed = Boolean(state.machine.allHomed || homed.length > 0 && homed.every(Boolean));
|
||||
const noForceHoming = Boolean(state.machine.noForceHoming);
|
||||
const homing = Boolean(
|
||||
state.machine.homing ||
|
||||
state.taskHalStatus?.ui?.homing ||
|
||||
state.taskHalStatus?.task?.homing,
|
||||
);
|
||||
const machineFileStaging = state.machineFileStaging || {};
|
||||
const profileId = state.profile?.id || state.machineProfile || null;
|
||||
const taskHalReadiness = state.taskHalRuntimeReadiness || {};
|
||||
@@ -70,6 +80,8 @@ export function createLinuxCncTaskPolicyStatus(state) {
|
||||
const machineFileOpened = profileId === "gmoccapy-xyzab"
|
||||
? Boolean(machineFileStaging.selectedGcodeSourceRel)
|
||||
: Boolean(machineFileStaging.selectedGcodeSourceRel || state.activeProgram);
|
||||
const taskProgramOpen = Boolean(state.taskHalStatus?.ui?.programOpen || state.taskHalStatus?.task?.programOpen);
|
||||
const taskMotionPlanLoaded = Boolean(state.taskHalStatus?.ui?.motionPlanLoaded || state.taskHalStatus?.task?.motionPlanLoaded);
|
||||
const interpIdle = interpState === "idle";
|
||||
const motionPaused = state.machine?.motionPaused === true ||
|
||||
state.taskHalStatus?.ui?.motionPaused === true ||
|
||||
@@ -77,6 +89,19 @@ export function createLinuxCncTaskPolicyStatus(state) {
|
||||
const taskPaused = state.machine?.taskPaused === true ||
|
||||
state.taskHalStatus?.ui?.taskPaused === true ||
|
||||
state.taskHalStatus?.task?.taskPaused === true;
|
||||
const motionEnabled = taskState === "on" ||
|
||||
state.machine?.motionEnabled === true ||
|
||||
state.taskHalStatus?.ui?.motionEnabled === true ||
|
||||
state.taskHalStatus?.motionStatus?.motion?.enabled === true;
|
||||
const singleStepping = state.machine?.singleStepping === true ||
|
||||
state.taskHalStatus?.ui?.singleStepping === true ||
|
||||
state.taskHalStatus?.task?.singleStepping === true;
|
||||
const motionStepping = state.machine?.motionStepping === true ||
|
||||
state.taskHalStatus?.ui?.motionStepping === true ||
|
||||
state.taskHalStatus?.motionStatus?.motion?.stepping === true;
|
||||
const resumeInhibit = state.machine?.resumeInhibit === true ||
|
||||
state.taskHalStatus?.ui?.resumeInhibit === true ||
|
||||
state.taskHalStatus?.task?.resumeInhibit === true;
|
||||
const feedbackPaused = state.programRuntimeFeedback?.paused === true;
|
||||
const paused = motionPaused || taskPaused || feedbackPaused || interpState === "paused" || state.runState === "paused";
|
||||
const running = taskMode === "auto" &&
|
||||
@@ -91,28 +116,40 @@ export function createLinuxCncTaskPolicyStatus(state) {
|
||||
runState: state.runState || "idle",
|
||||
motionPaused,
|
||||
taskPaused,
|
||||
motionEnabled,
|
||||
singleStepping,
|
||||
motionStepping,
|
||||
resumeInhibit,
|
||||
feedbackPaused,
|
||||
paused,
|
||||
homed,
|
||||
allHomed,
|
||||
noForceHoming,
|
||||
homing,
|
||||
iniLoaded,
|
||||
machineFileStaged,
|
||||
machineFileOpened,
|
||||
taskProgramOpen,
|
||||
taskMotionPlanLoaded,
|
||||
taskHalRuntimeReady,
|
||||
interpIdle,
|
||||
referenceOnly,
|
||||
profileId,
|
||||
powerOn: taskState === "on",
|
||||
estopActive: taskState === "estop",
|
||||
canPowerToggle: taskState !== "estop",
|
||||
powerAction: taskState === "estop-reset" ? "on" : taskState === "on" ? "off" : "disabled",
|
||||
canMove: taskState === "on",
|
||||
canJog: taskState === "on" && taskMode === "manual" && !running,
|
||||
canHome: taskState === "on" && taskMode === "manual" && !running,
|
||||
canRunAuto: taskState === "on" && taskMode === "auto" && (allHomed || noForceHoming),
|
||||
canJog: taskState === "on" && taskMode === "manual" && interpIdle && !homing && !running,
|
||||
canHome: taskState === "on" && taskMode === "manual" && interpIdle && !homing && !running,
|
||||
canRunAuto: taskState === "on" && taskMode === "auto" && !homing && (allHomed || noForceHoming),
|
||||
canRunAutoStrict: taskState === "on" && taskMode === "auto" && (allHomed || noForceHoming) &&
|
||||
interpIdle && iniLoaded && machineFileStaged && machineFileOpened && taskHalRuntimeReady,
|
||||
!homing && interpIdle && iniLoaded && machineFileStaged && machineFileOpened && taskHalRuntimeReady,
|
||||
canStepStrict: taskState === "on" && taskMode === "auto" && !homing &&
|
||||
(allHomed || noForceHoming) && machineFileOpened && !resumeInhibit,
|
||||
canExecuteMdi: taskState === "on" && taskMode === "mdi" && (allHomed || noForceHoming),
|
||||
canPause: taskState === "on" && taskMode === "auto" && (interpState === "reading" || interpState === "waiting"),
|
||||
canResume: taskState === "on" && (taskMode === "auto" || taskMode === "mdi") && paused,
|
||||
canResume: taskState === "on" && (taskMode === "auto" || taskMode === "mdi") && paused && !resumeInhibit,
|
||||
canAbort: true,
|
||||
canSpindle: taskState === "on",
|
||||
canCoolant: taskState === "on",
|
||||
@@ -128,24 +165,22 @@ export function gateLinuxCncTaskAction(state, action) {
|
||||
|
||||
switch (type) {
|
||||
case "TOGGLE_POWER":
|
||||
if (status.taskState === "estop") return block(status, "power blocked: reset ESTOP first");
|
||||
if (status.powerAction === "disabled") return block(status, "power blocked: invalid task state");
|
||||
return allow(status);
|
||||
case "SET_MODE":
|
||||
return gateMode(status, requestedMode);
|
||||
case "JOG":
|
||||
if (status.taskState !== "on") return block(status, "jog blocked: machine must be on");
|
||||
if (status.taskMode !== "manual") return block(status, "jog blocked: switch to manual mode first");
|
||||
if (status.taskMode === "auto" &&
|
||||
(status.runState === "running" || status.interpState === "reading" || status.interpState === "waiting")) {
|
||||
return block(status, "jog blocked: interpreter must be idle");
|
||||
}
|
||||
if (status.homing) return block(status, "jog blocked: homing is active");
|
||||
if (status.interpState !== "idle") return block(status, "jog blocked: interpreter must be idle");
|
||||
return allow(status);
|
||||
case "HOME":
|
||||
if (status.taskState !== "on") return block(status, "home blocked: machine must be on");
|
||||
if (status.taskMode !== "manual") return block(status, "home blocked: switch to manual mode first");
|
||||
if (status.taskMode === "auto" &&
|
||||
(status.runState === "running" || status.interpState === "reading" || status.interpState === "waiting")) {
|
||||
return block(status, "home blocked: interpreter must be idle");
|
||||
}
|
||||
if (status.homing) return block(status, "home blocked: homing is active");
|
||||
if (status.interpState !== "idle") return block(status, "home blocked: interpreter must be idle");
|
||||
return allow(status);
|
||||
case "RUN_MDI":
|
||||
{
|
||||
@@ -163,8 +198,15 @@ export function gateLinuxCncTaskAction(state, action) {
|
||||
case "RUN_FRAME":
|
||||
if (status.taskState !== "on") return block(status, `${type.toLowerCase()} blocked: machine must be on`);
|
||||
if (status.taskMode !== "auto") return block(status, `${type.toLowerCase()} blocked: switch to auto mode first`);
|
||||
if (status.homing) return block(status, `${type.toLowerCase()} blocked: homing is active`);
|
||||
if (!status.allHomed && !status.noForceHoming) return block(status, `${type.toLowerCase()} blocked: home machine first`);
|
||||
if (type === "RUN" && status.interpState === "paused") return block(status, "run blocked: resume paused program first");
|
||||
if (type === "RUN" && status.interpState !== "idle") return block(status, "run blocked: interpreter must be idle");
|
||||
if (type === "RUN" && status.taskHalRuntimeReady && !status.machineFileOpened) {
|
||||
return block(status, "run blocked: no G-code program opened");
|
||||
}
|
||||
if (type === "STEP" && !status.machineFileOpened) return block(status, "step blocked: no G-code program opened");
|
||||
if (type === "STEP" && status.resumeInhibit) return block(status, "step blocked: resume is inhibited");
|
||||
if (type === "RUN" && status.profileId === "gmoccapy-xyzab") {
|
||||
const strictGate = gateGmoccapyXyzabRun(status);
|
||||
if (!strictGate.allowed) return strictGate;
|
||||
@@ -193,6 +235,7 @@ export function gateLinuxCncTaskAction(state, action) {
|
||||
}
|
||||
case "RESUME":
|
||||
if (status.taskState !== "on") return block(status, "resume blocked: machine must be on");
|
||||
if (status.resumeInhibit) return block(status, "resume blocked: resume is inhibited");
|
||||
if (status.taskMode !== "auto" && status.taskMode !== "mdi" && status.runState !== "paused") {
|
||||
return block(status, "resume blocked: task mode must be auto or MDI");
|
||||
}
|
||||
@@ -259,12 +302,26 @@ function gateMode(status, requestedMode) {
|
||||
if (status.taskState !== "on") {
|
||||
return block(status, `mode blocked: machine must be on before ${targetMode.toUpperCase()}`);
|
||||
}
|
||||
if ((targetMode === "mdi" || targetMode === "auto") && !status.allHomed && !status.noForceHoming) {
|
||||
if ((targetMode === "mdi" || targetMode === "auto") &&
|
||||
!status.homing &&
|
||||
!status.allHomed &&
|
||||
!status.noForceHoming) {
|
||||
return block(status, `mode blocked: home machine before ${targetMode.toUpperCase()}`);
|
||||
}
|
||||
return allow(status);
|
||||
}
|
||||
|
||||
function normalizeHomedArray(machineHomed, statusHomed, jointCount = 0) {
|
||||
const source = Array.isArray(statusHomed)
|
||||
? statusHomed
|
||||
: Array.isArray(machineHomed)
|
||||
? machineHomed
|
||||
: [];
|
||||
const count = Math.max(Number(jointCount || 0), source.length);
|
||||
if (count <= 0) return source.map(Boolean);
|
||||
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
|
||||
}
|
||||
|
||||
function allow(status) {
|
||||
return {
|
||||
allowed: true,
|
||||
|
||||
@@ -43,6 +43,7 @@ import { createLinuxCncParityMatrix } from "../runtime/linuxcnc-parity-matrix.js
|
||||
import { gmoccapyHalModel, resolveGmoccapyHardwareButton } from "../runtime/gmoccapy-hal-model.js";
|
||||
import {
|
||||
createLinuxCncTaskPolicyStatus,
|
||||
deriveLinuxCncTaskState,
|
||||
gateLinuxCncTaskAction,
|
||||
normalizeLinuxCncTaskMode,
|
||||
} from "./linuxcnc-task-policy.js";
|
||||
@@ -166,15 +167,22 @@ const initialState = {
|
||||
machine: {
|
||||
powerOn: false,
|
||||
estopActive: false,
|
||||
motionEnabled: false,
|
||||
taskState: "estop-reset",
|
||||
mode: "manual",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
resumeInhibit: false,
|
||||
manualPanel: "manual",
|
||||
allHomed: false,
|
||||
homed: [false, false, false, false, false],
|
||||
noForceHoming: false,
|
||||
homing: false,
|
||||
homeState: "unhomed",
|
||||
selectedJoint: 0,
|
||||
jogAxis: "x",
|
||||
jogIncrement: 1,
|
||||
@@ -410,6 +418,7 @@ export function createSimulationStore(seed = {}) {
|
||||
: seed.programAxisPreviewPath;
|
||||
state = {
|
||||
...state,
|
||||
machine: normalizeMachineForLinuxCncTask(state.machine, state.runState),
|
||||
programAxisPreviewPath: initialAxisPreviewPath,
|
||||
preview: {
|
||||
...state.preview,
|
||||
@@ -1309,13 +1318,18 @@ export function createSimulationStore(seed = {}) {
|
||||
...state.machine,
|
||||
powerOn: false,
|
||||
estopActive: true,
|
||||
motionEnabled: false,
|
||||
taskState: "estop",
|
||||
manualPanel: "manual",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "estopped",
|
||||
taskHalPauseLock: null,
|
||||
kinsType: "identity",
|
||||
rtcpState: "off",
|
||||
feed: {
|
||||
@@ -1330,8 +1344,29 @@ export function createSimulationStore(seed = {}) {
|
||||
spindle: {
|
||||
...stoppedSpindleState(state.spindle),
|
||||
},
|
||||
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
|
||||
operatorMessage: "emergency stop active",
|
||||
});
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_SET_STATE", state: "ESTOP" },
|
||||
], {
|
||||
operatorMessage: "task/HAL emergency stop active",
|
||||
preserveMachine: {
|
||||
...state.machine,
|
||||
powerOn: false,
|
||||
estopActive: true,
|
||||
motionEnabled: false,
|
||||
taskState: "estop",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
}).catch(() => {});
|
||||
}
|
||||
break;
|
||||
case "RESET":
|
||||
setState({
|
||||
@@ -1339,14 +1374,19 @@ export function createSimulationStore(seed = {}) {
|
||||
...state.machine,
|
||||
powerOn: false,
|
||||
estopActive: false,
|
||||
motionEnabled: false,
|
||||
taskState: "estop-reset",
|
||||
manualPanel: "manual",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
resetCount: state.machine.resetCount + 1,
|
||||
},
|
||||
runState: "idle",
|
||||
taskHalPauseLock: null,
|
||||
kinsType: "identity",
|
||||
rtcpState: "off",
|
||||
coolant: {
|
||||
@@ -1357,8 +1397,29 @@ export function createSimulationStore(seed = {}) {
|
||||
spindle: {
|
||||
...stoppedSpindleState(state.spindle),
|
||||
},
|
||||
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
|
||||
operatorMessage: "estop reset; machine off",
|
||||
});
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_SET_STATE", state: "ESTOP_RESET" },
|
||||
], {
|
||||
operatorMessage: "task/HAL estop reset; machine off",
|
||||
preserveMachine: {
|
||||
...state.machine,
|
||||
powerOn: false,
|
||||
estopActive: false,
|
||||
motionEnabled: false,
|
||||
taskState: "estop-reset",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
}).catch(() => {});
|
||||
}
|
||||
break;
|
||||
case "SET_MODE":
|
||||
{
|
||||
@@ -1375,27 +1436,46 @@ export function createSimulationStore(seed = {}) {
|
||||
? "manual"
|
||||
: null;
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
const manualModePatch = mode === "manual"
|
||||
? {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
}
|
||||
: {};
|
||||
const requestedMachine = {
|
||||
...state.machine,
|
||||
mode,
|
||||
manualPanel,
|
||||
...manualModePatch,
|
||||
};
|
||||
setState({
|
||||
machine: requestedMachine,
|
||||
operatorMessage: `task/HAL mode ${mode} requested`,
|
||||
});
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_SET_MODE", mode: mode.toUpperCase() },
|
||||
], {
|
||||
operatorMessage: `task/HAL mode ${mode}`,
|
||||
preserveMachine: {
|
||||
...state.machine,
|
||||
mode,
|
||||
manualPanel,
|
||||
},
|
||||
preserveMachine: requestedMachine,
|
||||
}).catch(() => {});
|
||||
break;
|
||||
}
|
||||
setState({
|
||||
machine: {
|
||||
...state.machine,
|
||||
mode,
|
||||
manualPanel,
|
||||
interpState: mode === "manual" ? "idle" : state.machine.interpState,
|
||||
interpResumeState: mode === "manual" ? "idle" : state.machine.interpResumeState,
|
||||
taskPaused: mode === "manual" ? false : state.machine.taskPaused,
|
||||
},
|
||||
machine: {
|
||||
...state.machine,
|
||||
mode,
|
||||
manualPanel,
|
||||
interpState: mode === "manual" ? "idle" : state.machine.interpState,
|
||||
interpResumeState: mode === "manual" ? "idle" : state.machine.interpResumeState,
|
||||
taskPaused: mode === "manual" ? false : state.machine.taskPaused,
|
||||
motionPaused: mode === "manual" ? false : state.machine.motionPaused,
|
||||
singleStepping: mode === "manual" ? false : state.machine.singleStepping,
|
||||
motionStepping: mode === "manual" ? false : state.machine.motionStepping,
|
||||
},
|
||||
runState: mode === "manual" && state.runState === "running" ? "stopped" : state.runState,
|
||||
operatorMessage: `mode ${mode}`,
|
||||
});
|
||||
@@ -1473,6 +1553,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
axisPose: nextAxisPose,
|
||||
runState: "jogging",
|
||||
@@ -1496,6 +1579,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
operatorMessage: `task/HAL jog ${axis.toUpperCase()} ${direction > 0 ? "+" : "-"}${increment}`,
|
||||
}).catch(() => {});
|
||||
@@ -1580,6 +1666,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
axisPose: initialAxisPose,
|
||||
runState: "idle",
|
||||
@@ -1615,13 +1704,15 @@ export function createSimulationStore(seed = {}) {
|
||||
...state.machine,
|
||||
mode: "auto",
|
||||
manualPanel: null,
|
||||
interpState: "reading",
|
||||
interpResumeState: "reading",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "running",
|
||||
taskHalPauseLock: null,
|
||||
operatorMessage: "task/HAL program run requested",
|
||||
operatorMessage: "task/HAL program run requested; waiting for status",
|
||||
});
|
||||
runValidatedTaskHalProgramRun().catch(() => {});
|
||||
break;
|
||||
@@ -1644,6 +1735,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: playback.complete ? "idle" : "reading",
|
||||
interpResumeState: playback.complete ? "idle" : "reading",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: playback.complete ? "complete" : "running",
|
||||
taskHalPauseLock: null,
|
||||
@@ -1687,6 +1781,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
@@ -1705,6 +1802,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "stopped",
|
||||
taskHalPauseLock: null,
|
||||
@@ -1764,6 +1864,8 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "paused",
|
||||
taskPaused: true,
|
||||
motionPaused: true,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
};
|
||||
const pauseLock = createTaskHalPauseLock(state, "task-hal-plan-pause");
|
||||
setState({
|
||||
@@ -1803,6 +1905,8 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "paused",
|
||||
taskPaused: true,
|
||||
motionPaused: true,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "paused",
|
||||
taskHalPauseLock: pauseLock,
|
||||
@@ -1853,10 +1957,12 @@ export function createSimulationStore(seed = {}) {
|
||||
interpResumeState: resumeState,
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
};
|
||||
setState({
|
||||
machine: resumedMachine,
|
||||
runState: resumeState === "reading" ? "running" : "idle",
|
||||
runState: resumeState === "reading" || resumeState === "waiting" ? "running" : "idle",
|
||||
taskHalPauseLock: null,
|
||||
operatorMessage: "task/HAL resume requested",
|
||||
});
|
||||
@@ -1885,8 +1991,10 @@ export function createSimulationStore(seed = {}) {
|
||||
interpResumeState: resumeState,
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: resumeState === "reading" ? "running" : "idle",
|
||||
runState: resumeState === "reading" || resumeState === "waiting" ? "running" : "idle",
|
||||
taskHalPauseLock: null,
|
||||
operatorMessage: "program resumed",
|
||||
});
|
||||
@@ -1921,6 +2029,8 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "paused",
|
||||
taskPaused: true,
|
||||
motionPaused: true,
|
||||
singleStepping: true,
|
||||
motionStepping: true,
|
||||
};
|
||||
setState({
|
||||
machine: steppedMachine,
|
||||
@@ -1965,6 +2075,8 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: "paused",
|
||||
taskPaused: true,
|
||||
motionPaused: true,
|
||||
singleStepping: true,
|
||||
motionStepping: true,
|
||||
},
|
||||
runState: "stepping",
|
||||
taskHalPauseLock: null,
|
||||
@@ -2002,6 +2114,9 @@ export function createSimulationStore(seed = {}) {
|
||||
interpState: playback.complete ? "idle" : "reading",
|
||||
interpResumeState: playback.complete ? "idle" : "reading",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "running",
|
||||
...playbackPatch,
|
||||
@@ -2022,16 +2137,24 @@ export function createSimulationStore(seed = {}) {
|
||||
break;
|
||||
}
|
||||
const homeAxisPose = homeAxisPoseForState(state);
|
||||
const homedFalse = createHomedArrayForState(state, false);
|
||||
const homedTrue = createHomedArrayForState(state, true);
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
setState({
|
||||
machine: {
|
||||
...state.machine,
|
||||
mode: "manual",
|
||||
manualPanel: "manual",
|
||||
allHomed: true,
|
||||
allHomed: false,
|
||||
homed: homedFalse,
|
||||
homing: true,
|
||||
homeState: "homing",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "idle",
|
||||
axisPose: homeAxisPose,
|
||||
@@ -2044,9 +2167,11 @@ export function createSimulationStore(seed = {}) {
|
||||
operatorMessage: "task/HAL machine homed",
|
||||
preserveAxisPose: homeAxisPose,
|
||||
preserveMachine: {
|
||||
...state.machine,
|
||||
manualPanel: "manual",
|
||||
allHomed: true,
|
||||
homed: homedTrue,
|
||||
homing: false,
|
||||
homeState: "homed",
|
||||
},
|
||||
}).catch(() => {});
|
||||
break;
|
||||
@@ -2057,9 +2182,15 @@ export function createSimulationStore(seed = {}) {
|
||||
mode: "manual",
|
||||
manualPanel: "manual",
|
||||
allHomed: true,
|
||||
homed: homedTrue,
|
||||
homing: false,
|
||||
homeState: "homed",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "idle",
|
||||
axisPose: homeAxisPose,
|
||||
@@ -2073,6 +2204,9 @@ export function createSimulationStore(seed = {}) {
|
||||
machine: {
|
||||
...state.machine,
|
||||
allHomed: false,
|
||||
homed: createHomedArrayForState(state, false),
|
||||
homing: false,
|
||||
homeState: "unhomed",
|
||||
},
|
||||
operatorMessage: "machine unhomed",
|
||||
});
|
||||
@@ -2695,11 +2829,13 @@ export function createSimulationStore(seed = {}) {
|
||||
...state.machine,
|
||||
mode: "auto",
|
||||
manualPanel: null,
|
||||
interpState: "reading",
|
||||
interpResumeState: "reading",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: "running",
|
||||
taskHalPauseLock: null,
|
||||
operatorMessage: "RUN ready: motion plan source ready; preparing task/HAL session",
|
||||
});
|
||||
@@ -3806,7 +3942,13 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
: rawTaskState;
|
||||
const taskMode = statusPaused
|
||||
? programControlModeForMachine(state.machine)
|
||||
: normalizeLinuxCncTaskMode(preserveMachine?.mode || ui.taskMode || task.mode || state.machine.mode);
|
||||
: normalizeLinuxCncTaskMode(
|
||||
preserveMachine?.mode ||
|
||||
(preserveMachine?.allHomed && state.machine.mode !== "manual" ? state.machine.mode : null) ||
|
||||
ui.taskMode ||
|
||||
task.mode ||
|
||||
state.machine.mode,
|
||||
);
|
||||
const manualPanel = taskMode === "manual"
|
||||
? (preserveMachine?.manualPanel || state.machine.manualPanel || "manual")
|
||||
: null;
|
||||
@@ -3815,12 +3957,40 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
: Object.hasOwn(preserveMachine || {}, "interpState")
|
||||
? normalizeTaskHalInterpState(preserveMachine.interpState)
|
||||
: statusInterpState;
|
||||
const allHomed = Boolean(preserveMachine?.allHomed ?? state.machine.allHomed);
|
||||
const allHomed = Boolean(
|
||||
task.allHomed === true ||
|
||||
ui.allHomed === true ||
|
||||
preserveMachine?.allHomed ||
|
||||
state.machine.allHomed,
|
||||
);
|
||||
const homed = normalizeHomedArrayForState(
|
||||
state,
|
||||
ui.homed || task.homed || preserveMachine?.homed || state.machine.homed,
|
||||
allHomed,
|
||||
);
|
||||
const homing = Object.hasOwn(task, "homing")
|
||||
? task.homing === true
|
||||
: Object.hasOwn(ui, "homing")
|
||||
? ui.homing === true
|
||||
: Boolean(preserveMachine?.homing);
|
||||
const homeState = normalizeTaskHalHomeState(
|
||||
ui.homeState ||
|
||||
task.homeState ||
|
||||
preserveMachine?.homeState ||
|
||||
state.machine.homeState ||
|
||||
(allHomed ? "homed" : "unhomed"),
|
||||
);
|
||||
const activeLine = state.programStartLine + Math.max(Number(ui.activeLine || 1) - 1, 0);
|
||||
const kinsType = resolveTaskHalKinsType(state, status, activeLine);
|
||||
const motionPaused = statusMotionPaused || preserveMachine?.motionPaused === true;
|
||||
const taskPaused = statusTaskPaused || preserveMachine?.taskPaused === true;
|
||||
const paused = interpState === "paused" || motionPaused || taskPaused;
|
||||
const singleStepping = ui.singleStepping === true ||
|
||||
task.singleStepping === true ||
|
||||
preserveMachine?.singleStepping === true;
|
||||
const motionStepping = ui.motionStepping === true ||
|
||||
motion.stepping === true ||
|
||||
preserveMachine?.motionStepping === true;
|
||||
const interpResumeState = normalizeTaskHalInterpState(
|
||||
ui.interpResumeState ||
|
||||
task.interpResumeState ||
|
||||
@@ -3955,6 +4125,7 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
...state.machine,
|
||||
powerOn: taskState === "on",
|
||||
estopActive: taskState === "estop",
|
||||
motionEnabled: taskState === "on",
|
||||
taskState,
|
||||
mode: taskMode,
|
||||
manualPanel,
|
||||
@@ -3962,7 +4133,12 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
interpResumeState,
|
||||
taskPaused: paused,
|
||||
motionPaused,
|
||||
singleStepping,
|
||||
motionStepping,
|
||||
allHomed,
|
||||
homed,
|
||||
homing,
|
||||
homeState,
|
||||
noForceHoming: Boolean(state.machine.noForceHoming),
|
||||
},
|
||||
runState,
|
||||
@@ -4035,17 +4211,26 @@ function programControlModeForMachine(machine = {}) {
|
||||
}
|
||||
|
||||
function createPowerToggleMachinePatch(machine = {}, turningOn = false) {
|
||||
const poweredOffHomed = Array.isArray(machine.homed)
|
||||
? machine.homed.map(() => false)
|
||||
: [false, false, false, false, false];
|
||||
return {
|
||||
...machine,
|
||||
powerOn: turningOn,
|
||||
estopActive: false,
|
||||
taskState: turningOn ? "on" : "off",
|
||||
motionEnabled: turningOn,
|
||||
taskState: turningOn ? "on" : "estop-reset",
|
||||
manualPanel: "manual",
|
||||
allHomed: turningOn ? Boolean(machine.allHomed) : false,
|
||||
homed: turningOn ? normalizeHomedArrayForMachine(machine, Boolean(machine.allHomed)) : poweredOffHomed,
|
||||
homing: false,
|
||||
homeState: turningOn ? (machine.allHomed ? "homed" : machine.homeState || "unhomed") : "unhomed",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4094,6 +4279,9 @@ function createStoppedProgramStatePatch(state, {
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
motionPaused: false,
|
||||
singleStepping: false,
|
||||
motionStepping: false,
|
||||
},
|
||||
runState: reason === "aborted" ? "stopped" : reason,
|
||||
taskHalPauseLock: null,
|
||||
@@ -4108,6 +4296,7 @@ function createStoppedProgramStatePatch(state, {
|
||||
...state.feed,
|
||||
currentVelocity: 0,
|
||||
},
|
||||
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
|
||||
operatorMessage,
|
||||
};
|
||||
}
|
||||
@@ -4615,7 +4804,6 @@ function normalizeTaskHalTaskState(value) {
|
||||
const state = String(value || "").toLowerCase().replaceAll("_", "-");
|
||||
if (state === "on") return "on";
|
||||
if (state === "estop") return "estop";
|
||||
if (state === "off") return "off";
|
||||
return "estop-reset";
|
||||
}
|
||||
|
||||
@@ -4627,6 +4815,41 @@ function normalizeTaskHalInterpState(value) {
|
||||
return "idle";
|
||||
}
|
||||
|
||||
function normalizeTaskHalHomeState(value) {
|
||||
const state = String(value || "").toLowerCase().replaceAll("_", "-");
|
||||
if (state === "homing") return "homing";
|
||||
if (state === "homed") return "homed";
|
||||
if (state === "no-force-homing") return "no-force-homing";
|
||||
return "unhomed";
|
||||
}
|
||||
|
||||
function jointCountForState(state = {}) {
|
||||
return Math.max(
|
||||
Number(state.profile?.jointConfig?.length || 0),
|
||||
Number(state.profile?.joints?.length || 0),
|
||||
Array.isArray(state.machine?.homed) ? state.machine.homed.length : 0,
|
||||
5,
|
||||
);
|
||||
}
|
||||
|
||||
function createHomedArrayForState(state = {}, value = false) {
|
||||
return Array.from({ length: jointCountForState(state) }, () => Boolean(value));
|
||||
}
|
||||
|
||||
function normalizeHomedArrayForState(state = {}, source = [], allHomed = false) {
|
||||
const count = jointCountForState(state);
|
||||
if (allHomed) return Array.from({ length: count }, () => true);
|
||||
if (!Array.isArray(source)) return Array.from({ length: count }, () => false);
|
||||
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
|
||||
}
|
||||
|
||||
function normalizeHomedArrayForMachine(machine = {}, allHomed = false, fallbackCount = 5) {
|
||||
const source = Array.isArray(machine.homed) ? machine.homed : [];
|
||||
const count = Math.max(source.length, fallbackCount);
|
||||
if (allHomed) return Array.from({ length: count }, () => true);
|
||||
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
|
||||
}
|
||||
|
||||
function kinsTypeFromSwitchkinsTypeValue(state, value) {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric) || numeric === 0) return "identity";
|
||||
@@ -4639,33 +4862,37 @@ function canMoveMachine(state) {
|
||||
}
|
||||
|
||||
function normalizeMachineForLinuxCncTask(machine = {}, runState = "idle") {
|
||||
const taskState = machine.estopActive
|
||||
? "estop"
|
||||
: machine.taskState === "on" || machine.powerOn
|
||||
? "on"
|
||||
: machine.taskState === "off"
|
||||
? "off"
|
||||
: "estop-reset";
|
||||
const mode = normalizeLinuxCncTaskMode(machine.mode);
|
||||
const interpState = machine.interpState
|
||||
const baseMachine = { ...initialState.machine, ...machine };
|
||||
const taskState = deriveLinuxCncTaskState(baseMachine);
|
||||
const mode = normalizeLinuxCncTaskMode(baseMachine.mode);
|
||||
const interpState = baseMachine.interpState
|
||||
|| (runState === "running" ? "reading" : runState === "paused" || runState === "stepping" ? "paused" : "idle");
|
||||
const manualPanel = mode === "manual"
|
||||
? (machine.manualPanel === "jog" ? "jog" : "manual")
|
||||
? (baseMachine.manualPanel === "jog" ? "jog" : "manual")
|
||||
: null;
|
||||
const homed = normalizeHomedArrayForMachine(baseMachine, Boolean(baseMachine.allHomed));
|
||||
const allHomed = Boolean(baseMachine.allHomed || homed.every(Boolean));
|
||||
|
||||
return {
|
||||
...machine,
|
||||
...baseMachine,
|
||||
taskState,
|
||||
mode,
|
||||
manualPanel,
|
||||
interpState,
|
||||
interpResumeState: machine.interpResumeState || (interpState === "paused" ? "reading" : interpState),
|
||||
taskPaused: Boolean(machine.taskPaused || interpState === "paused"),
|
||||
motionPaused: Boolean(machine.motionPaused || machine.taskPaused || interpState === "paused"),
|
||||
interpResumeState: baseMachine.interpResumeState || (interpState === "paused" ? "reading" : interpState),
|
||||
taskPaused: Boolean(baseMachine.taskPaused || interpState === "paused"),
|
||||
motionPaused: Boolean(baseMachine.motionPaused || baseMachine.taskPaused || interpState === "paused"),
|
||||
singleStepping: Boolean(baseMachine.singleStepping),
|
||||
motionStepping: Boolean(baseMachine.motionStepping),
|
||||
resumeInhibit: Boolean(baseMachine.resumeInhibit),
|
||||
motionEnabled: taskState === "on",
|
||||
powerOn: taskState === "on",
|
||||
estopActive: taskState === "estop",
|
||||
allHomed: Boolean(machine.allHomed),
|
||||
noForceHoming: Boolean(machine.noForceHoming),
|
||||
allHomed,
|
||||
homed,
|
||||
noForceHoming: Boolean(baseMachine.noForceHoming),
|
||||
homing: Boolean(baseMachine.homing),
|
||||
homeState: allHomed ? "homed" : baseMachine.homeState || "unhomed",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user