同步五轴仿真文档和验证证据
This commit is contained in:
@@ -856,6 +856,9 @@ export function createSimulationStore(seed = {}) {
|
||||
}
|
||||
break;
|
||||
case "TASK_HAL_SESSION_READY":
|
||||
if (action.session?.programSourceRel && action.session.programSourceRel !== state.machineFileStaging?.selectedGcodeSourceRel) {
|
||||
break;
|
||||
}
|
||||
setState({
|
||||
taskHalSession: action.session,
|
||||
taskHalFallbackReason: null,
|
||||
@@ -898,6 +901,12 @@ export function createSimulationStore(seed = {}) {
|
||||
operatorMessage: action.operatorMessage || state.operatorMessage,
|
||||
});
|
||||
break;
|
||||
case "TASK_HAL_PROGRAM_STOPPED":
|
||||
setState(createStoppedProgramStatePatch(state, {
|
||||
reason: action.reason || "stopped",
|
||||
operatorMessage: action.operatorMessage || "task/HAL program stopped",
|
||||
}));
|
||||
break;
|
||||
case "TASK_HAL_COMMAND_FAILED":
|
||||
setState({
|
||||
taskHalFallbackReason: action.error,
|
||||
@@ -1243,9 +1252,21 @@ export function createSimulationStore(seed = {}) {
|
||||
stopTaskHalStatusLoop(action.type === "ABORT" ? "aborted" : "stopped", {
|
||||
operatorMessage: action.type === "ABORT" ? "task/HAL abort requested" : "task/HAL stop requested",
|
||||
});
|
||||
setState(createStoppedProgramStatePatch(state, {
|
||||
reason: action.type === "ABORT" ? "aborted" : "stopped",
|
||||
operatorMessage: action.type === "ABORT" ? "task abort requested" : "program stop requested",
|
||||
}));
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_ABORT" },
|
||||
], { operatorMessage: action.type === "ABORT" ? "task/HAL abort complete" : "task/HAL program stopped" }).catch(() => {});
|
||||
], { operatorMessage: action.type === "ABORT" ? "task/HAL abort complete" : "task/HAL program stopped" })
|
||||
.then(() => {
|
||||
dispatch({
|
||||
type: "TASK_HAL_PROGRAM_STOPPED",
|
||||
reason: action.type === "ABORT" ? "aborted" : "stopped",
|
||||
operatorMessage: action.type === "ABORT" ? "task/HAL abort complete" : "task/HAL program stopped",
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
break;
|
||||
}
|
||||
setState({
|
||||
@@ -1304,8 +1325,8 @@ export function createSimulationStore(seed = {}) {
|
||||
{ type: "EMC_TASK_PLAN_RESUME" },
|
||||
], {
|
||||
operatorMessage: "task/HAL program resumed",
|
||||
}).then(() => {
|
||||
if (state.runState === "running" || state.machine.interpState === "reading") {
|
||||
}).then((status) => {
|
||||
if (shouldContinueTaskHalStatusLoop(state, status)) {
|
||||
startTaskHalStatusLoop({
|
||||
operatorMessage: "task/HAL status loop resumed",
|
||||
});
|
||||
@@ -1337,7 +1358,9 @@ export function createSimulationStore(seed = {}) {
|
||||
}
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
stopTaskHalStatusLoop("step", { operatorMessage: "task/HAL step requested" });
|
||||
runTaskHalCommandSequence([], {
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_PLAN_STEP" },
|
||||
], {
|
||||
taskCycles: 1,
|
||||
operatorMessage: "task/HAL stepped one cycle",
|
||||
}).catch(() => {});
|
||||
@@ -1412,6 +1435,20 @@ export function createSimulationStore(seed = {}) {
|
||||
break;
|
||||
}
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
setState({
|
||||
machine: {
|
||||
...state.machine,
|
||||
mode: "manual",
|
||||
allHomed: true,
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
},
|
||||
runState: "idle",
|
||||
axisPose: initialAxisPose,
|
||||
programRuntimeFeedback: null,
|
||||
operatorMessage: "task/HAL home requested",
|
||||
});
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_JOINT_HOME", joint: -1 },
|
||||
], {
|
||||
@@ -1421,6 +1458,7 @@ export function createSimulationStore(seed = {}) {
|
||||
allHomed: true,
|
||||
},
|
||||
}).catch(() => {});
|
||||
break;
|
||||
}
|
||||
setState({
|
||||
machine: {
|
||||
@@ -1705,6 +1743,7 @@ export function createSimulationStore(seed = {}) {
|
||||
if (!state.taskHalRuntime?.loaded || !state.machineFileStaging?.save?.files?.length) {
|
||||
return null;
|
||||
}
|
||||
stopTaskHalStatusLoop("session-initialize", { notify: false });
|
||||
const preserveMachine = {
|
||||
powerOn: state.machine.powerOn,
|
||||
estopActive: state.machine.estopActive,
|
||||
@@ -1719,6 +1758,9 @@ export function createSimulationStore(seed = {}) {
|
||||
save: state.machineFileStaging.save,
|
||||
selectedProgramRel: state.machineFileStaging.selectedGcodeSourceRel,
|
||||
});
|
||||
if (session.programSourceRel && session.programSourceRel !== state.machineFileStaging.selectedGcodeSourceRel) {
|
||||
return null;
|
||||
}
|
||||
await state.taskHalRuntime.resetSession?.();
|
||||
await state.taskHalRuntime.initSession({
|
||||
profileId: session.profileId,
|
||||
@@ -1746,6 +1788,9 @@ export function createSimulationStore(seed = {}) {
|
||||
...deriveTaskHalCyclePeriods(state),
|
||||
taskCycles: 1,
|
||||
});
|
||||
if (session.programSourceRel && session.programSourceRel !== state.machineFileStaging.selectedGcodeSourceRel) {
|
||||
return null;
|
||||
}
|
||||
dispatch({ type: "TASK_HAL_SESSION_READY", session });
|
||||
const status = await state.taskHalRuntime.readStatus();
|
||||
dispatch({
|
||||
@@ -2345,11 +2390,12 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
: rawTaskState;
|
||||
const taskMode = normalizeLinuxCncTaskMode(preserveMachine?.mode || ui.taskMode || task.mode || state.machine.mode);
|
||||
const interpState = normalizeTaskHalInterpState(ui.interpState || task.interpState);
|
||||
const allHomed = Boolean(preserveMachine?.allHomed ?? state.machine.allHomed);
|
||||
const activeLine = state.programStartLine + Math.max(Number(ui.activeLine || 1) - 1, 0);
|
||||
const kinsType = resolveTaskHalKinsType(state, status, activeLine);
|
||||
const axisPose = resolveTaskHalAxisPose(state, status);
|
||||
const currentVelocity = Number.isFinite(ui.currentVelocity) && ui.currentVelocity > 0
|
||||
? ui.currentVelocity
|
||||
const currentVelocity = Number.isFinite(ui.currentVelocity)
|
||||
? Math.max(ui.currentVelocity, 0)
|
||||
: state.feed.currentVelocity;
|
||||
const paused = interpState === "paused" || motion.paused === true;
|
||||
const aborted = motion.aborted === true;
|
||||
@@ -2400,7 +2446,8 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
interpState,
|
||||
interpResumeState: paused ? state.machine.interpResumeState || "reading" : interpState,
|
||||
taskPaused: paused,
|
||||
allHomed: Boolean(preserveMachine?.allHomed ?? state.machine.allHomed),
|
||||
allHomed,
|
||||
noForceHoming: Boolean(state.machine.noForceHoming),
|
||||
},
|
||||
runState,
|
||||
taskHalStatusLoop: loopSequence === null
|
||||
@@ -2440,6 +2487,33 @@ function shouldContinueTaskHalStatusLoop(state = {}, status = {}) {
|
||||
return !aborted && !paused && !complete && (interpState === "reading" || taskMode === "mdi");
|
||||
}
|
||||
|
||||
function createStoppedProgramStatePatch(state, {
|
||||
reason = "stopped",
|
||||
operatorMessage = "program stopped",
|
||||
} = {}) {
|
||||
return {
|
||||
machine: {
|
||||
...state.machine,
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
},
|
||||
runState: reason === "aborted" ? "stopped" : reason,
|
||||
taskHalStatusLoop: {
|
||||
...state.taskHalStatusLoop,
|
||||
active: false,
|
||||
sequence: Number(state.taskHalStatusLoop?.sequence || 0) + 1,
|
||||
stopReason: reason,
|
||||
lastStatusAt: new Date().toISOString(),
|
||||
},
|
||||
feed: {
|
||||
...state.feed,
|
||||
currentVelocity: 0,
|
||||
},
|
||||
operatorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveTaskHalKinsType(state, status, activeLine) {
|
||||
const ui = status?.ui || {};
|
||||
const numeric = Number(ui.switchkinsType);
|
||||
|
||||
@@ -459,18 +459,18 @@ button:active {
|
||||
}
|
||||
|
||||
.gcode-row.active {
|
||||
background: #242424;
|
||||
color: #202020;
|
||||
background: #21a553;
|
||||
color: #0c2b17;
|
||||
border-left-color: #2ebf63;
|
||||
}
|
||||
|
||||
.gcode-row[data-line-status="done"] {
|
||||
background: #eef6ec;
|
||||
border-left-color: #79a96c;
|
||||
background: #eeeeee;
|
||||
border-left-color: #b8b8b8;
|
||||
}
|
||||
|
||||
.gcode-row[data-line-status="running"] {
|
||||
background: #242424;
|
||||
background: #21a553;
|
||||
border-left-color: #2ebf63;
|
||||
}
|
||||
|
||||
@@ -480,7 +480,7 @@ button:active {
|
||||
.gcode-row[data-line-status="running"] span,
|
||||
.gcode-row[data-line-status="running"] code,
|
||||
.gcode-row[data-line-status="running"] small {
|
||||
color: #f2f2f2;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.gcode-row code {
|
||||
|
||||
@@ -201,14 +201,12 @@ function renderGcode(element, state, dispatch) {
|
||||
const rows = state.programLines
|
||||
.map((line, index) => {
|
||||
const lineNumber = state.programStartLine + index;
|
||||
const execution = state.programLineExecution?.[lineNumber] || null;
|
||||
const active = lineNumber === state.activeLine ? " active" : "";
|
||||
const status = execution?.status || (lineNumber < state.activeLine ? "done" : "pending");
|
||||
const active = lineNumber === currentLine ? " active" : "";
|
||||
const status = active ? "running" : lineNumber < currentLine ? "done" : "pending";
|
||||
return `
|
||||
<li class="gcode-row${active}" data-program-line="${lineNumber}" data-line-status="${escapeHtml(status)}">
|
||||
<span>${lineNumber}</span>
|
||||
<code>${escapeHtml(line)}</code>
|
||||
<small data-line-execution="${lineNumber}">${formatLineExecution(execution, active)}</small>
|
||||
</li>
|
||||
`;
|
||||
})
|
||||
@@ -216,7 +214,7 @@ function renderGcode(element, state, dispatch) {
|
||||
const programEndLine = state.programStartLine + Math.max(state.programLines.length - 1, 0);
|
||||
const progressSpan = Math.max(programEndLine - state.programStartLine, 1);
|
||||
const progress = Math.min(
|
||||
Math.max(((state.activeLine - state.programStartLine) / progressSpan) * 100, 0),
|
||||
Math.max(((currentLine - state.programStartLine) / progressSpan) * 100, 0),
|
||||
100,
|
||||
);
|
||||
|
||||
@@ -238,7 +236,7 @@ function renderGcode(element, state, dispatch) {
|
||||
</div>
|
||||
<ol class="gcode-list" start="${state.programStartLine}">${rows}</ol>
|
||||
<div class="gcode-progress">
|
||||
<span>${state.activeLine} / ${programEndLine}</span>
|
||||
<span>${currentLine} / ${programEndLine}</span>
|
||||
<div><i style="width: ${progress}%"></i></div>
|
||||
</div>
|
||||
<section class="mdi-panel" data-mdi-mode="${state.machine.mode === "mdi"}">
|
||||
@@ -284,6 +282,16 @@ function renderGcode(element, state, dispatch) {
|
||||
if (!linuxCncSourceSelect.value) return;
|
||||
dispatch({ type: "LOAD_LINUXCNC_GCODE_SOURCE", sourceRel: linuxCncSourceSelect.value });
|
||||
});
|
||||
|
||||
scrollGcodeListToCurrentLine(element, currentLine);
|
||||
}
|
||||
|
||||
function scrollGcodeListToCurrentLine(element, currentLine) {
|
||||
const list = element.querySelector(".gcode-list");
|
||||
const row = element.querySelector(`.gcode-row[data-program-line="${currentLine}"]`);
|
||||
if (!list || !row) return;
|
||||
const targetTop = row.offsetTop - list.offsetTop - (list.clientHeight - row.clientHeight) / 2;
|
||||
list.scrollTop = Math.max(targetTop, 0);
|
||||
}
|
||||
|
||||
function currentGcodeExecutionLine(state) {
|
||||
@@ -471,21 +479,6 @@ function formatProgramRuntimeDtg(state) {
|
||||
return `DTG ${formatNumber(dtg.x, 3)} / ${formatNumber(dtg.y, 3)} / ${formatNumber(dtg.z, 3)} distance ${formatNumber(feedback.distanceToGo, 3)}`;
|
||||
}
|
||||
|
||||
function formatLineExecution(execution, active) {
|
||||
if (!execution) return active ? "running" : "pending";
|
||||
const axes = execution.axisPose || {};
|
||||
return [
|
||||
execution.status || (active ? "running" : "done"),
|
||||
`F ${formatNumber(execution.feed, 1)}`,
|
||||
`X ${formatNumber(axes.x, 3)}`,
|
||||
`Y ${formatNumber(axes.y, 3)}`,
|
||||
`Z ${formatNumber(axes.z, 3)}`,
|
||||
`A ${formatNumber(axes.a, 3)}`,
|
||||
`C ${formatNumber(axes.c, 3)}`,
|
||||
`cycle ${execution.taskCycle || 0}/${execution.servoCycle || 0}`,
|
||||
].join(" | ");
|
||||
}
|
||||
|
||||
function formatDuration(seconds) {
|
||||
const safeSeconds = Math.max(Number(seconds) || 0, 0);
|
||||
const minutes = Math.floor(safeSeconds / 60);
|
||||
@@ -643,6 +636,7 @@ function renderSpindleCoolant(element, state, dispatch) {
|
||||
}
|
||||
|
||||
function renderBottomControls(element, state, dispatch) {
|
||||
const promptableActions = new Set(["RUN", "STOP", "PAUSE", "RESUME", "STEP", "HOME"]);
|
||||
const controls = [
|
||||
["Open", "OPEN", null],
|
||||
["Reload", "RELOAD", () => dispatch({ type: "RELOAD_PROGRAM" })],
|
||||
@@ -669,9 +663,13 @@ function renderBottomControls(element, state, dispatch) {
|
||||
${controls
|
||||
.map(([label, action]) => {
|
||||
const gate = bottomControlGate(state, action);
|
||||
const disabled = gate.allowed ? "" : " disabled";
|
||||
const promptable = promptableActions.has(action);
|
||||
const disabled = gate.allowed || promptable ? "" : " disabled";
|
||||
const title = gate.allowed ? "" : ` title="${escapeHtml(gate.operatorMessage || "blocked")}"`;
|
||||
return `<button type="button" data-action="${action}"${disabled}${title}>${label}</button>`;
|
||||
const blockedAttrs = gate.allowed
|
||||
? ` data-command-ready="true" aria-disabled="false"`
|
||||
: ` data-command-ready="false" aria-disabled="${promptable ? "true" : "false"}"`;
|
||||
return `<button type="button" data-action="${action}"${disabled}${title}${blockedAttrs}>${label}</button>`;
|
||||
})
|
||||
.join("")}
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user