fix: make Pause button pause running program
This commit is contained in:
@@ -163,10 +163,28 @@ export function gateLinuxCncTaskAction(state, action) {
|
||||
return allow(status);
|
||||
case "PAUSE":
|
||||
if (status.taskState !== "on") return block(status, "pause blocked: machine must be on");
|
||||
if (status.taskMode !== "auto" && status.taskMode !== "mdi") {
|
||||
return block(status, "pause blocked: task mode must be auto or MDI");
|
||||
{
|
||||
const taskIsRunning = status.runState === "running" || status.runState === "stepping";
|
||||
const interpIsRunning = status.interpState === "reading" || status.interpState === "waiting";
|
||||
const interpCanPauseResume = status.interpState !== "idle" || taskIsRunning;
|
||||
const pauseReady = action.source === "pauseresume" ? interpCanPauseResume : (interpIsRunning || taskIsRunning);
|
||||
const requiredModeMessage = action.source === "pauseresume"
|
||||
? "pause blocked: task mode must be auto or MDI"
|
||||
: "pause blocked: task mode must be auto";
|
||||
const notRunningMessage = action.source === "pauseresume"
|
||||
? "pause ignored: interpreter is idle"
|
||||
: "pause blocked: interpreter is not running";
|
||||
|
||||
if (action.source === "pauseresume") {
|
||||
if (status.taskMode !== "auto" && status.taskMode !== "mdi") {
|
||||
return block(status, requiredModeMessage);
|
||||
}
|
||||
} else if (status.taskMode !== "auto") {
|
||||
return block(status, requiredModeMessage);
|
||||
}
|
||||
if (!pauseReady) return block(status, notRunningMessage);
|
||||
return allow(status);
|
||||
}
|
||||
return allow(status);
|
||||
case "RESUME":
|
||||
if (status.taskState !== "on") return block(status, "resume blocked: machine must be on");
|
||||
if (status.taskMode !== "auto" && status.taskMode !== "mdi") {
|
||||
|
||||
@@ -282,6 +282,13 @@ const initialState = {
|
||||
override: 100,
|
||||
enabled: false,
|
||||
direction: "stop",
|
||||
halPins: {
|
||||
on: 0,
|
||||
forward: 0,
|
||||
reverse: 0,
|
||||
speedOut: 0,
|
||||
atSpeed: 0,
|
||||
},
|
||||
},
|
||||
coolant: {
|
||||
flood: false,
|
||||
@@ -1123,6 +1130,7 @@ export function createSimulationStore(seed = {}) {
|
||||
taskHalStatusLoop: {
|
||||
...state.taskHalStatusLoop,
|
||||
active: false,
|
||||
sequence: Number(state.taskHalStatusLoop?.sequence || 0) + 1,
|
||||
stopReason: action.reason || "stopped",
|
||||
lastError: action.error || null,
|
||||
},
|
||||
@@ -1279,7 +1287,7 @@ export function createSimulationStore(seed = {}) {
|
||||
rtcpState: turningOff ? "off" : state.rtcpState,
|
||||
feed: turningOff ? { ...state.feed, currentVelocity: 0 } : state.feed,
|
||||
coolant: turningOff ? { ...state.coolant, flood: false, mist: false } : state.coolant,
|
||||
spindle: turningOff ? { ...state.spindle, enabled: false, direction: "stop" } : state.spindle,
|
||||
spindle: turningOff ? stoppedSpindleState(state.spindle) : state.spindle,
|
||||
operatorMessage: turningOff ? "task/HAL machine power off" : "task/HAL machine power on",
|
||||
});
|
||||
runTaskHalCommandSequence([
|
||||
@@ -1303,7 +1311,7 @@ export function createSimulationStore(seed = {}) {
|
||||
rtcpState: turningOff ? "off" : state.rtcpState,
|
||||
feed: turningOff ? { ...state.feed, currentVelocity: 0 } : state.feed,
|
||||
coolant: turningOff ? { ...state.coolant, flood: false, mist: false } : state.coolant,
|
||||
spindle: turningOff ? { ...state.spindle, enabled: false, direction: "stop" } : state.spindle,
|
||||
spindle: turningOff ? stoppedSpindleState(state.spindle) : state.spindle,
|
||||
operatorMessage: turningOff ? "machine power off" : "machine power on",
|
||||
});
|
||||
}
|
||||
@@ -1333,9 +1341,7 @@ export function createSimulationStore(seed = {}) {
|
||||
mist: false,
|
||||
},
|
||||
spindle: {
|
||||
...state.spindle,
|
||||
enabled: false,
|
||||
direction: "stop",
|
||||
...stoppedSpindleState(state.spindle),
|
||||
},
|
||||
operatorMessage: "emergency stop active",
|
||||
});
|
||||
@@ -1362,9 +1368,7 @@ export function createSimulationStore(seed = {}) {
|
||||
mist: false,
|
||||
},
|
||||
spindle: {
|
||||
...state.spindle,
|
||||
enabled: false,
|
||||
direction: "stop",
|
||||
...stoppedSpindleState(state.spindle),
|
||||
},
|
||||
operatorMessage: "estop reset; machine off",
|
||||
});
|
||||
@@ -1619,6 +1623,18 @@ export function createSimulationStore(seed = {}) {
|
||||
setState({ operatorMessage: preconditions.operatorMessage });
|
||||
break;
|
||||
}
|
||||
setState({
|
||||
machine: {
|
||||
...state.machine,
|
||||
mode: "auto",
|
||||
manualPanel: null,
|
||||
interpState: "reading",
|
||||
interpResumeState: "reading",
|
||||
taskPaused: false,
|
||||
},
|
||||
runState: "running",
|
||||
operatorMessage: "task/HAL program run requested",
|
||||
});
|
||||
runValidatedTaskHalProgramRun().catch(() => {});
|
||||
break;
|
||||
}
|
||||
@@ -1718,11 +1734,22 @@ export function createSimulationStore(seed = {}) {
|
||||
break;
|
||||
}
|
||||
const direction = normalizeSpindleDirection(action.direction);
|
||||
const spindleEnabled = direction !== "stop";
|
||||
const spindleActualRpm = spindleEnabled
|
||||
? Number(state.spindle.rpm || 0) * (Number(state.spindle.override || 100) / 100)
|
||||
: 0;
|
||||
setState({
|
||||
spindle: {
|
||||
...state.spindle,
|
||||
enabled: direction !== "stop",
|
||||
enabled: spindleEnabled,
|
||||
direction,
|
||||
halPins: {
|
||||
on: spindleEnabled ? 1 : 0,
|
||||
forward: direction === "forward" ? 1 : 0,
|
||||
reverse: direction === "reverse" ? 1 : 0,
|
||||
speedOut: spindleActualRpm,
|
||||
atSpeed: spindleEnabled ? 1 : 0,
|
||||
},
|
||||
},
|
||||
operatorMessage: direction === "stop" ? "spindle stopped" : `spindle ${direction}`,
|
||||
});
|
||||
@@ -1737,9 +1764,29 @@ export function createSimulationStore(seed = {}) {
|
||||
}
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
stopTaskHalStatusLoop("paused", { operatorMessage: "task/HAL pause requested" });
|
||||
const pausedMachine = {
|
||||
...state.machine,
|
||||
interpResumeState: state.machine.interpState === "paused"
|
||||
? state.machine.interpResumeState
|
||||
: state.machine.interpState || "reading",
|
||||
interpState: "paused",
|
||||
taskPaused: true,
|
||||
};
|
||||
setState({
|
||||
machine: pausedMachine,
|
||||
runState: "paused",
|
||||
feed: {
|
||||
...state.feed,
|
||||
currentVelocity: 0,
|
||||
},
|
||||
operatorMessage: "task/HAL pause requested",
|
||||
});
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_PLAN_PAUSE" },
|
||||
], { operatorMessage: "task/HAL program paused" }).catch(() => {});
|
||||
], {
|
||||
operatorMessage: "task/HAL program paused",
|
||||
preserveMachine: pausedMachine,
|
||||
}).catch(() => {});
|
||||
break;
|
||||
}
|
||||
setState({
|
||||
@@ -1756,6 +1803,26 @@ export function createSimulationStore(seed = {}) {
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "PAUSE_RESUME":
|
||||
{
|
||||
const interpState = state.machine?.interpState || state.linuxCncTaskPolicy?.interpState || "idle";
|
||||
const taskMode = state.machine?.mode || state.linuxCncTaskPolicy?.taskMode || "manual";
|
||||
const taskIsRunning = state.runState === "running" || state.runState === "stepping";
|
||||
if (state.machine?.taskPaused === true || interpState === "paused" || state.runState === "paused") {
|
||||
if (taskMode === "auto" || taskMode === "mdi") {
|
||||
dispatch({ type: "RESUME" });
|
||||
break;
|
||||
}
|
||||
setState({ operatorMessage: "resume blocked: task mode must be auto or MDI" });
|
||||
break;
|
||||
}
|
||||
if ((taskMode === "auto" || taskMode === "mdi") && (interpState !== "idle" || taskIsRunning)) {
|
||||
dispatch({ type: "PAUSE", source: "pauseresume" });
|
||||
break;
|
||||
}
|
||||
setState({ operatorMessage: "pause ignored: interpreter is idle" });
|
||||
}
|
||||
break;
|
||||
case "RESUME":
|
||||
{
|
||||
const gate = gateLinuxCncTaskAction(state, action);
|
||||
@@ -1764,10 +1831,25 @@ export function createSimulationStore(seed = {}) {
|
||||
break;
|
||||
}
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
const resumeState = state.machine.interpResumeState === "idle" || state.machine.interpResumeState === "paused"
|
||||
? "reading"
|
||||
: state.machine.interpResumeState || "reading";
|
||||
const resumedMachine = {
|
||||
...state.machine,
|
||||
interpState: resumeState,
|
||||
interpResumeState: resumeState,
|
||||
taskPaused: false,
|
||||
};
|
||||
setState({
|
||||
machine: resumedMachine,
|
||||
runState: resumeState === "reading" ? "running" : "idle",
|
||||
operatorMessage: "task/HAL resume requested",
|
||||
});
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_PLAN_RESUME" },
|
||||
], {
|
||||
operatorMessage: "task/HAL program resumed",
|
||||
preserveMachine: resumedMachine,
|
||||
}).then((status) => {
|
||||
if (shouldContinueTaskHalStatusLoop(state, status)) {
|
||||
startTaskHalStatusLoop({
|
||||
@@ -1801,11 +1883,44 @@ export function createSimulationStore(seed = {}) {
|
||||
}
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
stopTaskHalStatusLoop("step", { operatorMessage: "task/HAL step requested" });
|
||||
const playback = nextProgramRuntimeSamplePlayback(state, 1);
|
||||
const playbackPatch = applyProgramPlaybackUiPatch(state, {
|
||||
activeLine: playback.activeLine,
|
||||
axisPose: playback.axisPose,
|
||||
kinsType: playback.kinsType,
|
||||
rtcpState: playback.rtcpState,
|
||||
motionIndex: playback.motionIndex,
|
||||
sampleIndex: playback.sampleIndex,
|
||||
runtimeFeedback: playback.runtimeFeedback,
|
||||
});
|
||||
const steppedMachine = {
|
||||
...state.machine,
|
||||
mode: "auto",
|
||||
manualPanel: null,
|
||||
interpResumeState: state.machine.interpState === "paused"
|
||||
? state.machine.interpResumeState || "reading"
|
||||
: state.machine.interpState || "reading",
|
||||
interpState: "paused",
|
||||
taskPaused: true,
|
||||
};
|
||||
setState({
|
||||
machine: steppedMachine,
|
||||
runState: "stepping",
|
||||
...playbackPatch,
|
||||
programElapsedSeconds: playback.timing.elapsedSeconds,
|
||||
programRemainingSeconds: playback.timing.remainingSeconds,
|
||||
feed: {
|
||||
...state.feed,
|
||||
currentVelocity: playback.timing.currentVelocity,
|
||||
},
|
||||
operatorMessage: `task/HAL step requested line ${playback.activeLine}`,
|
||||
});
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_PLAN_STEP" },
|
||||
], {
|
||||
taskCycles: 1,
|
||||
operatorMessage: "task/HAL stepped one cycle",
|
||||
preserveMachine: steppedMachine,
|
||||
}).catch(() => {});
|
||||
break;
|
||||
}
|
||||
@@ -2047,6 +2162,10 @@ export function createSimulationStore(seed = {}) {
|
||||
spindle: {
|
||||
...state.spindle,
|
||||
override: clampPercent(state.spindle.override + action.delta, 0, 150),
|
||||
halPins: spindleHalPinsForState({
|
||||
...state.spindle,
|
||||
override: clampPercent(state.spindle.override + action.delta, 0, 150),
|
||||
}),
|
||||
},
|
||||
gmoccapyGui: {
|
||||
...state.gmoccapyGui,
|
||||
@@ -2399,10 +2518,13 @@ export function createSimulationStore(seed = {}) {
|
||||
}
|
||||
dispatch({ type: "TASK_HAL_SESSION_READY", session });
|
||||
const status = await state.taskHalRuntime.readStatus();
|
||||
const statusPreserveMachine = isTaskHalRunPausedByOperator(state)
|
||||
? { ...state.machine }
|
||||
: preserveMachine;
|
||||
dispatch({
|
||||
type: "TASK_HAL_STATUS_APPLIED",
|
||||
status,
|
||||
preserveMachine,
|
||||
preserveMachine: statusPreserveMachine,
|
||||
operatorMessage: `LinuxCNC task/HAL session ready ${session.programPath || "-"}`,
|
||||
});
|
||||
return session;
|
||||
@@ -2418,8 +2540,14 @@ export function createSimulationStore(seed = {}) {
|
||||
const expectedProgramPath = expectedTaskHalProgramPathForState(state);
|
||||
if (!state.taskHalSession || (expectedProgramPath && state.taskHalSession.programPath !== expectedProgramPath)) {
|
||||
await initializeTaskHalSession({ openProgram: true });
|
||||
if (isTaskHalRunPausedByOperator(state)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const loadedMotionPlan = await loadTaskHalMotionPlanForSession(state.taskHalSession);
|
||||
if (isTaskHalRunPausedByOperator(state)) {
|
||||
return null;
|
||||
}
|
||||
if (!loadedMotionPlan) {
|
||||
setState({ operatorMessage: "run blocked: task/HAL feed motion plan not loaded" });
|
||||
return null;
|
||||
@@ -2430,10 +2558,16 @@ export function createSimulationStore(seed = {}) {
|
||||
setState({ operatorMessage: ready.operatorMessage });
|
||||
return null;
|
||||
}
|
||||
if (isTaskHalRunPausedByOperator(state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
stopTaskHalStatusLoop("restarted", {
|
||||
operatorMessage: "task/HAL status loop restarting",
|
||||
});
|
||||
if (isTaskHalRunPausedByOperator(state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const status = await runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_SET_STATE", state: "ON" },
|
||||
@@ -2444,6 +2578,9 @@ export function createSimulationStore(seed = {}) {
|
||||
operatorMessage: `task/HAL program run ${ready.profileId} ${ready.kinematicsModuleId}`,
|
||||
allowFixtureSession: false,
|
||||
});
|
||||
if (isTaskHalRunPausedByOperator(state)) {
|
||||
return status;
|
||||
}
|
||||
if (shouldContinueTaskHalStatusLoop(state, status)) {
|
||||
startTaskHalStatusLoop({
|
||||
profileId: ready.profileId,
|
||||
@@ -2548,20 +2685,26 @@ export function createSimulationStore(seed = {}) {
|
||||
mode: "auto",
|
||||
manualPanel: null,
|
||||
allHomed: true,
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
interpState: "reading",
|
||||
interpResumeState: "reading",
|
||||
taskPaused: false,
|
||||
},
|
||||
runState: "idle",
|
||||
runState: "running",
|
||||
operatorMessage: "RUN ready: motion plan source ready; preparing task/HAL session",
|
||||
});
|
||||
const expectedProgramPath = expectedTaskHalProgramPathForState(state);
|
||||
if (!state.taskHalSession || (expectedProgramPath && state.taskHalSession.programPath !== expectedProgramPath)) {
|
||||
setState({ operatorMessage: "RUN preparing: initializing task/HAL session" });
|
||||
await initializeTaskHalSession({ openProgram: true });
|
||||
if (isTaskHalRunPausedByOperator(state)) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
setState({ operatorMessage: "RUN preparing: loading task/HAL motion plan" });
|
||||
const loadedMotionPlan = await loadTaskHalMotionPlanWithSessionRetry();
|
||||
if (isTaskHalRunPausedByOperator(state)) {
|
||||
return state;
|
||||
}
|
||||
if (!loadedMotionPlan) {
|
||||
setState({ operatorMessage: "run blocked: task/HAL feed motion plan not loaded" });
|
||||
return state;
|
||||
@@ -2571,9 +2714,15 @@ export function createSimulationStore(seed = {}) {
|
||||
setState({ operatorMessage: ready.operatorMessage });
|
||||
return state;
|
||||
}
|
||||
if (isTaskHalRunPausedByOperator(state)) {
|
||||
return state;
|
||||
}
|
||||
stopTaskHalStatusLoop("restarted", {
|
||||
operatorMessage: "task/HAL status loop restarting",
|
||||
});
|
||||
if (isTaskHalRunPausedByOperator(state)) {
|
||||
return state;
|
||||
}
|
||||
setState({
|
||||
activeLine: state.programStartLine || 1,
|
||||
programExecutionMotionIndex: 0,
|
||||
@@ -2592,6 +2741,9 @@ export function createSimulationStore(seed = {}) {
|
||||
operatorMessage: `task/HAL program run ${ready.profileId} ${ready.kinematicsModuleId}`,
|
||||
allowFixtureSession: false,
|
||||
});
|
||||
if (isTaskHalRunPausedByOperator(state)) {
|
||||
return state;
|
||||
}
|
||||
if (shouldContinueTaskHalStatusLoop(state, status)) {
|
||||
startTaskHalStatusLoop({
|
||||
profileId: ready.profileId,
|
||||
@@ -2703,7 +2855,7 @@ export function createSimulationStore(seed = {}) {
|
||||
taskCycles: loop.batchSize,
|
||||
});
|
||||
const status = await state.taskHalRuntime.readStatus();
|
||||
if (state.taskHalStatusLoop?.sequence !== sequence) {
|
||||
if (state.taskHalStatusLoop?.sequence !== sequence || state.taskHalStatusLoop?.active !== true) {
|
||||
return status;
|
||||
}
|
||||
dispatch({
|
||||
@@ -3187,6 +3339,7 @@ function createLinuxCncProcessMonitor(state = {}) {
|
||||
const spindleActualRpm = state.spindle?.enabled
|
||||
? spindleCommandRpm * (Number(state.spindle?.override || 100) / 100)
|
||||
: 0;
|
||||
const spindleHalPins = state.spindle?.halPins || {};
|
||||
const toolChange = {
|
||||
toolInSpindle: Number(toolRuntime.toolInSpindle || toolDb.toolInSpindle || 0),
|
||||
toolFromPocket: Number(toolRuntime.toolFromPocket || toolDb.toolFromPocket || 0),
|
||||
@@ -3280,10 +3433,11 @@ function createLinuxCncProcessMonitor(state = {}) {
|
||||
actualRpm: spindleActualRpm,
|
||||
overridePercent: Number(state.spindle?.override || 0),
|
||||
halPins: {
|
||||
on: Number(halPins["spindle.0.on"]?.value ?? (state.spindle?.enabled ? 1 : 0)),
|
||||
forward: Number(halPins["spindle.0.forward"]?.value ?? (state.spindle?.direction === "forward" ? 1 : 0)),
|
||||
reverse: Number(halPins["spindle.0.reverse"]?.value ?? (state.spindle?.direction === "reverse" ? 1 : 0)),
|
||||
speedOut: Number(halPins["spindle.0.speed-out"]?.value ?? spindleActualRpm),
|
||||
on: Number(spindleHalPins.on ?? halPins["spindle.0.on"]?.value ?? (state.spindle?.enabled ? 1 : 0)),
|
||||
forward: Number(spindleHalPins.forward ?? halPins["spindle.0.forward"]?.value ?? (state.spindle?.direction === "forward" ? 1 : 0)),
|
||||
reverse: Number(spindleHalPins.reverse ?? halPins["spindle.0.reverse"]?.value ?? (state.spindle?.direction === "reverse" ? 1 : 0)),
|
||||
speedOut: Number(spindleHalPins.speedOut ?? halPins["spindle.0.speed-out"]?.value ?? spindleActualRpm),
|
||||
atSpeed: Number(spindleHalPins.atSpeed ?? halPins["spindle.0.at-speed"]?.value ?? (state.spindle?.enabled ? 1 : 0)),
|
||||
},
|
||||
},
|
||||
feed: {
|
||||
@@ -3779,6 +3933,12 @@ function shouldContinueTaskHalStatusLoop(state = {}, status = {}) {
|
||||
return !aborted && !paused && !complete && (interpState === "reading" || taskMode === "mdi");
|
||||
}
|
||||
|
||||
function isTaskHalRunPausedByOperator(state = {}) {
|
||||
return state.runState === "paused"
|
||||
|| state.machine?.interpState === "paused"
|
||||
|| state.machine?.taskPaused === true;
|
||||
}
|
||||
|
||||
function createStoppedProgramStatePatch(state, {
|
||||
reason = "stopped",
|
||||
operatorMessage = "program stopped",
|
||||
@@ -4404,9 +4564,9 @@ function homeAxisPoseForState(state = {}) {
|
||||
if (state.profile?.id === "xyzac-trt" || state.profile?.id === "xyzbc-trt") {
|
||||
return clampAxisPoseToProfile({
|
||||
...initialAxisPose,
|
||||
x: 43,
|
||||
y: -32.15,
|
||||
z: -11.306,
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 10,
|
||||
}, state.profile);
|
||||
}
|
||||
const pose = { ...initialAxisPose };
|
||||
@@ -4489,6 +4649,12 @@ function executeMdiCommand(state, rawCommand) {
|
||||
rpm: parsed.spindleRpm ?? state.spindle.rpm,
|
||||
enabled: parsed.spindleEnabled ?? state.spindle.enabled,
|
||||
direction: parsed.spindleDirection ?? state.spindle.direction,
|
||||
halPins: spindleHalPinsForState({
|
||||
...state.spindle,
|
||||
rpm: parsed.spindleRpm ?? state.spindle.rpm,
|
||||
enabled: parsed.spindleEnabled ?? state.spindle.enabled,
|
||||
direction: parsed.spindleDirection ?? state.spindle.direction,
|
||||
}),
|
||||
}
|
||||
: state.spindle,
|
||||
coolant: parsed.coolantPatch
|
||||
@@ -4592,6 +4758,33 @@ function applyMdiMCode(parsed, value) {
|
||||
}
|
||||
}
|
||||
|
||||
function spindleHalPinsForState(spindle = {}) {
|
||||
const enabled = Boolean(spindle.enabled) && spindle.direction !== "stop";
|
||||
const speedOut = enabled
|
||||
? Number(spindle.rpm || 0) * (Number(spindle.override || 100) / 100)
|
||||
: 0;
|
||||
return {
|
||||
on: enabled ? 1 : 0,
|
||||
forward: spindle.direction === "forward" ? 1 : 0,
|
||||
reverse: spindle.direction === "reverse" ? 1 : 0,
|
||||
speedOut,
|
||||
atSpeed: enabled ? 1 : 0,
|
||||
};
|
||||
}
|
||||
|
||||
function stoppedSpindleState(spindle = {}) {
|
||||
return {
|
||||
...spindle,
|
||||
enabled: false,
|
||||
direction: "stop",
|
||||
halPins: spindleHalPinsForState({
|
||||
...spindle,
|
||||
enabled: false,
|
||||
direction: "stop",
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSpindleDirection(direction) {
|
||||
const value = String(direction || "stop").toLowerCase();
|
||||
return value === "forward" || value === "reverse" ? value : "stop";
|
||||
|
||||
@@ -162,6 +162,12 @@ body {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.axis-tool-button[data-paused="true"],
|
||||
.axis-tool-button[data-active="true"] {
|
||||
background: #d8e9f7;
|
||||
box-shadow: inset 1px 1px #7d7d7d, inset -1px -1px #ffffff;
|
||||
}
|
||||
|
||||
.axis-text-tool {
|
||||
font: 700 18px/1 "Times New Roman", serif;
|
||||
text-decoration: underline;
|
||||
|
||||
@@ -28,7 +28,7 @@ export const AXIS_BUTTON_PARITY = [
|
||||
{ id: "toolbar-load", action: "open", sourceSymbol: "commands.open_file", sourceLines: "axis.py:2243-2262", expected: "open local G-code" },
|
||||
{ id: "toolbar-reload", action: "reload", sourceSymbol: "commands.reload_file", sourceLines: "axis.py:2296-2297", expected: "reload active program" },
|
||||
{ id: "toolbar-run", action: "run", sourceSymbol: "commands.task_run", sourceLines: "axis.py:2308-2320", expected: "start AUTO run" },
|
||||
{ id: "toolbar-pause-resume", action: "pause", sourceSymbol: "commands.task_pauseresume", sourceLines: "axis.py:2353-2363", expected: "pause or resume based on paused state" },
|
||||
{ id: "toolbar-pause-resume", action: "pause-resume", sourceSymbol: "commands.task_pauseresume", sourceLines: "axis.py:2433-2443 / axis.tcl:543-549", expected: "AUTO_PAUSE when interpreter is not idle; AUTO_RESUME when paused" },
|
||||
{ id: "toolbar-step", action: "step", sourceSymbol: "commands.task_step", sourceLines: "axis.py:2321-2327", expected: "single step" },
|
||||
{ id: "toolbar-stop", action: "stop", sourceSymbol: "commands.task_stop", sourceLines: "axis.py:2365-2372", expected: "abort current task" },
|
||||
{ id: "toolbar-view-z", action: "view-z", sourceSymbol: "commands.set_view_z", sourceLines: "axis.py:2208-2215", expected: "Z view" },
|
||||
@@ -58,6 +58,7 @@ export const AXIS_BUTTON_PARITY = [
|
||||
{ id: "manual-optional-stop", action: "optional-stop", sourceSymbol: "commands.toggle_optional_stop", sourceLines: "axis.py:2047-2049", expected: "optional stop state" },
|
||||
{ id: "manual-flood", action: "toggle-flood", sourceSymbol: "commands.flood_toggle", sourceLines: "axis.py:3176", expected: "flood coolant toggle" },
|
||||
{ id: "manual-mist", action: "toggle-mist", sourceSymbol: "commands.mist_toggle", sourceLines: "axis.py:3175", expected: "mist coolant toggle" },
|
||||
{ id: "mdi-input", action: "mdi-input", sourceSymbol: "commands.mdi_command.entry", sourceLines: "axis.py:2413-2417,2499-2516", expected: "edit pending MDI command text before submit/history execution" },
|
||||
{ id: "mdi-submit", action: "mdi-form", sourceSymbol: "commands.send_mdi", sourceLines: "axis.py:2413-2417", expected: "submit MDI command" },
|
||||
{ id: "mdi-history", action: "mdi-history", sourceSymbol: "commands.mdi_history_butt_1", sourceLines: "axis.py:2499-2516", expected: "run/restore MDI history command" },
|
||||
{ id: "pyvcp-identity", action: "kins-identity", sourceSymbol: "pyvcp.type0-button -> halui.mdi-command-00", sourceLines: "switchkins_postgui.hal", expected: "M429 sets identity kins" },
|
||||
@@ -144,7 +145,8 @@ function renderMenu(element, state, dispatch) {
|
||||
["home-all", "Home All"],
|
||||
["run-ready", "Run Ready"],
|
||||
["run", "Run"],
|
||||
[state.runState === "paused" ? "resume" : "pause", state.runState === "paused" ? "Resume" : "Pause"],
|
||||
["pause", "Pause"],
|
||||
["resume", "Resume"],
|
||||
["step", "Step"],
|
||||
["stop", "Stop"],
|
||||
])}
|
||||
@@ -166,36 +168,50 @@ function renderMenu(element, state, dispatch) {
|
||||
}
|
||||
|
||||
function renderToolbar(element, state, dispatch) {
|
||||
element.innerHTML = `
|
||||
<input type="file" class="axis-file-input" data-action="OPEN_FILE" accept=".ngc,.nc,.tap,.gcode,.txt" />
|
||||
${toolButton("tbtn_estop", "estop", "Emergency stop", state.machine.estopActive)}
|
||||
${toolButton("tbtn_on", "power", "Machine power", state.machine.powerOn)}
|
||||
${modeToggleTool(state)}
|
||||
${toolButton("btn_load", "open", "Open program")}
|
||||
${toolButton("btn_reload", "reload", "Reload program")}
|
||||
${toolButton("btn_run", "run", "Run program")}
|
||||
${toolButton("tbtn_pause", state.runState === "paused" ? "resume" : "pause", state.runState === "paused" ? "Resume" : "Pause", state.runState === "paused")}
|
||||
${toolButton("btn_step", "step", "Step")}
|
||||
${toolButton("btn_stop", "stop", "Stop")}
|
||||
<span class="axis-toolbar-separator"></span>
|
||||
${smallTextTool("Z", "view-z", "View Z")}
|
||||
${smallTextTool("Y", "view-y", "View Y")}
|
||||
${smallTextTool("X", "view-x", "View X")}
|
||||
${smallTextTool("P", "view-p", "Fit perspective")}
|
||||
${toolButton("tbtn_view_tool_path", "clear-preview", "Clear plot")}
|
||||
`;
|
||||
|
||||
element.__axisLatestState = state;
|
||||
if (element.dataset.mounted !== "true") {
|
||||
element.innerHTML = `
|
||||
<input type="file" class="axis-file-input" data-action="OPEN_FILE" accept=".ngc,.nc,.tap,.gcode,.txt" />
|
||||
${toolButton("tbtn_estop", "estop", "Emergency stop", state.machine.estopActive)}
|
||||
${toolButton("tbtn_on", "power", "Machine power", state.machine.powerOn)}
|
||||
${modeToggleTool(state)}
|
||||
${toolButton("btn_load", "open", "Open program")}
|
||||
${toolButton("btn_reload", "reload", "Reload program")}
|
||||
${toolButton("btn_run", "run", "Run program")}
|
||||
${toolButton("tbtn_pause", "pause-resume", pauseResumeToolbarTitle(state), isProgramPaused(state))}
|
||||
${toolButton("btn_step", "step", "Step")}
|
||||
${toolButton("btn_stop", "stop", "Stop")}
|
||||
<span class="axis-toolbar-separator"></span>
|
||||
${smallTextTool("Z", "view-z", "View Z")}
|
||||
${smallTextTool("Y", "view-y", "View Y")}
|
||||
${smallTextTool("X", "view-x", "View X")}
|
||||
${smallTextTool("P", "view-p", "Fit perspective")}
|
||||
${toolButton("tbtn_view_tool_path", "clear-preview", "Clear plot")}
|
||||
`;
|
||||
element.dataset.mounted = "true";
|
||||
element.addEventListener("click", (event) => {
|
||||
const button = event.target.closest?.(".axis-tool-button[data-action]");
|
||||
if (!button || !element.contains(button)) return;
|
||||
const action = button.dataset.action;
|
||||
if (action === "open") {
|
||||
element.querySelector('[data-action="OPEN_FILE"]').click();
|
||||
return;
|
||||
}
|
||||
runAxisCommand(action, element.__axisLatestState || state, dispatch, element);
|
||||
});
|
||||
element.querySelector('[data-action="OPEN_FILE"]').addEventListener("change", async (event) => {
|
||||
const [file] = event.target.files || [];
|
||||
if (!file) return;
|
||||
dispatch({ type: "LOAD_PROGRAM", filename: file.name, content: await file.text() });
|
||||
event.target.value = "";
|
||||
});
|
||||
}
|
||||
updateToolbarButton(element, "tbtn_estop", "estop", "Emergency stop", state.machine.estopActive);
|
||||
updateToolbarButton(element, "tbtn_on", "power", "Machine power", state.machine.powerOn);
|
||||
updateToolbarModeButton(element, state);
|
||||
updateToolbarButton(element, "tbtn_pause", "pause-resume", pauseResumeToolbarTitle(state), isProgramPaused(state));
|
||||
element.querySelectorAll(".axis-tool-button[data-action]").forEach((button) => {
|
||||
tagAxisControl(button, button.dataset.action);
|
||||
if (button.dataset.action === "open") return;
|
||||
button.addEventListener("click", () => runAxisCommand(button.dataset.action, state, dispatch, element));
|
||||
});
|
||||
element.querySelector('[data-action="open"]').addEventListener("click", () => element.querySelector('[data-action="OPEN_FILE"]').click());
|
||||
element.querySelector('[data-action="OPEN_FILE"]').addEventListener("change", async (event) => {
|
||||
const [file] = event.target.files || [];
|
||||
if (!file) return;
|
||||
dispatch({ type: "LOAD_PROGRAM", filename: file.name, content: await file.text() });
|
||||
event.target.value = "";
|
||||
});
|
||||
}
|
||||
|
||||
@@ -270,7 +286,7 @@ function renderManual(element, state, dispatch) {
|
||||
<button type="button" data-action="toggle-mist">Mist</button>
|
||||
</div>
|
||||
<div class="active-gcodes-label">Active G-Codes:</div>
|
||||
<div class="active-gcodes">G80 G17 G40 G21 G90 G94 G54 G49 G99 G64<br />G97 G91.1 G8 M5 M9 M48 M53 F0 S0</div>
|
||||
${activeGcodesMarkup(state)}
|
||||
</section>
|
||||
`;
|
||||
|
||||
@@ -550,12 +566,13 @@ function monitorAxisRows(axisPose) {
|
||||
`).join("");
|
||||
}
|
||||
|
||||
function toolButton(buttonId, action, title, active = false) {
|
||||
const icon = getGmoccapyIcon(buttonId, active ? "active" : "inactive");
|
||||
function toolButton(buttonId, action, title, active = false, iconId = buttonId) {
|
||||
const icon = getGmoccapyIcon(iconId, active ? "active" : "inactive");
|
||||
const iconMarkup = icon.path
|
||||
? `<img src="${escapeHtml(icon.path)}" alt="" />`
|
||||
: `<span>${escapeHtml(title.slice(0, 1))}</span>`;
|
||||
return `<button type="button" class="axis-tool-button" data-action="${action}" title="${escapeHtml(title)}">${iconMarkup}</button>`;
|
||||
const pausedAttr = buttonId === "tbtn_pause" ? ` data-paused="${active ? "true" : "false"}"` : "";
|
||||
return `<button type="button" class="axis-tool-button" data-tool-id="${escapeHtml(buttonId)}" data-action="${action}" data-active="${active ? "true" : "false"}"${pausedAttr} aria-label="${escapeHtml(title)}" aria-pressed="${active ? "true" : "false"}" title="${escapeHtml(title)}">${iconMarkup}</button>`;
|
||||
}
|
||||
|
||||
function smallTextTool(label, action, title) {
|
||||
@@ -570,6 +587,7 @@ function modeToggleTool(state) {
|
||||
<button
|
||||
type="button"
|
||||
class="axis-tool-button axis-mode-toggle"
|
||||
data-tool-id="toolbar-auto-manual"
|
||||
data-action="toggle-auto-manual"
|
||||
data-mode="${autoMode ? "auto" : "manual"}"
|
||||
title="${escapeHtml(title)}"
|
||||
@@ -577,6 +595,46 @@ function modeToggleTool(state) {
|
||||
`;
|
||||
}
|
||||
|
||||
function updateToolbarButton(element, buttonId, action, title, active = false, iconId = buttonId) {
|
||||
const button = element.querySelector(`[data-tool-id="${buttonId}"]`);
|
||||
if (!button) return;
|
||||
const icon = getGmoccapyIcon(iconId, active ? "active" : "inactive");
|
||||
const iconMarkup = icon.path
|
||||
? `<img src="${escapeHtml(icon.path)}" alt="" />`
|
||||
: `<span>${escapeHtml(title.slice(0, 1))}</span>`;
|
||||
button.dataset.action = action;
|
||||
button.title = title;
|
||||
button.setAttribute("aria-label", title);
|
||||
button.setAttribute("aria-pressed", active ? "true" : "false");
|
||||
button.dataset.active = active ? "true" : "false";
|
||||
if (buttonId === "tbtn_pause") {
|
||||
button.dataset.paused = active ? "true" : "false";
|
||||
}
|
||||
if (button.innerHTML !== iconMarkup) button.innerHTML = iconMarkup;
|
||||
}
|
||||
|
||||
function isProgramPaused(state) {
|
||||
return state.runState === "paused" ||
|
||||
state.machine?.interpState === "paused" ||
|
||||
state.machine?.taskPaused === true;
|
||||
}
|
||||
|
||||
function pauseResumeToolbarTitle(state) {
|
||||
return isProgramPaused(state) ? "Resume program" : "Pause program";
|
||||
}
|
||||
|
||||
function updateToolbarModeButton(element, state) {
|
||||
const button = element.querySelector('[data-tool-id="toolbar-auto-manual"]');
|
||||
if (!button) return;
|
||||
const autoMode = state.machine?.mode === "auto";
|
||||
const label = autoMode ? "MAN" : "AUTO";
|
||||
const title = autoMode ? "Switch to Manual mode" : "Switch to Auto mode";
|
||||
button.dataset.action = "toggle-auto-manual";
|
||||
button.dataset.mode = autoMode ? "auto" : "manual";
|
||||
button.title = title;
|
||||
if (button.textContent !== label) button.textContent = label;
|
||||
}
|
||||
|
||||
function menuButton(label, entries) {
|
||||
return `
|
||||
<div class="axis-menu">
|
||||
@@ -653,6 +711,28 @@ function mdiQuickCommands(state) {
|
||||
].filter((command, index, commands) => command && commands.indexOf(command) === index).slice(0, 8);
|
||||
}
|
||||
|
||||
function activeGcodesMarkup(state) {
|
||||
const spindleCode = state.spindle?.direction === "forward"
|
||||
? "M3"
|
||||
: state.spindle?.direction === "reverse"
|
||||
? "M4"
|
||||
: "M5";
|
||||
const coolantCode = state.coolant?.flood
|
||||
? "M8"
|
||||
: state.coolant?.mist
|
||||
? "M7"
|
||||
: "M9";
|
||||
const feedRate = formatNumber(state.feed?.feedRate || 0, 0);
|
||||
const spindleRpm = state.spindle?.enabled ? formatNumber(state.spindle?.rpm || 0, 0) : "0";
|
||||
const modalLine = "G80 G17 G40 G21 G90 G94 G54 G49 G99 G64";
|
||||
const machineLine = `G97 G91.1 G8 ${spindleCode} ${coolantCode} M48 M53 F${feedRate} S${spindleRpm}`;
|
||||
return `
|
||||
<div class="active-gcodes" data-active-gcodes="${escapeHtml(`${modalLine} ${machineLine}`)}">
|
||||
${escapeHtml(modalLine)}<br />${escapeHtml(machineLine)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function runAxisCommand(command, state, dispatch, root) {
|
||||
const selectedJoint = Number(state.machine?.selectedJoint ?? 0);
|
||||
const selectedAxis = jointAxis(selectedJoint);
|
||||
@@ -662,6 +742,7 @@ function runAxisCommand(command, state, dispatch, root) {
|
||||
"toggle-auto-manual": () => dispatch({ type: "SET_MODE", mode: state.machine?.mode === "auto" ? "manual" : "auto" }),
|
||||
reload: () => dispatch({ type: "RELOAD_PROGRAM" }),
|
||||
run: () => dispatch({ type: "RUN_FROM_OPERATOR" }),
|
||||
"pause-resume": () => dispatch({ type: "PAUSE_RESUME" }),
|
||||
pause: () => dispatch({ type: "PAUSE" }),
|
||||
resume: () => dispatch({ type: "RESUME" }),
|
||||
step: () => dispatch({ type: "STEP" }),
|
||||
|
||||
Reference in New Issue
Block a user