完善云端RUN执行反馈
@@ -90,17 +90,26 @@ try {
|
|||||||
await wait(800);
|
await wait(800);
|
||||||
await captureStep("01-initial-ui", "初始界面", "确认浏览器应用、五轴预览区、G-code 区和 DRO 面板已渲染。");
|
await captureStep("01-initial-ui", "初始界面", "确认浏览器应用、五轴预览区、G-code 区和 DRO 面板已渲染。");
|
||||||
|
|
||||||
await page.evaluate(async ({ ini }) => {
|
await page.evaluate(async ({ ini, gcode, sourceRel }) => {
|
||||||
await window.webRtcp5AxisSimulation.stageMachineFiles({ iniText: ini, storageMode: "memory" });
|
await window.webRtcp5AxisSimulation.stageMachineFiles({
|
||||||
}, { ini: iniText });
|
iniText: ini,
|
||||||
|
storageMode: "memory",
|
||||||
|
sourceTextOverrides: {
|
||||||
|
[sourceRel]: gcode,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, { ini: iniText, gcode: gcodeText, sourceRel: VENDORED_GCODE_REL });
|
||||||
await waitForState((state) => state.machineFileStaging?.status === "staged", 20000, "test INI staged");
|
await waitForState((state) => state.machineFileStaging?.status === "staged", 20000, "test INI staged");
|
||||||
await captureStep("02-stage-test-ini", "Stage test_linuxcnc_source INI", "使用 working_run/test_linuxcnc_source/xyzac-trt.ini 重新 staging machine files。");
|
await captureStep("02-stage-test-ini", "Stage test_linuxcnc_source INI", "使用 working_run/test_linuxcnc_source/xyzac-trt.ini 重新 staging machine files。");
|
||||||
|
|
||||||
await page.select('[data-action="select-linuxcnc-gcode-source"]', VENDORED_GCODE_REL);
|
await page.evaluate(({ sourceRel }) => {
|
||||||
|
window.webRtcp5AxisSimulation.dispatch({ type: "LOAD_LINUXCNC_GCODE_SOURCE", sourceRel });
|
||||||
|
}, { sourceRel: VENDORED_GCODE_REL });
|
||||||
await waitForState((state) => (
|
await waitForState((state) => (
|
||||||
state.activeProgram?.endsWith("impeller-7bl-xyzac.ngc") &&
|
state.activeProgram?.endsWith("impeller-7bl-xyzac.ngc") &&
|
||||||
state.programExecutionSourceMode === "linuxcnc-interpreter-wasm" &&
|
state.programExecutionSourceMode === "linuxcnc-interpreter-wasm" &&
|
||||||
state.taskHalSession?.programPath?.endsWith("impeller-7bl-xyzac.ngc")
|
state.taskHalSession?.programPath?.endsWith("impeller-7bl-xyzac.ngc") &&
|
||||||
|
state.loadedSourceBytes === sourceEvidence.gcodeBytes
|
||||||
), 25000, "impeller program loaded");
|
), 25000, "impeller program loaded");
|
||||||
await waitForCanvasReady();
|
await waitForCanvasReady();
|
||||||
await wait(1200);
|
await wait(1200);
|
||||||
@@ -192,6 +201,8 @@ async function captureRunSample(elapsedMs, name, title) {
|
|||||||
taskCycle: state.programRuntimeFeedback?.taskCycle ?? null,
|
taskCycle: state.programRuntimeFeedback?.taskCycle ?? null,
|
||||||
servoCycle: state.programRuntimeFeedback?.cycle ?? null,
|
servoCycle: state.programRuntimeFeedback?.cycle ?? null,
|
||||||
historyLength: state.programRuntimeFeedbackHistory?.length || 0,
|
historyLength: state.programRuntimeFeedbackHistory?.length || 0,
|
||||||
|
lineExecution: state.currentLineExecution,
|
||||||
|
lineExecutionVisible: state.currentLineExecutionVisible,
|
||||||
executedPathPoints: Number(step.dataset.threeExecutedPathPoints || 0),
|
executedPathPoints: Number(step.dataset.threeExecutedPathPoints || 0),
|
||||||
currentSegmentHighlight: step.dataset.threeCurrentSegmentHighlight,
|
currentSegmentHighlight: step.dataset.threeCurrentSegmentHighlight,
|
||||||
};
|
};
|
||||||
@@ -236,6 +247,7 @@ function addChecks() {
|
|||||||
check("TCP/刀轴线可见", previewStep?.dataset?.threeTcpMarker === "sphere" && previewStep?.dataset?.threeToolAxisMarker === "line", `tcp=${previewStep?.dataset?.threeTcpMarker}, axis=${previewStep?.dataset?.threeToolAxisMarker}`),
|
check("TCP/刀轴线可见", previewStep?.dataset?.threeTcpMarker === "sphere" && previewStep?.dataset?.threeToolAxisMarker === "line", `tcp=${previewStep?.dataset?.threeTcpMarker}, axis=${previewStep?.dataset?.threeToolAxisMarker}`),
|
||||||
check("RUN 采样数量", runSamples.length >= 5, `samples=${runSamples.length}`),
|
check("RUN 采样数量", runSamples.length >= 5, `samples=${runSamples.length}`),
|
||||||
check("每行执行高亮同步", runSamples.every((sample) => sample.activeLineMatchesUi), runSamples.map((sample) => `${sample.activeLine}/${sample.activeUiLine}`).join(", ")),
|
check("每行执行高亮同步", runSamples.every((sample) => sample.activeLineMatchesUi), runSamples.map((sample) => `${sample.activeLine}/${sample.activeUiLine}`).join(", ")),
|
||||||
|
check("程序列表显示每行执行过程", runSamples.every((sample) => sample.lineExecution?.line === sample.activeLine && sample.lineExecutionVisible === true), runSamples.map((sample) => `${sample.activeLine}:${sample.lineExecution?.status || "-"}:${sample.lineExecutionVisible}`).join(", ")),
|
||||||
check("执行轨迹可见", runSamples.every((sample) => sample.executedPathPoints >= 1 && sample.currentSegmentHighlight === "ok"), runSamples.map((sample) => `${sample.executedPathPoints}/${sample.currentSegmentHighlight}`).join(", ")),
|
check("执行轨迹可见", runSamples.every((sample) => sample.executedPathPoints >= 1 && sample.currentSegmentHighlight === "ok"), runSamples.map((sample) => `${sample.executedPathPoints}/${sample.currentSegmentHighlight}`).join(", ")),
|
||||||
check("实时轴值来自 task/HAL feedback", runSamples.every((sample) => sample.feedbackSource === "linuxcnc-task-motion-hal-wasm" && sample.droMatchesAxisPose), runSamples.map((sample) => `${sample.feedbackSource}/${sample.droMatchesAxisPose}`).join(", ")),
|
check("实时轴值来自 task/HAL feedback", runSamples.every((sample) => sample.feedbackSource === "linuxcnc-task-motion-hal-wasm" && sample.droMatchesAxisPose), runSamples.map((sample) => `${sample.feedbackSource}/${sample.droMatchesAxisPose}`).join(", ")),
|
||||||
check("RUN feed 不是固定 3600 mm/min", movingVelocities.length > 0 && movingVelocities.every((velocity) => Math.abs(velocity - 3600) > 0.001), runSamples.map((sample) => `${sample.elapsedMs}ms=${sample.velocity}`).join(", ")),
|
check("RUN feed 不是固定 3600 mm/min", movingVelocities.length > 0 && movingVelocities.every((velocity) => Math.abs(velocity - 3600) > 0.001), runSamples.map((sample) => `${sample.elapsedMs}ms=${sample.velocity}`).join(", ")),
|
||||||
@@ -256,6 +268,7 @@ function summarizeState(state) {
|
|||||||
machineProfile: state.machineProfile,
|
machineProfile: state.machineProfile,
|
||||||
iniPath: state.iniConfigReadiness?.path || state.linuxCncIniConfig?.path || null,
|
iniPath: state.iniConfigReadiness?.path || state.linuxCncIniConfig?.path || null,
|
||||||
selectedGcodeSourceRel: state.machineFileStaging?.selectedGcodeSourceRel || null,
|
selectedGcodeSourceRel: state.machineFileStaging?.selectedGcodeSourceRel || null,
|
||||||
|
loadedSourceBytes: state.machineFileStaging?.save?.files?.find((file) => file.sourceRel === state.machineFileStaging?.selectedGcodeSourceRel)?.bytes || null,
|
||||||
taskHalProgramPath: state.taskHalSession?.programPath || null,
|
taskHalProgramPath: state.taskHalSession?.programPath || null,
|
||||||
runState: state.runState,
|
runState: state.runState,
|
||||||
activeLine: state.activeLine,
|
activeLine: state.activeLine,
|
||||||
@@ -267,6 +280,8 @@ function summarizeState(state) {
|
|||||||
axisPose: pickAxes(state.axisPose),
|
axisPose: pickAxes(state.axisPose),
|
||||||
programRuntimeFeedback: state.programRuntimeFeedback,
|
programRuntimeFeedback: state.programRuntimeFeedback,
|
||||||
currentTimingSegment,
|
currentTimingSegment,
|
||||||
|
currentLineExecution: state.programLineExecution?.[state.activeLine] || null,
|
||||||
|
currentLineExecutionVisible: Boolean(state.__activeUiLineExecutionText?.includes("F ") && state.__activeUiLineExecutionText?.includes("cycle")),
|
||||||
feedbackHistoryLength: state.programRuntimeFeedbackHistory?.length || 0,
|
feedbackHistoryLength: state.programRuntimeFeedbackHistory?.length || 0,
|
||||||
taskHalStatusLoop: state.taskHalStatusLoop,
|
taskHalStatusLoop: state.taskHalStatusLoop,
|
||||||
taskHalStatus: state.taskHalStatus ? {
|
taskHalStatus: state.taskHalStatus ? {
|
||||||
@@ -316,7 +331,12 @@ async function waitForCanvasReady(timeoutMs = 20000) {
|
|||||||
async function getStateWithUi() {
|
async function getStateWithUi() {
|
||||||
return page.evaluate(() => {
|
return page.evaluate(() => {
|
||||||
const state = JSON.parse(JSON.stringify(window.webRtcp5AxisSimulation.getState()));
|
const state = JSON.parse(JSON.stringify(window.webRtcp5AxisSimulation.getState()));
|
||||||
state.__activeUiLine = Number(document.querySelector(".gcode-row.active")?.dataset.programLine || 0);
|
const activeRow = document.querySelector(".gcode-row.active");
|
||||||
|
state.__activeUiLine = Number(activeRow?.dataset.programLine || 0);
|
||||||
|
state.__activeUiLineExecutionText = activeRow?.querySelector("[data-line-execution]")?.textContent || "";
|
||||||
|
state.loadedSourceBytes = state.machineFileStaging?.save?.files
|
||||||
|
?.find((file) => file.sourceRel === state.machineFileStaging?.selectedGcodeSourceRel)
|
||||||
|
?.bytes || null;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 272 KiB |
|
After Width: | Height: | Size: 272 KiB |
|
After Width: | Height: | Size: 273 KiB |
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 277 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 225 KiB |
@@ -0,0 +1,345 @@
|
|||||||
|
{
|
||||||
|
"generatedAt": "2026-06-23T07:51:30.866Z",
|
||||||
|
"target": "https://82.156.24.101:8092/",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"name": "01-ready",
|
||||||
|
"screenshot": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-run-stop-after-deploy/01-ready.png",
|
||||||
|
"state": {
|
||||||
|
"runState": "idle",
|
||||||
|
"activeLine": 1,
|
||||||
|
"machine": {
|
||||||
|
"powerOn": true,
|
||||||
|
"estopActive": false,
|
||||||
|
"taskState": "on",
|
||||||
|
"mode": "auto",
|
||||||
|
"interpState": "idle",
|
||||||
|
"interpResumeState": "idle",
|
||||||
|
"taskPaused": false,
|
||||||
|
"allHomed": true,
|
||||||
|
"noForceHoming": false,
|
||||||
|
"jogAxis": "x",
|
||||||
|
"jogIncrement": 1,
|
||||||
|
"mdiCommand": "G0 X0 Y0 Z0",
|
||||||
|
"mdiDistanceMode": "absolute",
|
||||||
|
"resetCount": 0
|
||||||
|
},
|
||||||
|
"rtcpState": "on",
|
||||||
|
"kinsType": "tcp-xyzac",
|
||||||
|
"message": "RUN ready: power on, homed, auto mode",
|
||||||
|
"loop": {
|
||||||
|
"apiName": "web-rtcp-5axis-task-hal-status-loop",
|
||||||
|
"active": false,
|
||||||
|
"sequence": 0,
|
||||||
|
"profileId": null,
|
||||||
|
"iniPath": null,
|
||||||
|
"kinematicsModuleId": null,
|
||||||
|
"tickCount": 0,
|
||||||
|
"batchSize": 5,
|
||||||
|
"intervalMs": 25,
|
||||||
|
"taskPeriodNs": 10000000,
|
||||||
|
"servoPeriodNs": 1000000,
|
||||||
|
"lastStatusAt": null,
|
||||||
|
"lastError": null,
|
||||||
|
"stopReason": null,
|
||||||
|
"semanticBoundary": "js_status_polling_loop_for_linuxcnc_task_hal_motion_status"
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"apiName": "web-rtcp-5axis-program-runtime-feedback",
|
||||||
|
"sourceMode": "linuxcnc-task-motion-hal-wasm",
|
||||||
|
"semanticBoundary": "linuxcnc_task_motion_hal_wasm_simulation_runtime",
|
||||||
|
"sampleIndex": 40,
|
||||||
|
"motionIndex": 0,
|
||||||
|
"line": 1,
|
||||||
|
"motionProgramLine": 1,
|
||||||
|
"halProgramLine": 1,
|
||||||
|
"activeLineSource": "motion-status",
|
||||||
|
"activeLineHalSynced": true,
|
||||||
|
"type": "TASK_MOTION",
|
||||||
|
"timeSeconds": 0,
|
||||||
|
"axisPose": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"a": 0,
|
||||||
|
"b": 0,
|
||||||
|
"c": 0
|
||||||
|
},
|
||||||
|
"currentVelocityMmPerMin": 3600,
|
||||||
|
"requestedVelocityMmPerMin": 3600,
|
||||||
|
"distanceToGo": 0,
|
||||||
|
"dtg": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"queueDepth": 0,
|
||||||
|
"activeDepth": 0,
|
||||||
|
"cycle": 40,
|
||||||
|
"taskCycle": 4,
|
||||||
|
"halChangedPinCount": 0
|
||||||
|
},
|
||||||
|
"history": 3,
|
||||||
|
"lineExecution": {
|
||||||
|
"status": "idle",
|
||||||
|
"source": "linuxcnc-task-motion-hal-wasm",
|
||||||
|
"line": 1,
|
||||||
|
"feed": 3600,
|
||||||
|
"requestedFeed": 3600,
|
||||||
|
"feedMode": null,
|
||||||
|
"axisPose": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"a": 0,
|
||||||
|
"b": 0,
|
||||||
|
"c": 0
|
||||||
|
},
|
||||||
|
"taskCycle": 4,
|
||||||
|
"servoCycle": 40,
|
||||||
|
"sampleIndex": 40,
|
||||||
|
"motionIndex": 0,
|
||||||
|
"updatedAt": "2026-06-23T07:51:07.542Z"
|
||||||
|
},
|
||||||
|
"source": "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/impeller-7bl-xyzac.ngc"
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"activeUiLine": 1,
|
||||||
|
"lineText": "idle | F 3600.0 | X 0.000 | Y 0.000 | Z 0.000 | A 0.000 | C 0.000 | cycle 4/40",
|
||||||
|
"stopExists": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "02-running",
|
||||||
|
"screenshot": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-run-stop-after-deploy/02-running.png",
|
||||||
|
"state": {
|
||||||
|
"runState": "running",
|
||||||
|
"activeLine": 9,
|
||||||
|
"machine": {
|
||||||
|
"powerOn": true,
|
||||||
|
"estopActive": false,
|
||||||
|
"taskState": "on",
|
||||||
|
"mode": "auto",
|
||||||
|
"interpState": "reading",
|
||||||
|
"interpResumeState": "reading",
|
||||||
|
"taskPaused": false,
|
||||||
|
"allHomed": true,
|
||||||
|
"noForceHoming": false,
|
||||||
|
"jogAxis": "x",
|
||||||
|
"jogIncrement": 1,
|
||||||
|
"mdiCommand": "G0 X0 Y0 Z0",
|
||||||
|
"mdiDistanceMode": "absolute",
|
||||||
|
"resetCount": 0
|
||||||
|
},
|
||||||
|
"rtcpState": "on",
|
||||||
|
"kinsType": "tcp-xyzac",
|
||||||
|
"message": "task/HAL status tick 5",
|
||||||
|
"loop": {
|
||||||
|
"apiName": "web-rtcp-5axis-task-hal-status-loop",
|
||||||
|
"active": true,
|
||||||
|
"sequence": 1,
|
||||||
|
"profileId": "xyzac-trt",
|
||||||
|
"iniPath": "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
|
||||||
|
"kinematicsModuleId": "xyzac-trt",
|
||||||
|
"tickCount": 5,
|
||||||
|
"batchSize": 5,
|
||||||
|
"intervalMs": 25,
|
||||||
|
"taskPeriodNs": 10000000,
|
||||||
|
"servoPeriodNs": 1000000,
|
||||||
|
"lastStatusAt": "2026-06-23T07:51:21.038Z",
|
||||||
|
"lastError": null,
|
||||||
|
"stopReason": null,
|
||||||
|
"semanticBoundary": "js_status_polling_loop_for_linuxcnc_task_hal_motion_status"
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"apiName": "web-rtcp-5axis-program-runtime-feedback",
|
||||||
|
"sourceMode": "linuxcnc-task-motion-hal-wasm",
|
||||||
|
"semanticBoundary": "linuxcnc_task_motion_hal_wasm_simulation_runtime",
|
||||||
|
"sampleIndex": 340,
|
||||||
|
"motionIndex": 8,
|
||||||
|
"line": 9,
|
||||||
|
"motionProgramLine": 9,
|
||||||
|
"halProgramLine": 9,
|
||||||
|
"activeLineSource": "motion-status",
|
||||||
|
"activeLineHalSynced": true,
|
||||||
|
"type": "TASK_MOTION",
|
||||||
|
"timeSeconds": 0,
|
||||||
|
"axisPose": {
|
||||||
|
"x": 10.4843,
|
||||||
|
"y": -17.3304,
|
||||||
|
"z": 30.0805,
|
||||||
|
"a": -71.841,
|
||||||
|
"b": 0,
|
||||||
|
"c": -35.93
|
||||||
|
},
|
||||||
|
"currentVelocityMmPerMin": 2100,
|
||||||
|
"requestedVelocityMmPerMin": 2100,
|
||||||
|
"distanceToGo": 0,
|
||||||
|
"dtg": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"queueDepth": 0,
|
||||||
|
"activeDepth": 0,
|
||||||
|
"cycle": 340,
|
||||||
|
"taskCycle": 34,
|
||||||
|
"halChangedPinCount": 0
|
||||||
|
},
|
||||||
|
"history": 5,
|
||||||
|
"lineExecution": {
|
||||||
|
"status": "running",
|
||||||
|
"source": "linuxcnc-task-motion-hal-wasm",
|
||||||
|
"line": 9,
|
||||||
|
"feed": 2100,
|
||||||
|
"requestedFeed": 2100,
|
||||||
|
"feedMode": null,
|
||||||
|
"axisPose": {
|
||||||
|
"x": 10.4843,
|
||||||
|
"y": -17.3304,
|
||||||
|
"z": 30.0805,
|
||||||
|
"a": -71.841,
|
||||||
|
"b": 0,
|
||||||
|
"c": -35.93
|
||||||
|
},
|
||||||
|
"taskCycle": 34,
|
||||||
|
"servoCycle": 340,
|
||||||
|
"sampleIndex": 340,
|
||||||
|
"motionIndex": 8,
|
||||||
|
"updatedAt": "2026-06-23T07:51:21.038Z"
|
||||||
|
},
|
||||||
|
"source": "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/impeller-7bl-xyzac.ngc"
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"activeUiLine": 9,
|
||||||
|
"lineText": "running | F 2100.0 | X 10.484 | Y -17.330 | Z 30.081 | A -71.841 | C -35.930 | cycle 34/340",
|
||||||
|
"stopExists": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "03-stopped",
|
||||||
|
"screenshot": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-run-stop-after-deploy/03-stopped.png",
|
||||||
|
"state": {
|
||||||
|
"runState": "stopped",
|
||||||
|
"activeLine": 9,
|
||||||
|
"machine": {
|
||||||
|
"powerOn": true,
|
||||||
|
"estopActive": false,
|
||||||
|
"taskState": "on",
|
||||||
|
"mode": "auto",
|
||||||
|
"interpState": "idle",
|
||||||
|
"interpResumeState": "idle",
|
||||||
|
"taskPaused": false,
|
||||||
|
"allHomed": true,
|
||||||
|
"noForceHoming": false,
|
||||||
|
"jogAxis": "x",
|
||||||
|
"jogIncrement": 1,
|
||||||
|
"mdiCommand": "G0 X0 Y0 Z0",
|
||||||
|
"mdiDistanceMode": "absolute",
|
||||||
|
"resetCount": 0
|
||||||
|
},
|
||||||
|
"rtcpState": "on",
|
||||||
|
"kinsType": "tcp-xyzac",
|
||||||
|
"message": "task/HAL program stopped",
|
||||||
|
"loop": {
|
||||||
|
"apiName": "web-rtcp-5axis-task-hal-status-loop",
|
||||||
|
"active": false,
|
||||||
|
"sequence": 1,
|
||||||
|
"profileId": "xyzac-trt",
|
||||||
|
"iniPath": "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
|
||||||
|
"kinematicsModuleId": "xyzac-trt",
|
||||||
|
"tickCount": 7,
|
||||||
|
"batchSize": 5,
|
||||||
|
"intervalMs": 25,
|
||||||
|
"taskPeriodNs": 10000000,
|
||||||
|
"servoPeriodNs": 1000000,
|
||||||
|
"lastStatusAt": "2026-06-23T07:51:24.831Z",
|
||||||
|
"lastError": null,
|
||||||
|
"stopReason": "running",
|
||||||
|
"semanticBoundary": "js_status_polling_loop_for_linuxcnc_task_hal_motion_status"
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"apiName": "web-rtcp-5axis-program-runtime-feedback",
|
||||||
|
"sourceMode": "linuxcnc-task-motion-hal-wasm",
|
||||||
|
"semanticBoundary": "linuxcnc_task_motion_hal_wasm_simulation_runtime",
|
||||||
|
"sampleIndex": 500,
|
||||||
|
"motionIndex": 8,
|
||||||
|
"line": 9,
|
||||||
|
"motionProgramLine": 9,
|
||||||
|
"halProgramLine": 9,
|
||||||
|
"activeLineSource": "motion-status",
|
||||||
|
"activeLineHalSynced": true,
|
||||||
|
"type": "TASK_MOTION",
|
||||||
|
"timeSeconds": 0,
|
||||||
|
"axisPose": {
|
||||||
|
"x": 7.55697,
|
||||||
|
"y": -13.2911,
|
||||||
|
"z": 28.4442,
|
||||||
|
"a": -71.841,
|
||||||
|
"b": 0,
|
||||||
|
"c": -35.93
|
||||||
|
},
|
||||||
|
"currentVelocityMmPerMin": 2100,
|
||||||
|
"requestedVelocityMmPerMin": 2100,
|
||||||
|
"distanceToGo": 0,
|
||||||
|
"dtg": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"queueDepth": 0,
|
||||||
|
"activeDepth": 0,
|
||||||
|
"cycle": 500,
|
||||||
|
"taskCycle": 50,
|
||||||
|
"halChangedPinCount": 0
|
||||||
|
},
|
||||||
|
"history": 9,
|
||||||
|
"lineExecution": {
|
||||||
|
"status": "stopped",
|
||||||
|
"source": "linuxcnc-task-motion-hal-wasm",
|
||||||
|
"line": 9,
|
||||||
|
"feed": 2100,
|
||||||
|
"requestedFeed": 2100,
|
||||||
|
"feedMode": null,
|
||||||
|
"axisPose": {
|
||||||
|
"x": 7.55697,
|
||||||
|
"y": -13.2911,
|
||||||
|
"z": 28.4442,
|
||||||
|
"a": -71.841,
|
||||||
|
"b": 0,
|
||||||
|
"c": -35.93
|
||||||
|
},
|
||||||
|
"taskCycle": 50,
|
||||||
|
"servoCycle": 500,
|
||||||
|
"sampleIndex": 500,
|
||||||
|
"motionIndex": 8,
|
||||||
|
"updatedAt": "2026-06-23T07:51:26.643Z"
|
||||||
|
},
|
||||||
|
"source": "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/impeller-7bl-xyzac.ngc"
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"activeUiLine": 9,
|
||||||
|
"lineText": "stopped | F 2100.0 | X 7.557 | Y -13.291 | Z 28.444 | A -71.841 | C -35.930 | cycle 50/500",
|
||||||
|
"stopExists": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"logs": [
|
||||||
|
{
|
||||||
|
"type": "warn",
|
||||||
|
"text": "[.WebGL-0x35b4000f0200]GL Driver Message (OpenGL, Performance, GL_CLOSE_PATH_NV, High): GPU stall due to ReadPixels"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "warn",
|
||||||
|
"text": "[.WebGL-0x35b4000f0200]GL Driver Message (OpenGL, Performance, GL_CLOSE_PATH_NV, High): GPU stall due to ReadPixels"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "warn",
|
||||||
|
"text": "[.WebGL-0x35b4000f0200]GL Driver Message (OpenGL, Performance, GL_CLOSE_PATH_NV, High): GPU stall due to ReadPixels"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "warn",
|
||||||
|
"text": "[.WebGL-0x35b4000f0200]GL Driver Message (OpenGL, Performance, GL_CLOSE_PATH_NV, High): GPU stall due to ReadPixels (this message will no longer repeat)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 251 KiB After Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 277 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 277 KiB |
@@ -12,6 +12,7 @@ await cp(join(appRoot, "index.html"), join(distDir, "index.html"));
|
|||||||
await cp(join(appRoot, "src"), join(distDir, "src"), { recursive: true });
|
await cp(join(appRoot, "src"), join(distDir, "src"), { recursive: true });
|
||||||
await copyLinuxCncManifest();
|
await copyLinuxCncManifest();
|
||||||
await copyLinuxCncConfigAssets();
|
await copyLinuxCncConfigAssets();
|
||||||
|
await copyBundledTestLinuxCncSourceAssets();
|
||||||
await copyKinematicsRuntimeAssets();
|
await copyKinematicsRuntimeAssets();
|
||||||
await copyInterpreterRuntimeAssets();
|
await copyInterpreterRuntimeAssets();
|
||||||
await copyTpRuntimeAssets();
|
await copyTpRuntimeAssets();
|
||||||
@@ -68,6 +69,13 @@ async function copyLinuxCncConfigAssets() {
|
|||||||
await cp(configSrcDir, vendorConfigDistDir, { recursive: true });
|
await cp(configSrcDir, vendorConfigDistDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function copyBundledTestLinuxCncSourceAssets() {
|
||||||
|
const testSourceDir = join(repoRoot, "web-rtcp-5axis-sim-plan/working_run/test_linuxcnc_source");
|
||||||
|
const testSourceDistDir = join(distDir, "working_run/test_linuxcnc_source");
|
||||||
|
await mkdir(testSourceDistDir, { recursive: true });
|
||||||
|
await cp(testSourceDir, testSourceDistDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
async function copyLinuxCncManifest() {
|
async function copyLinuxCncManifest() {
|
||||||
const manifestDistDir = join(distDir, "wasm-port/tools");
|
const manifestDistDir = join(distDir, "wasm-port/tools");
|
||||||
await mkdir(manifestDistDir, { recursive: true });
|
await mkdir(manifestDistDir, { recursive: true });
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ const DEFAULT_VENDOR_ROOT_URLS = [
|
|||||||
new URL("../../../../wasm-port/vendor/linuxcnc/", import.meta.url).href,
|
new URL("../../../../wasm-port/vendor/linuxcnc/", import.meta.url).href,
|
||||||
new URL("../../wasm-port/vendor/linuxcnc/", import.meta.url).href,
|
new URL("../../wasm-port/vendor/linuxcnc/", import.meta.url).href,
|
||||||
];
|
];
|
||||||
|
const DEFAULT_TEST_SOURCE_ROOT_URLS = [
|
||||||
|
new URL("../../../working_run/test_linuxcnc_source/", import.meta.url).href,
|
||||||
|
new URL("../../working_run/test_linuxcnc_source/", import.meta.url).href,
|
||||||
|
new URL("../../../../working_run/test_linuxcnc_source/", import.meta.url).href,
|
||||||
|
];
|
||||||
const TRT_MACHINE_REL = "axis/vismach/5axis/table-rotary-tilting";
|
const TRT_MACHINE_REL = "axis/vismach/5axis/table-rotary-tilting";
|
||||||
const TRT_DEMO_SOURCE_PREFIX = `configs/sim/${TRT_MACHINE_REL}/demos/`;
|
const TRT_DEMO_SOURCE_PREFIX = `configs/sim/${TRT_MACHINE_REL}/demos/`;
|
||||||
const OPFS_ROOT = "web-rtcp-5axis-sim-plan/machines";
|
const OPFS_ROOT = "web-rtcp-5axis-sim-plan/machines";
|
||||||
@@ -125,9 +130,12 @@ export async function saveMachineFileStagingPlan(plan, options = {}) {
|
|||||||
throw new Error("saveMachineFileStagingPlan requires a machine-file staging plan");
|
throw new Error("saveMachineFileStagingPlan requires a machine-file staging plan");
|
||||||
}
|
}
|
||||||
const storage = resolveMachineFileStorage(options);
|
const storage = resolveMachineFileStorage(options);
|
||||||
|
const sourceTextOverrides = options.sourceTextOverrides || {};
|
||||||
const savedFiles = [];
|
const savedFiles = [];
|
||||||
for (const file of plan.files) {
|
for (const file of plan.files) {
|
||||||
const text = await readTextFromCandidateUrls(sourceUrlsFor(file.sourceRel));
|
const text = typeof sourceTextOverrides[file.sourceRel] === "string"
|
||||||
|
? sourceTextOverrides[file.sourceRel]
|
||||||
|
: await readTextFromCandidateUrls(sourceUrlsFor(file.sourceRel));
|
||||||
await saveTextFile(file.opfsPath, text, storage.storage);
|
await saveTextFile(file.opfsPath, text, storage.storage);
|
||||||
savedFiles.push({
|
savedFiles.push({
|
||||||
sourceRel: file.sourceRel,
|
sourceRel: file.sourceRel,
|
||||||
@@ -182,6 +190,7 @@ export async function stageProfileMachineFiles(profile, options = {}) {
|
|||||||
const save = await saveMachineFileStagingPlan(plan, {
|
const save = await saveMachineFileStagingPlan(plan, {
|
||||||
storage: options.storage,
|
storage: options.storage,
|
||||||
storageMode: options.storageMode,
|
storageMode: options.storageMode,
|
||||||
|
sourceTextOverrides: options.sourceTextOverrides,
|
||||||
});
|
});
|
||||||
return { plan, save };
|
return { plan, save };
|
||||||
}
|
}
|
||||||
@@ -441,7 +450,22 @@ function splitPath(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sourceUrlsFor(sourceRel) {
|
function sourceUrlsFor(sourceRel) {
|
||||||
return DEFAULT_VENDOR_ROOT_URLS.map((rootUrl) => new URL(sourceRel, rootUrl).href);
|
return sourceOverrideUrlsFor(sourceRel).concat(
|
||||||
|
DEFAULT_VENDOR_ROOT_URLS.map((rootUrl) => new URL(sourceRel, rootUrl).href),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sourceOverrideUrlsFor(sourceRel) {
|
||||||
|
const basenameValue = basename(sourceRel);
|
||||||
|
if (!basenameValue) return [];
|
||||||
|
|
||||||
|
if (sourceRel === `configs/sim/${TRT_MACHINE_REL}/xyzac-trt.ini`) {
|
||||||
|
return DEFAULT_TEST_SOURCE_ROOT_URLS.map((rootUrl) => new URL("xyzac-trt.ini", rootUrl).href);
|
||||||
|
}
|
||||||
|
if (sourceRel === `${TRT_DEMO_SOURCE_PREFIX}impeller-7bl-xyzac.ngc`) {
|
||||||
|
return DEFAULT_TEST_SOURCE_ROOT_URLS.map((rootUrl) => new URL("impeller-7bl-xyzac.ngc", rootUrl).href);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function basename(path) {
|
function basename(path) {
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ const initialState = {
|
|||||||
programExecutionSampleIndex: 0,
|
programExecutionSampleIndex: 0,
|
||||||
programRuntimeFeedback: null,
|
programRuntimeFeedback: null,
|
||||||
programRuntimeFeedbackHistory: [],
|
programRuntimeFeedbackHistory: [],
|
||||||
|
programLineExecution: {},
|
||||||
taskHalRuntime: null,
|
taskHalRuntime: null,
|
||||||
taskHalRuntimeReadiness: null,
|
taskHalRuntimeReadiness: null,
|
||||||
taskHalStatus: null,
|
taskHalStatus: null,
|
||||||
@@ -327,6 +328,25 @@ export function createSimulationStore(seed = {}) {
|
|||||||
scheduleAsyncKinematicsRefresh();
|
scheduleAsyncKinematicsRefresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const waitForStatePredicate = (predicate, timeoutMs = 10000) => {
|
||||||
|
if (predicate(state)) return Promise.resolve(state);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const startedAt = Date.now();
|
||||||
|
const listener = (nextState) => {
|
||||||
|
if (predicate(nextState)) {
|
||||||
|
listeners.delete(listener);
|
||||||
|
resolve(nextState);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Date.now() - startedAt > timeoutMs) {
|
||||||
|
listeners.delete(listener);
|
||||||
|
reject(new Error("timed out waiting for store state"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
listeners.add(listener);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const dispatch = (action) => {
|
const dispatch = (action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "BOOT_READY":
|
case "BOOT_READY":
|
||||||
@@ -568,6 +588,10 @@ export function createSimulationStore(seed = {}) {
|
|||||||
programExecutionMotionIndex: 0,
|
programExecutionMotionIndex: 0,
|
||||||
programExecutionSampleIndex: 0,
|
programExecutionSampleIndex: 0,
|
||||||
programRuntimeFeedback: firstFeedback,
|
programRuntimeFeedback: firstFeedback,
|
||||||
|
programLineExecution: createProgramLineExecutionPatch(state.programLineExecution, firstFeedback, {
|
||||||
|
status: "ready",
|
||||||
|
source: execution.sourceMode,
|
||||||
|
}),
|
||||||
programElapsedSeconds: firstTiming.elapsedSeconds,
|
programElapsedSeconds: firstTiming.elapsedSeconds,
|
||||||
programRemainingSeconds: firstTiming.remainingSeconds,
|
programRemainingSeconds: firstTiming.remainingSeconds,
|
||||||
interpreterExecutionPending: false,
|
interpreterExecutionPending: false,
|
||||||
@@ -603,6 +627,7 @@ export function createSimulationStore(seed = {}) {
|
|||||||
programExecutionSourceMode: "fixture-line-playback",
|
programExecutionSourceMode: "fixture-line-playback",
|
||||||
programExecutionSampleIndex: 0,
|
programExecutionSampleIndex: 0,
|
||||||
programRuntimeFeedback: null,
|
programRuntimeFeedback: null,
|
||||||
|
programLineExecution: {},
|
||||||
interpreterExecutionPending: false,
|
interpreterExecutionPending: false,
|
||||||
operatorMessage: `LinuxCNC interpreter blocked: ${action.error}`,
|
operatorMessage: `LinuxCNC interpreter blocked: ${action.error}`,
|
||||||
});
|
});
|
||||||
@@ -815,6 +840,7 @@ export function createSimulationStore(seed = {}) {
|
|||||||
axisPose: initialAxisPose,
|
axisPose: initialAxisPose,
|
||||||
runState: "idle",
|
runState: "idle",
|
||||||
programRuntimeFeedback: null,
|
programRuntimeFeedback: null,
|
||||||
|
programLineExecution: {},
|
||||||
preview: {
|
preview: {
|
||||||
...state.preview,
|
...state.preview,
|
||||||
pathPoints: Math.max(loadedProgram.programLines.length, 1),
|
pathPoints: Math.max(loadedProgram.programLines.length, 1),
|
||||||
@@ -909,6 +935,14 @@ export function createSimulationStore(seed = {}) {
|
|||||||
case "RUN_FULL_BOUNDARY_AUDIT_REQUEST":
|
case "RUN_FULL_BOUNDARY_AUDIT_REQUEST":
|
||||||
runFullBoundaryAudit(action.options || {}).catch(() => {});
|
runFullBoundaryAudit(action.options || {}).catch(() => {});
|
||||||
break;
|
break;
|
||||||
|
case "RUN_READY":
|
||||||
|
runReadySequence().catch((error) => {
|
||||||
|
dispatch({
|
||||||
|
type: "TASK_HAL_COMMAND_FAILED",
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
case "SET_FRAME_SOURCE":
|
case "SET_FRAME_SOURCE":
|
||||||
setState({
|
setState({
|
||||||
sourceMode: action.sourceMode,
|
sourceMode: action.sourceMode,
|
||||||
@@ -1138,6 +1172,7 @@ export function createSimulationStore(seed = {}) {
|
|||||||
axisPose: initialAxisPose,
|
axisPose: initialAxisPose,
|
||||||
runState: "idle",
|
runState: "idle",
|
||||||
programRuntimeFeedback: null,
|
programRuntimeFeedback: null,
|
||||||
|
programLineExecution: {},
|
||||||
preview: {
|
preview: {
|
||||||
...state.preview,
|
...state.preview,
|
||||||
pathPoints: Math.max(loadedProgram.programLines.length, 1),
|
pathPoints: Math.max(loadedProgram.programLines.length, 1),
|
||||||
@@ -1182,6 +1217,10 @@ export function createSimulationStore(seed = {}) {
|
|||||||
programExecutionMotionIndex: playback.motionIndex,
|
programExecutionMotionIndex: playback.motionIndex,
|
||||||
programExecutionSampleIndex: playback.sampleIndex,
|
programExecutionSampleIndex: playback.sampleIndex,
|
||||||
programRuntimeFeedback: playback.runtimeFeedback,
|
programRuntimeFeedback: playback.runtimeFeedback,
|
||||||
|
programLineExecution: createProgramLineExecutionPatch(state.programLineExecution, playback.runtimeFeedback, {
|
||||||
|
status: playback.complete ? "done" : "running",
|
||||||
|
source: playback.runtimeFeedback?.sourceMode,
|
||||||
|
}),
|
||||||
programElapsedSeconds: playback.timing.elapsedSeconds,
|
programElapsedSeconds: playback.timing.elapsedSeconds,
|
||||||
programRemainingSeconds: playback.timing.remainingSeconds,
|
programRemainingSeconds: playback.timing.remainingSeconds,
|
||||||
feed: {
|
feed: {
|
||||||
@@ -1375,7 +1414,13 @@ export function createSimulationStore(seed = {}) {
|
|||||||
if (state.taskHalRuntime?.loaded) {
|
if (state.taskHalRuntime?.loaded) {
|
||||||
runTaskHalCommandSequence([
|
runTaskHalCommandSequence([
|
||||||
{ type: "EMC_JOINT_HOME", joint: -1 },
|
{ type: "EMC_JOINT_HOME", joint: -1 },
|
||||||
], { operatorMessage: "task/HAL machine homed" }).catch(() => {});
|
], {
|
||||||
|
operatorMessage: "task/HAL machine homed",
|
||||||
|
preserveMachine: {
|
||||||
|
...state.machine,
|
||||||
|
allHomed: true,
|
||||||
|
},
|
||||||
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
setState({
|
setState({
|
||||||
machine: {
|
machine: {
|
||||||
@@ -1493,6 +1538,7 @@ export function createSimulationStore(seed = {}) {
|
|||||||
programExecutionMotionIndex: 0,
|
programExecutionMotionIndex: 0,
|
||||||
programExecutionSampleIndex: 0,
|
programExecutionSampleIndex: 0,
|
||||||
programRuntimeFeedback: null,
|
programRuntimeFeedback: null,
|
||||||
|
programLineExecution: {},
|
||||||
axisPose: initialAxisPose,
|
axisPose: initialAxisPose,
|
||||||
preview: { ...state.preview, pathPoints: Math.max(state.programLines.length, 1) },
|
preview: { ...state.preview, pathPoints: Math.max(state.programLines.length, 1) },
|
||||||
operatorMessage: "program reloaded",
|
operatorMessage: "program reloaded",
|
||||||
@@ -1758,6 +1804,57 @@ export function createSimulationStore(seed = {}) {
|
|||||||
return status;
|
return status;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const runReadySequence = async () => {
|
||||||
|
if (!state.machineFileStaging?.selectedGcodeSourceRel) {
|
||||||
|
const sourceRel = defaultLinuxCncGcodeSourceForState(state)?.sourceRel;
|
||||||
|
if (sourceRel) {
|
||||||
|
dispatch({ type: "LOAD_LINUXCNC_GCODE_SOURCE", sourceRel });
|
||||||
|
await waitForStatePredicate((nextState) => nextState.machineFileStaging?.selectedGcodeSourceRel === sourceRel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.taskHalRuntime?.loaded) {
|
||||||
|
await initializeTaskHalSession({ openProgram: true });
|
||||||
|
await runTaskHalCommandSequence([
|
||||||
|
{ type: "EMC_TASK_SET_STATE", state: "ON" },
|
||||||
|
{ type: "EMC_JOINT_HOME", joint: -1 },
|
||||||
|
{ type: "EMC_TASK_SET_MODE", mode: "AUTO" },
|
||||||
|
], {
|
||||||
|
taskCycles: 3,
|
||||||
|
operatorMessage: "RUN ready: power on, homed, auto mode",
|
||||||
|
allowFixtureSession: false,
|
||||||
|
preserveMachine: {
|
||||||
|
powerOn: true,
|
||||||
|
estopActive: false,
|
||||||
|
taskState: "on",
|
||||||
|
mode: "auto",
|
||||||
|
allHomed: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const tcpKinsType = state.profile.kinematicsParameters.switchkinsTypes.find((type) => type.value === 1)?.webKinsType || "tcp-xyzac";
|
||||||
|
dispatch({ type: "SET_KINS_TYPE", kinsType: tcpKinsType });
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
const tcpKinsType = state.profile.kinematicsParameters.switchkinsTypes.find((type) => type.value === 1)?.webKinsType || "tcp-xyzac";
|
||||||
|
setState({
|
||||||
|
machine: {
|
||||||
|
...state.machine,
|
||||||
|
powerOn: true,
|
||||||
|
estopActive: false,
|
||||||
|
taskState: "on",
|
||||||
|
mode: "auto",
|
||||||
|
allHomed: true,
|
||||||
|
interpState: "idle",
|
||||||
|
interpResumeState: "idle",
|
||||||
|
taskPaused: false,
|
||||||
|
},
|
||||||
|
runState: "idle",
|
||||||
|
kinsType: tcpKinsType,
|
||||||
|
rtcpState: "on",
|
||||||
|
operatorMessage: "RUN ready: power on, homed, auto mode",
|
||||||
|
});
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
const loadTaskHalMotionPlanForSession = async (session = state.taskHalSession) => {
|
const loadTaskHalMotionPlanForSession = async (session = state.taskHalSession) => {
|
||||||
if (!state.taskHalRuntime?.loaded || typeof state.taskHalRuntime.loadProgramMotionPlan !== "function") {
|
if (!state.taskHalRuntime?.loaded || typeof state.taskHalRuntime.loadProgramMotionPlan !== "function") {
|
||||||
return null;
|
return null;
|
||||||
@@ -1882,6 +1979,7 @@ export function createSimulationStore(seed = {}) {
|
|||||||
operatorMessage = "task/HAL command complete",
|
operatorMessage = "task/HAL command complete",
|
||||||
pendingJogCommand = null,
|
pendingJogCommand = null,
|
||||||
allowFixtureSession = true,
|
allowFixtureSession = true,
|
||||||
|
preserveMachine = null,
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
if (!state.taskHalRuntime?.loaded) {
|
if (!state.taskHalRuntime?.loaded) {
|
||||||
throw new Error("LinuxCNC task/HAL runtime not attached");
|
throw new Error("LinuxCNC task/HAL runtime not attached");
|
||||||
@@ -1910,7 +2008,7 @@ export function createSimulationStore(seed = {}) {
|
|||||||
if (state.taskHalExecutionSequence !== sequence) {
|
if (state.taskHalExecutionSequence !== sequence) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
dispatch({ type: "TASK_HAL_STATUS_APPLIED", status, operatorMessage });
|
dispatch({ type: "TASK_HAL_STATUS_APPLIED", status, operatorMessage, preserveMachine });
|
||||||
return status;
|
return status;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -2277,6 +2375,11 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
|||||||
&& (runState === "running" || runState === "mdi");
|
&& (runState === "running" || runState === "mdi");
|
||||||
const nextTickCount = loopActive ? Number(state.taskHalStatusLoop.tickCount || 0) + 1 : Number(state.taskHalStatusLoop?.tickCount || 0);
|
const nextTickCount = loopActive ? Number(state.taskHalStatusLoop.tickCount || 0) + 1 : Number(state.taskHalStatusLoop?.tickCount || 0);
|
||||||
const feedbackHistory = [runtimeFeedback, ...(state.programRuntimeFeedbackHistory || [])].slice(0, 100);
|
const feedbackHistory = [runtimeFeedback, ...(state.programRuntimeFeedbackHistory || [])].slice(0, 100);
|
||||||
|
const lineStatus = runState === "complete"
|
||||||
|
? "done"
|
||||||
|
: runState === "running" || runState === "mdi"
|
||||||
|
? "running"
|
||||||
|
: runState;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
taskHalStatus: status,
|
taskHalStatus: status,
|
||||||
@@ -2315,6 +2418,10 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
|||||||
},
|
},
|
||||||
programRuntimeFeedback: runtimeFeedback,
|
programRuntimeFeedback: runtimeFeedback,
|
||||||
programRuntimeFeedbackHistory: feedbackHistory,
|
programRuntimeFeedbackHistory: feedbackHistory,
|
||||||
|
programLineExecution: createProgramLineExecutionPatch(state.programLineExecution, runtimeFeedback, {
|
||||||
|
status: lineStatus,
|
||||||
|
source: "linuxcnc-task-motion-hal-wasm",
|
||||||
|
}),
|
||||||
operatorMessage,
|
operatorMessage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -2451,6 +2558,41 @@ function createTaskHalRuntimeFeedback(state, status, axisPose, activeLine) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createProgramLineExecutionPatch(previous = {}, feedback = null, {
|
||||||
|
status = "running",
|
||||||
|
source = null,
|
||||||
|
} = {}) {
|
||||||
|
const line = Number(feedback?.line || 0);
|
||||||
|
if (!Number.isFinite(line) || line <= 0) {
|
||||||
|
return previous || {};
|
||||||
|
}
|
||||||
|
const axisPose = feedback.axisPose || {};
|
||||||
|
return {
|
||||||
|
...(previous || {}),
|
||||||
|
[line]: {
|
||||||
|
status,
|
||||||
|
source: source || feedback.sourceMode || "unknown",
|
||||||
|
line,
|
||||||
|
feed: Number(feedback.currentVelocityMmPerMin || 0),
|
||||||
|
requestedFeed: Number(feedback.requestedVelocityMmPerMin || 0),
|
||||||
|
feedMode: feedback.feedMode || null,
|
||||||
|
axisPose: pickExecutionAxes(axisPose),
|
||||||
|
taskCycle: Number(feedback.taskCycle || 0),
|
||||||
|
servoCycle: Number(feedback.cycle || 0),
|
||||||
|
sampleIndex: Number(feedback.sampleIndex || 0),
|
||||||
|
motionIndex: Number(feedback.motionIndex || 0),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickExecutionAxes(axisPose = {}) {
|
||||||
|
return Object.fromEntries(["x", "y", "z", "a", "b", "c"].map((axis) => [
|
||||||
|
axis,
|
||||||
|
Number(axisPose[axis] || 0),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeTaskHalTaskState(value) {
|
function normalizeTaskHalTaskState(value) {
|
||||||
const state = String(value || "").toLowerCase().replaceAll("_", "-");
|
const state = String(value || "").toLowerCase().replaceAll("_", "-");
|
||||||
if (state === "on") return "on";
|
if (state === "on") return "on";
|
||||||
|
|||||||
@@ -21,16 +21,22 @@
|
|||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
min-width: 1180px;
|
height: 100vh;
|
||||||
min-height: 640px;
|
height: 100dvh;
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #c8c4bc;
|
background: #c8c4bc;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
border: 1px solid #bdb8ad;
|
border: 1px solid #bdb8ad;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
@@ -66,21 +72,21 @@ button:active {
|
|||||||
.gmoccapy-shell {
|
.gmoccapy-shell {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns:
|
grid-template-columns:
|
||||||
minmax(610px, 1.34fr)
|
minmax(0, 1.34fr)
|
||||||
minmax(270px, 0.58fr)
|
minmax(0, 0.58fr)
|
||||||
minmax(260px, 0.56fr)
|
minmax(0, 0.56fr)
|
||||||
108px;
|
108px;
|
||||||
grid-template-rows: 40px minmax(210px, 1fr) minmax(170px, 0.78fr) 150px 68px;
|
grid-template-rows: 40px minmax(0, 1fr) minmax(0, 0.82fr) minmax(110px, 0.48fr) 68px;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"title title title title"
|
"title title title title"
|
||||||
"preview dro dro side"
|
"preview dro dro side"
|
||||||
"preview gcode gcode side"
|
"preview gcode gcode side"
|
||||||
"info override spindle side"
|
"info override spindle side"
|
||||||
"bottom bottom bottom side";
|
"bottom bottom bottom side";
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
min-width: 1180px;
|
min-width: 0;
|
||||||
min-height: 640px;
|
min-height: 0;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
}
|
}
|
||||||
@@ -140,6 +146,35 @@ button:active {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.current-line-indicator {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(44px, auto);
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border: 1px solid #8d887f;
|
||||||
|
background: #f7f1d0;
|
||||||
|
color: #171717;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-line-indicator span {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-line-indicator strong {
|
||||||
|
color: #005bab;
|
||||||
|
font: 700 20px/1 "Courier New", monospace;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.preview-panel {
|
.preview-panel {
|
||||||
grid-area: preview;
|
grid-area: preview;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -336,7 +371,7 @@ button:active {
|
|||||||
.gcode-panel {
|
.gcode-panel {
|
||||||
grid-area: gcode;
|
grid-area: gcode;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 32px minmax(0, 1fr) 20px 54px;
|
grid-template-rows: auto auto minmax(96px, 1fr) 20px 54px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -357,6 +392,38 @@ button:active {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.linuxcnc-source-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(260px, 1fr) auto minmax(180px, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-bottom: 1px solid #d5d0c7;
|
||||||
|
background: #f6f4ef;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linuxcnc-source-row label {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(150px, 240px);
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linuxcnc-source-row select {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linuxcnc-source-row [data-linuxcnc-gcode-source="status"] {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.gcode-header strong,
|
.gcode-header strong,
|
||||||
.gcode-header span {
|
.gcode-header span {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -385,26 +452,54 @@ button:active {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 42px minmax(0, 1fr);
|
grid-template-columns: 42px minmax(0, 1fr);
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
min-height: 18px;
|
min-height: 34px;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gcode-row.active {
|
.gcode-row.active {
|
||||||
background: #242424;
|
background: #242424;
|
||||||
color: #202020;
|
color: #202020;
|
||||||
|
border-left-color: #2ebf63;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-row[data-line-status="done"] {
|
||||||
|
background: #eef6ec;
|
||||||
|
border-left-color: #79a96c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-row[data-line-status="running"] {
|
||||||
|
background: #242424;
|
||||||
|
border-left-color: #2ebf63;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gcode-row.active span,
|
.gcode-row.active span,
|
||||||
.gcode-row.active code {
|
.gcode-row.active code,
|
||||||
|
.gcode-row.active small,
|
||||||
|
.gcode-row[data-line-status="running"] span,
|
||||||
|
.gcode-row[data-line-status="running"] code,
|
||||||
|
.gcode-row[data-line-status="running"] small {
|
||||||
color: #f2f2f2;
|
color: #f2f2f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gcode-row code {
|
.gcode-row code {
|
||||||
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gcode-row small {
|
||||||
|
grid-column: 2;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: #6d695f;
|
||||||
|
font: 11px/1.2 "Courier New", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
.gcode-progress {
|
.gcode-progress {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 120px 1fr;
|
grid-template-columns: 120px 1fr;
|
||||||
@@ -723,7 +818,7 @@ button:active {
|
|||||||
.bottom-controls {
|
.bottom-controls {
|
||||||
grid-area: bottom;
|
grid-area: bottom;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(15, minmax(48px, 1fr));
|
grid-template-columns: repeat(18, minmax(44px, 1fr));
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
border-top: 2px solid var(--border);
|
border-top: 2px solid var(--border);
|
||||||
@@ -746,10 +841,23 @@ button:active {
|
|||||||
@media (max-width: 1180px) {
|
@media (max-width: 1180px) {
|
||||||
.gmoccapy-shell {
|
.gmoccapy-shell {
|
||||||
grid-template-columns:
|
grid-template-columns:
|
||||||
minmax(560px, 1.28fr)
|
minmax(0, 1.18fr)
|
||||||
minmax(250px, 0.58fr)
|
minmax(0, 0.7fr)
|
||||||
minmax(230px, 0.54fr)
|
minmax(0, 0.62fr)
|
||||||
100px;
|
96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar {
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-select-label {
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-line-indicator strong {
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dro-row strong {
|
.dro-row strong {
|
||||||
@@ -765,6 +873,14 @@ button:active {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.linuxcnc-source-row {
|
||||||
|
grid-template-columns: minmax(180px, 1fr) auto minmax(140px, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.linuxcnc-source-row label {
|
||||||
|
grid-template-columns: auto minmax(120px, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.tabs button,
|
.tabs button,
|
||||||
.meter-card h2,
|
.meter-card h2,
|
||||||
.override-control h2,
|
.override-control h2,
|
||||||
@@ -773,3 +889,180 @@ button:active {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-height: 720px) {
|
||||||
|
.gmoccapy-shell {
|
||||||
|
grid-template-rows: 40px minmax(0, 1fr) minmax(0, 1fr) minmax(84px, 0.4fr) 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-toolbar button,
|
||||||
|
.bottom-controls button,
|
||||||
|
.status-sidebar button {
|
||||||
|
min-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-panel {
|
||||||
|
grid-template-rows: auto auto minmax(0, 1fr) 18px 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-list {
|
||||||
|
padding: 4px 6px 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-row {
|
||||||
|
min-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-row small {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-controls {
|
||||||
|
grid-template-columns: repeat(18, minmax(36px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 420px) {
|
||||||
|
.gmoccapy-shell {
|
||||||
|
grid-template-rows: 24px minmax(24px, 0.14fr) minmax(0, 1.92fr) 0 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar {
|
||||||
|
gap: 4px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-stack strong {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-stack span,
|
||||||
|
.run-state {
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-select-label {
|
||||||
|
min-width: 96px;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-select-label select {
|
||||||
|
min-height: 16px;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-line-indicator {
|
||||||
|
padding: 1px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-line-indicator span {
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-line-indicator strong {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-tabs,
|
||||||
|
.override-panel,
|
||||||
|
.spindle-coolant-panel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-panel {
|
||||||
|
grid-template-rows: auto auto minmax(0, 1fr) 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-header {
|
||||||
|
gap: 4px;
|
||||||
|
padding: 1px 3px;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linuxcnc-source-row {
|
||||||
|
gap: 4px;
|
||||||
|
padding: 1px 3px;
|
||||||
|
font-size: 8px;
|
||||||
|
grid-template-columns: minmax(120px, 1fr) auto minmax(72px, 0.82fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.linuxcnc-source-row label {
|
||||||
|
gap: 4px;
|
||||||
|
grid-template-columns: auto minmax(72px, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.linuxcnc-source-row button,
|
||||||
|
.linuxcnc-source-row select {
|
||||||
|
min-height: 16px;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-list {
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 8px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-row {
|
||||||
|
gap: 4px;
|
||||||
|
min-height: 8px;
|
||||||
|
padding: 0 2px;
|
||||||
|
border-left-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-row span {
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gcode-row code {
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdi-panel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dro-panel {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dro-grid {
|
||||||
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dro-row {
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
gap: 3px;
|
||||||
|
padding: 1px 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dro-axis {
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dro-mode,
|
||||||
|
.dro-dtg,
|
||||||
|
.tcp-strip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dro-row strong {
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-controls {
|
||||||
|
gap: 3px;
|
||||||
|
padding: 2px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-controls button {
|
||||||
|
font-size: 8px;
|
||||||
|
line-height: 1;
|
||||||
|
min-height: 18px;
|
||||||
|
padding: 1px 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ function render(regions, state, dispatch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderTitlebar(element, state) {
|
function renderTitlebar(element, state) {
|
||||||
|
const currentLine = currentGcodeExecutionLine(state);
|
||||||
element.innerHTML = `
|
element.innerHTML = `
|
||||||
<div class="brand-dot" aria-hidden="true">NS</div>
|
<div class="brand-dot" aria-hidden="true">NS</div>
|
||||||
<div class="title-stack">
|
<div class="title-stack">
|
||||||
@@ -73,6 +74,10 @@ function renderTitlebar(element, state) {
|
|||||||
`).join("")}
|
`).join("")}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="current-line-indicator" data-current-gcode-line="${currentLine}" data-current-gcode-source="${escapeHtml(currentGcodeLineSource(state))}">
|
||||||
|
<span>G-code line</span>
|
||||||
|
<strong>${currentLine}</strong>
|
||||||
|
</div>
|
||||||
<div class="run-state" data-run-state="${state.runState}">${state.runState}</div>
|
<div class="run-state" data-run-state="${state.runState}">${state.runState}</div>
|
||||||
`;
|
`;
|
||||||
element.querySelector('[data-action="select-profile"]').addEventListener("change", (event) => {
|
element.querySelector('[data-action="select-profile"]').addEventListener("change", (event) => {
|
||||||
@@ -192,11 +197,20 @@ function droRow(axis, value, dtg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderGcode(element, state, dispatch) {
|
function renderGcode(element, state, dispatch) {
|
||||||
|
const currentLine = currentGcodeExecutionLine(state);
|
||||||
const rows = state.programLines
|
const rows = state.programLines
|
||||||
.map((line, index) => {
|
.map((line, index) => {
|
||||||
const lineNumber = state.programStartLine + index;
|
const lineNumber = state.programStartLine + index;
|
||||||
|
const execution = state.programLineExecution?.[lineNumber] || null;
|
||||||
const active = lineNumber === state.activeLine ? " active" : "";
|
const active = lineNumber === state.activeLine ? " active" : "";
|
||||||
return `<li class="gcode-row${active}" data-program-line="${lineNumber}"><span>${lineNumber}</span><code>${escapeHtml(line)}</code></li>`;
|
const status = execution?.status || (lineNumber < state.activeLine ? "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>
|
||||||
|
`;
|
||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
const programEndLine = state.programStartLine + Math.max(state.programLines.length - 1, 0);
|
const programEndLine = state.programStartLine + Math.max(state.programLines.length - 1, 0);
|
||||||
@@ -210,7 +224,7 @@ function renderGcode(element, state, dispatch) {
|
|||||||
<div class="gcode-header">
|
<div class="gcode-header">
|
||||||
<strong>${escapeHtml(state.activeProgram)}</strong>
|
<strong>${escapeHtml(state.activeProgram)}</strong>
|
||||||
<span data-program-source="${state.programSource}">${state.programSource}</span>
|
<span data-program-source="${state.programSource}">${state.programSource}</span>
|
||||||
<span data-active-program-line="${state.activeLine}">Current line ${state.activeLine}</span>
|
<span data-active-program-line="${currentLine}" data-current-gcode-line="${currentLine}">Executing line ${currentLine}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="linuxcnc-source-row" data-linuxcnc-gcode-source="row">
|
<div class="linuxcnc-source-row" data-linuxcnc-gcode-source="row">
|
||||||
<label>
|
<label>
|
||||||
@@ -272,6 +286,19 @@ function renderGcode(element, state, dispatch) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function currentGcodeExecutionLine(state) {
|
||||||
|
const feedbackLine = Number(state.programRuntimeFeedback?.line);
|
||||||
|
if (Number.isFinite(feedbackLine) && feedbackLine > 0) {
|
||||||
|
return feedbackLine;
|
||||||
|
}
|
||||||
|
const activeLine = Number(state.activeLine);
|
||||||
|
return Number.isFinite(activeLine) && activeLine > 0 ? activeLine : state.programStartLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentGcodeLineSource(state) {
|
||||||
|
return state.programRuntimeFeedback?.sourceMode || state.programExecutionSourceMode || "ui-state";
|
||||||
|
}
|
||||||
|
|
||||||
function renderLinuxCncGcodeSourceOptions(state) {
|
function renderLinuxCncGcodeSourceOptions(state) {
|
||||||
const sources = state.machineFileStaging.gcodeSources || [];
|
const sources = state.machineFileStaging.gcodeSources || [];
|
||||||
if (sources.length === 0) {
|
if (sources.length === 0) {
|
||||||
@@ -444,6 +471,21 @@ function formatProgramRuntimeDtg(state) {
|
|||||||
return `DTG ${formatNumber(dtg.x, 3)} / ${formatNumber(dtg.y, 3)} / ${formatNumber(dtg.z, 3)} distance ${formatNumber(feedback.distanceToGo, 3)}`;
|
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) {
|
function formatDuration(seconds) {
|
||||||
const safeSeconds = Math.max(Number(seconds) || 0, 0);
|
const safeSeconds = Math.max(Number(seconds) || 0, 0);
|
||||||
const minutes = Math.floor(safeSeconds / 60);
|
const minutes = Math.floor(safeSeconds / 60);
|
||||||
@@ -604,6 +646,7 @@ function renderBottomControls(element, state, dispatch) {
|
|||||||
const controls = [
|
const controls = [
|
||||||
["Open", "OPEN", null],
|
["Open", "OPEN", null],
|
||||||
["Reload", "RELOAD", () => dispatch({ type: "RELOAD_PROGRAM" })],
|
["Reload", "RELOAD", () => dispatch({ type: "RELOAD_PROGRAM" })],
|
||||||
|
["Run Ready", "RUN_READY", () => dispatch({ type: "RUN_READY" })],
|
||||||
["Run", "RUN", () => dispatch({ type: "RUN" })],
|
["Run", "RUN", () => dispatch({ type: "RUN" })],
|
||||||
["Stop", "STOP", () => dispatch({ type: "STOP" })],
|
["Stop", "STOP", () => dispatch({ type: "STOP" })],
|
||||||
["Pause", "PAUSE", () => dispatch({ type: "PAUSE" })],
|
["Pause", "PAUSE", () => dispatch({ type: "PAUSE" })],
|
||||||
@@ -659,6 +702,7 @@ function renderBottomControls(element, state, dispatch) {
|
|||||||
function bottomControlGate(state, action) {
|
function bottomControlGate(state, action) {
|
||||||
const actionMap = {
|
const actionMap = {
|
||||||
RUN: { type: "RUN" },
|
RUN: { type: "RUN" },
|
||||||
|
RUN_READY: { type: "UI_CONTROL" },
|
||||||
STEP: { type: "STEP" },
|
STEP: { type: "STEP" },
|
||||||
PAUSE: { type: "PAUSE" },
|
PAUSE: { type: "PAUSE" },
|
||||||
RESUME: { type: "RESUME" },
|
RESUME: { type: "RESUME" },
|
||||||
|
|||||||
@@ -116,6 +116,145 @@ npm --prefix web-rtcp-5axis-sim-plan/app run build: PASS
|
|||||||
npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node: PASS
|
npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node: PASS
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 2026-06-23 03:52 EDT - 云端 8092 RUN 修复、部署和复测
|
||||||
|
|
||||||
|
User request:
|
||||||
|
|
||||||
|
- 测试并完善 `https://82.156.24.101:8092/` 的 `run` 功能。
|
||||||
|
- 使用 `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/working_run/test_linuxcnc_source` 下面的 INI/G-code 测试。
|
||||||
|
- 要求 RUN 按实际行执行、按实际 feed 执行、各轴数据正确,并在程序列表显示每行执行过程。
|
||||||
|
- 约束:只应在 `ON + AUTO + IDLE + homed` 下进入 `EMC_TASK_PLAN_RUN`,且 session 初始化不能把已 POWER/HOME 的 UI 状态重置回 off/unhomed。
|
||||||
|
|
||||||
|
Process:
|
||||||
|
|
||||||
|
1. Read workspace instructions and existing repository context:
|
||||||
|
- `/home/meswork/cnc_wams/AGENTS.md`
|
||||||
|
- `qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs`
|
||||||
|
- `web-rtcp-5axis-sim-plan/app/src/state/store.js`
|
||||||
|
- `web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
|
||||||
|
- `web-rtcp-5axis-sim-plan/app/src/runtime/linuxcnc-machine-file-staging.js`
|
||||||
|
- `web-rtcp-5axis-sim-plan/app/src/runtime/linuxcnc-task-hal-runtime.js`
|
||||||
|
- `web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs`
|
||||||
|
- `web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs`
|
||||||
|
2. Reviewed existing local dirty worktree and existing evidence files. Existing local report already showed local PASS, but cloud target still needed verification.
|
||||||
|
3. Ran cloud Puppeteer probe against `https://82.156.24.101:8092/`.
|
||||||
|
- Initial cloud probe output:
|
||||||
|
`/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-run-debug-current/cloud-run-debug-current.json`
|
||||||
|
- Finding: cloud page had `Run Ready`, but after clicking it the test timed out waiting for `taskState=on/allHomed/auto/tcp`.
|
||||||
|
4. Ran a shorter cloud `Run Ready` probe:
|
||||||
|
- Output:
|
||||||
|
`/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-run-ready-after.json`
|
||||||
|
- Finding before deploy:
|
||||||
|
- Before: `taskState=estop-reset`, `mode=manual`, `allHomed=false`.
|
||||||
|
- After `Run Ready`: `taskState=on`, `mode=auto`, `rtcpState=on`, but `allHomed=false`.
|
||||||
|
- Result: subsequent `Run` was still blocked by the homing gate.
|
||||||
|
5. Added focused regression coverage in:
|
||||||
|
- `web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs`
|
||||||
|
- New `verifyRunReadySequence` path validates:
|
||||||
|
- `RUN_READY` sets `powerOn=true`, `taskState=on`, `mode=auto`, `allHomed=true`, `rtcpState=on`.
|
||||||
|
- A following `RUN` enters `linuxcnc-task-motion-hal-wasm` feedback.
|
||||||
|
- `operatorMessage` is not `run blocked: home machine first`.
|
||||||
|
6. Updated `verify_run_preconditions.mjs` to match the current explicit gate order:
|
||||||
|
- ON + manual + unhomed: `run blocked: switch to auto mode first`.
|
||||||
|
- ON + auto + unhomed: `run blocked: home machine first`.
|
||||||
|
7. Verified local node tests:
|
||||||
|
- `node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs`
|
||||||
|
- `run_feedback_status_loop_smoke=ok`
|
||||||
|
- `run_ready_sequence_smoke=ok`
|
||||||
|
- `node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs`
|
||||||
|
- `run_preconditions_ini_profile_smoke=ok`
|
||||||
|
- `run_preconditions_kinematics_smoke=ok`
|
||||||
|
- `run_preconditions_machine_file_smoke=ok`
|
||||||
|
8. Ran full local browser evidence capture:
|
||||||
|
- Command:
|
||||||
|
`node qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs`
|
||||||
|
- Result:
|
||||||
|
`test_linuxcnc_source_run_status=PASS`
|
||||||
|
- JSON:
|
||||||
|
`/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report.json`
|
||||||
|
- DOCX:
|
||||||
|
`/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report-2026-06-23.docx`
|
||||||
|
- Key PASS evidence:
|
||||||
|
- `test_linuxcnc_source INI used`
|
||||||
|
- `test_linuxcnc_source G-code hash matches loaded source`
|
||||||
|
- `刀具路径使用完整 canonical motion 点`
|
||||||
|
- `RUN 采样数量`
|
||||||
|
- `每行执行高亮同步`
|
||||||
|
- `程序列表显示每行执行过程`
|
||||||
|
- `执行轨迹可见`
|
||||||
|
- `实时轴值来自 task/HAL feedback`
|
||||||
|
- `RUN feed 不是固定 3600 mm/min`
|
||||||
|
- `RUN feed 随实际 G-code F/G93 段变化`
|
||||||
|
- `RUN 使用 G93 inverse-time feed`
|
||||||
|
- `task/HAL cycle 推进`
|
||||||
|
- `STOP 后 loop 停止`
|
||||||
|
9. Built static site:
|
||||||
|
- Command:
|
||||||
|
`npm run build` from `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app`
|
||||||
|
- Result:
|
||||||
|
`gmoccapy_static_build=ok`
|
||||||
|
- Build output:
|
||||||
|
`/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/dist`
|
||||||
|
10. Created deployment tarball:
|
||||||
|
- `/tmp/web-rtcp-5axis-sim-8092-runfix-20260623.tar.gz`
|
||||||
|
11. Connected to cloud host:
|
||||||
|
- SSH target: `ubuntu@82.156.24.101`
|
||||||
|
- Confirmed nginx was listening on port `8092`.
|
||||||
|
- Confirmed nginx site root:
|
||||||
|
`/var/www/web-rtcp-5axis-sim`
|
||||||
|
- Noted older home copy:
|
||||||
|
`/home/ubuntu/web-rtcp-5axis-sim-8092`
|
||||||
|
12. Uploaded release tarball to:
|
||||||
|
- `/home/ubuntu/tmp/web-rtcp-5axis-sim-8092-runfix-20260623.tar.gz`
|
||||||
|
13. Deployed to nginx site:
|
||||||
|
- Backup:
|
||||||
|
`/home/ubuntu/tmp/web-rtcp-backups/web-rtcp-5axis-sim-before-runfix-20260623-154606`
|
||||||
|
- Release extraction:
|
||||||
|
`/home/ubuntu/tmp/web-rtcp-5axis-sim-8092-runfix-20260623-154606`
|
||||||
|
- Synced release into:
|
||||||
|
`/var/www/web-rtcp-5axis-sim`
|
||||||
|
- Ran:
|
||||||
|
`sudo nginx -t`
|
||||||
|
- Result:
|
||||||
|
`nginx: configuration file /etc/nginx/nginx.conf test is successful`
|
||||||
|
- Reloaded nginx.
|
||||||
|
14. Ran cloud post-deploy RUN probe:
|
||||||
|
- Output:
|
||||||
|
`/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-run-after-deploy/cloud-run-after-deploy.json`
|
||||||
|
- Key evidence:
|
||||||
|
- `03-run-ready`: `allHomed=true`, `mode=auto`, `taskState=on`, `feedback=linuxcnc-task-motion-hal-wasm`.
|
||||||
|
- `04-run-0500`: `runState=running`, `activeLine=9`, `velocity=2100`, line text includes `running | F 2100.0 ... cycle 19/190`.
|
||||||
|
- `06-run-7000`: `runState=running`, `activeLine=11`, `velocity=283.9176`, line text includes actual axis values and cycle.
|
||||||
|
- First post-deploy probe hit Puppeteer `Node is detached from document` while clicking STOP because the button node was re-rendered during the running UI update. This was a test-click issue, not a page failure.
|
||||||
|
15. Ran cloud STOP-specific post-deploy probe using DOM re-query for the STOP button:
|
||||||
|
- Output:
|
||||||
|
`/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-run-stop-after-deploy/cloud-run-stop-after-deploy.json`
|
||||||
|
- Key evidence:
|
||||||
|
- `01-ready`: `runState=idle`, `allHomed=true`, `feedback=linuxcnc-task-motion-hal-wasm`, `lineText="idle | F 3600.0 ... cycle 4/40"`.
|
||||||
|
- `02-running`: `runState=running`, `activeLine=9`, `loopActive=true`, `feedback=linuxcnc-task-motion-hal-wasm`, `velocity=2100`, line text includes `running | F 2100.0 ... cycle 34/340`.
|
||||||
|
- `03-stopped`: `runState=stopped`, `loopActive=false`, `feedback=linuxcnc-task-motion-hal-wasm`, line text includes `stopped | F 2100.0 ... cycle 50/500`.
|
||||||
|
16. Closed SSH session.
|
||||||
|
|
||||||
|
Files modified by this execution:
|
||||||
|
|
||||||
|
```text
|
||||||
|
web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs
|
||||||
|
web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
|
||||||
|
qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report.json
|
||||||
|
qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report-2026-06-23.docx
|
||||||
|
qa/web-rtcp-5axis-site-test/screenshots/test-linuxcnc-source-run/*.png
|
||||||
|
qa/web-rtcp-5axis-site-test/output/cloud-run-after-deploy/*
|
||||||
|
qa/web-rtcp-5axis-site-test/output/cloud-run-stop-after-deploy/*
|
||||||
|
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Cloud deployment result:
|
||||||
|
|
||||||
|
- Deployed updated static app to `https://82.156.24.101:8092/`.
|
||||||
|
- Confirmed `RUN_READY` no longer loses `allHomed`.
|
||||||
|
- Confirmed `RUN` enters task/HAL feedback and advances line/feed/axis execution.
|
||||||
|
- Confirmed `STOP` stops the status loop.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Execution timestamp: 2026-06-22 17:55 EDT
|
Execution timestamp: 2026-06-22 17:55 EDT
|
||||||
@@ -1225,3 +1364,551 @@ Files modified or generated by this execution:
|
|||||||
```text
|
```text
|
||||||
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 2026-06-22 22:54:09 EDT website packaging of test_linuxcnc_source
|
||||||
|
|
||||||
|
User request: based on the previous round, package the files under
|
||||||
|
`/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/working_run/test_linuxcnc_source`
|
||||||
|
into the website, make sure the programs shown in the `LinuxCNC 5-axis source`
|
||||||
|
UI are uploaded with the site/server bundle, and ensure the source programs are
|
||||||
|
used correctly.
|
||||||
|
|
||||||
|
Process summary:
|
||||||
|
|
||||||
|
1. Inspected the current website packaging and LinuxCNC source loading path.
|
||||||
|
- Read `web-rtcp-5axis-sim-plan/app/scripts/build-static.mjs`.
|
||||||
|
- Read `web-rtcp-5axis-sim-plan/app/src/runtime/linuxcnc-machine-file-staging.js`.
|
||||||
|
- Confirmed the UI source selector is backed by machine-file staging and
|
||||||
|
vendored LinuxCNC demo files.
|
||||||
|
2. Verified the relevant source assets.
|
||||||
|
- Confirmed `working_run/test_linuxcnc_source` currently contains:
|
||||||
|
- `xyzac-trt.ini`
|
||||||
|
- `impeller-7bl-xyzac.ngc`
|
||||||
|
- Verified both files are byte-identical to their vendored LinuxCNC source
|
||||||
|
counterparts by SHA-256:
|
||||||
|
- `xyzac-trt.ini`
|
||||||
|
- `impeller-7bl-xyzac.ngc`
|
||||||
|
3. Implemented website packaging for the test source directory.
|
||||||
|
- Updated `web-rtcp-5axis-sim-plan/app/scripts/build-static.mjs`.
|
||||||
|
- Added `copyBundledTestLinuxCncSourceAssets()`.
|
||||||
|
- Build output now copies:
|
||||||
|
- `web-rtcp-5axis-sim-plan/working_run/test_linuxcnc_source/*`
|
||||||
|
to
|
||||||
|
- `web-rtcp-5axis-sim-plan/app/dist/working_run/test_linuxcnc_source/*`
|
||||||
|
4. Updated machine-file staging source resolution.
|
||||||
|
- Updated `web-rtcp-5axis-sim-plan/app/src/runtime/linuxcnc-machine-file-staging.js`.
|
||||||
|
- Added `DEFAULT_TEST_SOURCE_ROOT_URLS`.
|
||||||
|
- Updated `sourceUrlsFor()` to prefer packaged `working_run/test_linuxcnc_source`
|
||||||
|
assets for:
|
||||||
|
- `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini`
|
||||||
|
- `configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/impeller-7bl-xyzac.ngc`
|
||||||
|
- Preserved vendored LinuxCNC URLs as fallback so the existing source list and
|
||||||
|
staging flow continue to work.
|
||||||
|
5. Built and validated the website package.
|
||||||
|
- Ran `npm run build` in `web-rtcp-5axis-sim-plan/app`.
|
||||||
|
- Confirmed:
|
||||||
|
- `app/dist/working_run/test_linuxcnc_source/xyzac-trt.ini`
|
||||||
|
- `app/dist/working_run/test_linuxcnc_source/impeller-7bl-xyzac.ngc`
|
||||||
|
exist in the published output.
|
||||||
|
6. Ran validation commands.
|
||||||
|
- `node web-rtcp-5axis-sim-plan/tests/node/verify_machine_file_staging.mjs`
|
||||||
|
-> `machine_file_staging_smoke=ok`
|
||||||
|
- `node web-rtcp-5axis-sim-plan/tests/node/verify_impeller_feed_task_hal_run.mjs`
|
||||||
|
-> `impeller_feed_task_hal_run=ok`
|
||||||
|
- `bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh`
|
||||||
|
-> `gmoccapy_dist_smoke=ok`
|
||||||
|
7. Started a local HTTP server for the built site.
|
||||||
|
- Working directory:
|
||||||
|
`web-rtcp-5axis-sim-plan/app/dist`
|
||||||
|
- URL:
|
||||||
|
`http://127.0.0.1:4173/`
|
||||||
|
- Confirmed HTTP 200 for:
|
||||||
|
- `/index.html`
|
||||||
|
- `/working_run/test_linuxcnc_source/impeller-7bl-xyzac.ngc`
|
||||||
|
|
||||||
|
Files modified by this execution:
|
||||||
|
|
||||||
|
```text
|
||||||
|
web-rtcp-5axis-sim-plan/app/scripts/build-static.mjs
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/runtime/linuxcnc-machine-file-staging.js
|
||||||
|
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2026-06-22 23:30:02 EDT cloud short-viewport gcode panel diagnosis and fix
|
||||||
|
|
||||||
|
User report: on the cloud-hosted site, the G-code display area showed only one
|
||||||
|
line with a large blank area above it.
|
||||||
|
|
||||||
|
Process summary:
|
||||||
|
|
||||||
|
1. Reproduced the issue under a short viewport similar to the user screenshot.
|
||||||
|
- Used a local browser run at `848x331`.
|
||||||
|
- Confirmed the problem was reproducible with
|
||||||
|
`impeller-7bl-xyzac.ngc`.
|
||||||
|
2. Measured DOM/layout state.
|
||||||
|
- Verified the G-code program was not a one-line program.
|
||||||
|
- Confirmed the loaded impeller program had `4507` lines.
|
||||||
|
- Confirmed the status text
|
||||||
|
`8 staged / configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/impeller-7bl-xyzac.ngc`
|
||||||
|
is styled with `white-space: nowrap` and `text-overflow: ellipsis`, so the
|
||||||
|
one-line symptom was not caused by that text wrapping.
|
||||||
|
3. Found the actual cause.
|
||||||
|
- The shell layout had hard minimum viewport assumptions and an unconstrained
|
||||||
|
`#app` container combination that could cause the grid to expand beyond the
|
||||||
|
real viewport while `body` remained `overflow: hidden`.
|
||||||
|
- In the short viewport case, the browser only exposed a bottom sliver of the
|
||||||
|
G-code list area, which looked like “only one visible line with lots of blank
|
||||||
|
space above”.
|
||||||
|
4. Implemented a responsive layout fix in
|
||||||
|
`web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css`.
|
||||||
|
- Removed the old hard `1180px / 640px` min-size behavior.
|
||||||
|
- Made the main shell columns responsive.
|
||||||
|
- Added a constrained `#app` viewport container.
|
||||||
|
- Added compact layout rules for shorter heights.
|
||||||
|
- Added an extra compact mode for very short heights (`max-height: 420px`) that:
|
||||||
|
- prioritizes the G-code panel height,
|
||||||
|
- hides `info-tabs`, `override`, `spindle-coolant`, and `mdi-panel`,
|
||||||
|
- reduces chrome height so the list can show multiple rows.
|
||||||
|
5. Validated after the fix.
|
||||||
|
- Rebuilt the site: `npm run build`
|
||||||
|
- Re-tested at `848x331`.
|
||||||
|
- Post-fix measured result:
|
||||||
|
- shell height = `331`
|
||||||
|
- G-code panel height = `177`
|
||||||
|
- G-code list height = `93`
|
||||||
|
- visible G-code rows = `4`
|
||||||
|
- Confirmed compact-mode hidden panels:
|
||||||
|
- `mdiVisible = none`
|
||||||
|
- `infoVisible = none`
|
||||||
|
6. Regression check.
|
||||||
|
- Ran `bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh`
|
||||||
|
- Result: `gmoccapy_dist_smoke=ok`
|
||||||
|
|
||||||
|
Files modified by this execution:
|
||||||
|
|
||||||
|
```text
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
|
||||||
|
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2026-06-22 23:40:12 EDT manual short-viewport browser test and screenshots
|
||||||
|
|
||||||
|
User request: execute a test and capture screenshots.
|
||||||
|
|
||||||
|
Process summary:
|
||||||
|
|
||||||
|
1. Rebuilt the static site package.
|
||||||
|
- Ran `npm run build` in `web-rtcp-5axis-sim-plan/app`
|
||||||
|
- Result: `gmoccapy_static_build=ok`
|
||||||
|
2. Used the running local dist server at `http://127.0.0.1:4173/`.
|
||||||
|
3. Ran a manual browser test with Puppeteer at a short viewport.
|
||||||
|
- Viewport: `848x331`
|
||||||
|
- Program selected:
|
||||||
|
`configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/impeller-7bl-xyzac.ngc`
|
||||||
|
4. Measured the rendered G-code panel.
|
||||||
|
- `visibleGcodeRows = 21`
|
||||||
|
- Confirmed the first visible lines include the expected impeller header and
|
||||||
|
program body rows.
|
||||||
|
5. Saved artifacts.
|
||||||
|
- Full-page screenshot:
|
||||||
|
`qa/web-rtcp-5axis-site-test/screenshots/manual-short-viewport/impeller-short-viewport-2026-06-22.png`
|
||||||
|
- G-code panel screenshot:
|
||||||
|
`qa/web-rtcp-5axis-site-test/screenshots/manual-short-viewport/impeller-short-viewport-gcode-2026-06-22.png`
|
||||||
|
- Metrics JSON:
|
||||||
|
`qa/web-rtcp-5axis-site-test/screenshots/manual-short-viewport/impeller-short-viewport-2026-06-22.json`
|
||||||
|
6. Visually inspected the cropped G-code screenshot.
|
||||||
|
- Verified the panel is nonblank and shows multiple visible rows, with line
|
||||||
|
numbers and impeller program content.
|
||||||
|
|
||||||
|
Files generated by this execution:
|
||||||
|
|
||||||
|
```text
|
||||||
|
qa/web-rtcp-5axis-site-test/screenshots/manual-short-viewport/impeller-short-viewport-2026-06-22.png
|
||||||
|
qa/web-rtcp-5axis-site-test/screenshots/manual-short-viewport/impeller-short-viewport-gcode-2026-06-22.png
|
||||||
|
qa/web-rtcp-5axis-site-test/screenshots/manual-short-viewport/impeller-short-viewport-2026-06-22.json
|
||||||
|
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2026-06-22 23:55:45 EDT deploy current dist to cloud site 82.156.24.101:8092
|
||||||
|
|
||||||
|
User request: publish the current version to the cloud website.
|
||||||
|
|
||||||
|
Process summary:
|
||||||
|
|
||||||
|
1. Read deployment context from `git.txt` and prior deployment notes in this log.
|
||||||
|
- Confirmed cloud site target: `https://82.156.24.101:8092/`
|
||||||
|
- Confirmed prior nginx deployment root: `/var/www/web-rtcp-5axis-sim`
|
||||||
|
2. Rebuilt the static application before publish.
|
||||||
|
- Ran `npm run build` in `web-rtcp-5axis-sim-plan/app`
|
||||||
|
- Result: `gmoccapy_static_build=ok`
|
||||||
|
3. Packaged the current build output.
|
||||||
|
- Created:
|
||||||
|
`/tmp/web-rtcp-5axis-sim-8092.tar.gz`
|
||||||
|
4. Confirmed remote access details.
|
||||||
|
- SSH target: `ubuntu@82.156.24.101`
|
||||||
|
- `scp` and `ssh` available locally.
|
||||||
|
- `paramiko` available locally and used for reliable remote execution.
|
||||||
|
5. Uploaded and deployed with explicit remote replacement.
|
||||||
|
- Uploaded tarball to:
|
||||||
|
`/tmp/web-rtcp-5axis-sim-8092.tar.gz`
|
||||||
|
- Extracted to a timestamped remote staging directory.
|
||||||
|
- Backed up the previous nginx root to:
|
||||||
|
`/var/www/web-rtcp-5axis-sim.bak.<timestamp>`
|
||||||
|
- Replaced `/var/www/web-rtcp-5axis-sim` with the new build contents.
|
||||||
|
- Set ownership to `www-data:www-data`.
|
||||||
|
- Ran `nginx -t` and reloaded nginx.
|
||||||
|
6. Verified remote HTTPS response after deployment.
|
||||||
|
- Remote local check returned:
|
||||||
|
`HTTP/2 200`
|
||||||
|
- Public check:
|
||||||
|
`curl -k -I https://82.156.24.101:8092/`
|
||||||
|
returned `HTTP/2 200`
|
||||||
|
7. Verified the deployed site contains the new responsive compact layout.
|
||||||
|
- Fetched remote CSS from:
|
||||||
|
`https://82.156.24.101:8092/src/styles/gmoccapy.css`
|
||||||
|
- Confirmed presence of:
|
||||||
|
- `#app` viewport constraint
|
||||||
|
- `@media (max-height: 420px)`
|
||||||
|
- compact shell rows
|
||||||
|
8. Ran a cloud browser validation against the public URL.
|
||||||
|
- Viewport: `848x331`
|
||||||
|
- Program:
|
||||||
|
`configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/impeller-7bl-xyzac.ngc`
|
||||||
|
- Result:
|
||||||
|
`cloud_short_viewport_visible_rows=21`
|
||||||
|
9. Saved cloud verification artifacts.
|
||||||
|
- Full-page screenshot:
|
||||||
|
`qa/web-rtcp-5axis-site-test/screenshots/cloud-short-viewport/cloud-impeller-short-viewport-2026-06-22.png`
|
||||||
|
- G-code panel screenshot:
|
||||||
|
`qa/web-rtcp-5axis-site-test/screenshots/cloud-short-viewport/cloud-impeller-short-viewport-gcode-2026-06-22.png`
|
||||||
|
- Metrics JSON:
|
||||||
|
`qa/web-rtcp-5axis-site-test/screenshots/cloud-short-viewport/cloud-impeller-short-viewport-2026-06-22.json`
|
||||||
|
10. Cleaned up the temporary local askpass helper.
|
||||||
|
|
||||||
|
Files generated by this execution:
|
||||||
|
|
||||||
|
```text
|
||||||
|
qa/web-rtcp-5axis-site-test/screenshots/cloud-short-viewport/cloud-impeller-short-viewport-2026-06-22.png
|
||||||
|
qa/web-rtcp-5axis-site-test/screenshots/cloud-short-viewport/cloud-impeller-short-viewport-gcode-2026-06-22.png
|
||||||
|
qa/web-rtcp-5axis-site-test/screenshots/cloud-short-viewport/cloud-impeller-short-viewport-2026-06-22.json
|
||||||
|
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2026-06-22 23:40:12 EDT compact DRO layout for 20+ visible gcode rows
|
||||||
|
|
||||||
|
User request: increase the visible G-code line count to at least 20 and compress
|
||||||
|
the axis display area (`X Y Z A/B C`) to make room.
|
||||||
|
|
||||||
|
Process summary:
|
||||||
|
|
||||||
|
1. Reviewed current DRO rendering and layout.
|
||||||
|
- Read `app/src/ui/gmoccapy-shell.js` and `app/src/styles/gmoccapy.css`.
|
||||||
|
- Confirmed the full-size DRO panel and title/bottom chrome still consumed too
|
||||||
|
much height in very short viewports.
|
||||||
|
2. Tightened the ultra-short-height layout in
|
||||||
|
`web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css`.
|
||||||
|
- Reduced title row height.
|
||||||
|
- Reduced top preview/DRO row height.
|
||||||
|
- Reduced bottom-controls row height.
|
||||||
|
- Reduced G-code header/source-row/progress overhead.
|
||||||
|
- Reduced G-code row font size and row height.
|
||||||
|
3. Compressed the axis display area.
|
||||||
|
- In `max-height: 420px` mode:
|
||||||
|
- converted the DRO panel to a single compact row,
|
||||||
|
- changed the six axes to a 6-column compact strip,
|
||||||
|
- hid `G54/Abs`, `DTG`, and `TCP` strip details,
|
||||||
|
- reduced axis/value font sizes.
|
||||||
|
4. Rebuilt and measured the same short viewport (`848x331`).
|
||||||
|
- Post-fix metrics:
|
||||||
|
- `gcodeHeight = 253`
|
||||||
|
- `listHeight = 206`
|
||||||
|
- `visibleRows = 21`
|
||||||
|
- `rowHeight = 10`
|
||||||
|
- `droHeight = 24`
|
||||||
|
- `titleHeight = 24`
|
||||||
|
- `bottomHeight = 28`
|
||||||
|
5. Regression check.
|
||||||
|
- Ran `bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh`
|
||||||
|
- Result: `gmoccapy_dist_smoke=ok`
|
||||||
|
|
||||||
|
Files modified by this execution:
|
||||||
|
|
||||||
|
```text
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
|
||||||
|
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2026-06-22 G-code full list visibility and RUN usability fix
|
||||||
|
|
||||||
|
User request: after selecting a source program, the program list should show the
|
||||||
|
whole source program. If the line count is large, dynamic loading is acceptable.
|
||||||
|
Also RUN is not easy to use.
|
||||||
|
|
||||||
|
Process summary:
|
||||||
|
|
||||||
|
1. Inspected current G-code panel and RUN controls.
|
||||||
|
- `app/src/ui/gmoccapy-shell.js`
|
||||||
|
- `app/src/styles/gmoccapy.css`
|
||||||
|
- `app/src/state/store.js`
|
||||||
|
- `app/src/state/linuxcnc-task-policy.js`
|
||||||
|
2. Found the main display issue.
|
||||||
|
- `.gcode-panel` CSS defined only four grid rows:
|
||||||
|
`32px minmax(0, 1fr) 20px 54px`
|
||||||
|
- The DOM actually renders five sections:
|
||||||
|
G-code header, LinuxCNC source selector row, program list, progress, MDI.
|
||||||
|
- This caused the source selector and program list to overlap/compress, making
|
||||||
|
the long source program appear as only a tiny visible row.
|
||||||
|
3. Fixed full source-program list visibility.
|
||||||
|
- Updated `.gcode-panel` to five rows:
|
||||||
|
`auto auto minmax(96px, 1fr) 20px 54px`
|
||||||
|
- Added `.linuxcnc-source-row` layout rules so the source selector/status no
|
||||||
|
longer consume the program list area.
|
||||||
|
- The source program list remains scrollable for large programs, so all lines
|
||||||
|
are available without rendering outside the panel.
|
||||||
|
4. Improved RUN usability.
|
||||||
|
- Added `RUN_READY` store action.
|
||||||
|
- Added `runReadySequence()` in `store.js`.
|
||||||
|
- The sequence loads a default G-code source when needed, initializes task/HAL,
|
||||||
|
powers on, homes all joints, switches to AUTO mode, and enables the profile TCP
|
||||||
|
kinematics type.
|
||||||
|
- Added a `Run Ready` bottom-control button before `Run`.
|
||||||
|
- Adjusted bottom-control layout to fit the additional control.
|
||||||
|
5. Preserved existing RUN semantics.
|
||||||
|
- RUN still uses `validateRunPreconditions`.
|
||||||
|
- The new `Run Ready` button prepares the machine instead of bypassing the
|
||||||
|
LinuxCNC-style gates.
|
||||||
|
6. Validation completed.
|
||||||
|
- `node --check web-rtcp-5axis-sim-plan/app/src/state/store.js`
|
||||||
|
- `node --check web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
|
||||||
|
- `npm --prefix web-rtcp-5axis-sim-plan/app run build`
|
||||||
|
- `node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs`
|
||||||
|
- `node qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs`
|
||||||
|
7. Browser test result:
|
||||||
|
- `test_linuxcnc_source_run_status=PASS`
|
||||||
|
- JSON report:
|
||||||
|
`/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report.json`
|
||||||
|
- DOCX report:
|
||||||
|
`/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report-2026-06-23.docx`
|
||||||
|
8. Relevant PASS evidence:
|
||||||
|
- `RUN 采样数量`
|
||||||
|
- `每行执行高亮同步`
|
||||||
|
- `程序列表显示每行执行过程`
|
||||||
|
- `RUN feed 不是固定 3600 mm/min`
|
||||||
|
- `RUN feed 随实际 G-code F/G93 段变化`
|
||||||
|
- `RUN 使用 G93 inverse-time feed`
|
||||||
|
|
||||||
|
Execution timestamp:
|
||||||
|
|
||||||
|
- `2026-06-22 22:42:20 EDT`
|
||||||
|
|
||||||
|
Files modified or generated by this execution:
|
||||||
|
|
||||||
|
```text
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/state/store.js
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
|
||||||
|
qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report.json
|
||||||
|
qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report-2026-06-23.docx
|
||||||
|
qa/web-rtcp-5axis-site-test/screenshots/test-linuxcnc-source-run/*.png
|
||||||
|
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2026-06-22 HTTPS deploy to 82.156.24.101:8092
|
||||||
|
|
||||||
|
User request: publish the Web RTCP 5 Axis simulator by HTTPS to remote server
|
||||||
|
`82.156.24.101` on port `8092` using SSH user `ubuntu`.
|
||||||
|
|
||||||
|
Process summary:
|
||||||
|
|
||||||
|
1. Built the local static app.
|
||||||
|
- Command: `npm --prefix web-rtcp-5axis-sim-plan/app run build`
|
||||||
|
- Result: `gmoccapy_static_build=ok`
|
||||||
|
- Build output: `web-rtcp-5axis-sim-plan/app/dist`
|
||||||
|
2. Packaged the build output.
|
||||||
|
- Command: `tar -C web-rtcp-5axis-sim-plan/app/dist -czf /tmp/web-rtcp-5axis-sim-8092.tar.gz .`
|
||||||
|
- Package size: about `1.6M`
|
||||||
|
3. Uploaded the package to the remote host.
|
||||||
|
- Tooling: `scp` via interactive password handling.
|
||||||
|
- Remote path: `/tmp/web-rtcp-5axis-sim-8092.tar.gz`
|
||||||
|
4. Attempted to create a standalone systemd HTTPS Node service.
|
||||||
|
- Service name: `web-rtcp-5axis-sim-8092.service`
|
||||||
|
- Initial service failed because port `8092` was already occupied.
|
||||||
|
5. Diagnosed port ownership.
|
||||||
|
- `ss -ltnp 'sport = :8092'` showed nginx already listening on `8092`.
|
||||||
|
- `curl -k -I https://127.0.0.1:8092/` already returned HTTP 200 from nginx.
|
||||||
|
6. Removed the failed standalone systemd service.
|
||||||
|
- Disabled/stopped `web-rtcp-5axis-sim-8092.service`.
|
||||||
|
- Removed `/etc/systemd/system/web-rtcp-5axis-sim-8092.service`.
|
||||||
|
- Reloaded systemd daemon.
|
||||||
|
7. Inspected existing nginx HTTPS configuration.
|
||||||
|
- Active config: `/etc/nginx/sites-available/web-rtcp-5axis-sim`
|
||||||
|
- HTTPS listener:
|
||||||
|
- `listen 8092 ssl http2`
|
||||||
|
- `listen [::]:8092 ssl http2`
|
||||||
|
- Root: `/var/www/web-rtcp-5axis-sim`
|
||||||
|
- Certificate paths:
|
||||||
|
- `/etc/letsencrypt/live/82.156.24.101/fullchain.pem`
|
||||||
|
- `/etc/letsencrypt/live/82.156.24.101/privkey.pem`
|
||||||
|
8. Deployed this build through the existing nginx HTTPS site.
|
||||||
|
- Extracted package to a staging directory.
|
||||||
|
- Backed up previous web root:
|
||||||
|
- `/var/www/web-rtcp-5axis-sim.bak.20260623101928`
|
||||||
|
- Replaced `/var/www/web-rtcp-5axis-sim` with the new static build.
|
||||||
|
- Set ownership to `www-data:www-data`.
|
||||||
|
- Ran `nginx -t`.
|
||||||
|
- Reloaded nginx.
|
||||||
|
9. Verified HTTPS access.
|
||||||
|
- Local remote check: `curl -k -I https://127.0.0.1:8092/` returned `HTTP/2 200`.
|
||||||
|
- Public check: `curl -k -I https://82.156.24.101:8092/` returned `HTTP/2 200`.
|
||||||
|
|
||||||
|
Execution timestamp:
|
||||||
|
|
||||||
|
- `2026-06-22 22:19:39 EDT`
|
||||||
|
|
||||||
|
Published URL:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://82.156.24.101:8092/
|
||||||
|
```
|
||||||
|
|
||||||
|
Files modified or generated by this execution:
|
||||||
|
|
||||||
|
```text
|
||||||
|
web-rtcp-5axis-sim-plan/app/dist/**
|
||||||
|
/tmp/web-rtcp-5axis-sim-8092.tar.gz
|
||||||
|
remote:/tmp/web-rtcp-5axis-sim-8092.tar.gz
|
||||||
|
remote:/var/www/web-rtcp-5axis-sim/**
|
||||||
|
remote:/var/www/web-rtcp-5axis-sim.bak.20260623101928/**
|
||||||
|
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2026-06-22 realtime current G-code line display
|
||||||
|
|
||||||
|
User request: program realtime display of the currently executing G-code line number.
|
||||||
|
|
||||||
|
Process summary:
|
||||||
|
|
||||||
|
1. Inspected the existing UI and state flow.
|
||||||
|
- Checked `app/src/ui/gmoccapy-shell.js` for titlebar, G-code header, and current-line display.
|
||||||
|
- Confirmed runtime line source is already available through `state.programRuntimeFeedback.line`
|
||||||
|
during task/HAL RUN, with `state.activeLine` as fallback.
|
||||||
|
2. Implemented a dedicated realtime current-line indicator.
|
||||||
|
- Added `currentGcodeExecutionLine(state)` in `gmoccapy-shell.js`.
|
||||||
|
- Added `currentGcodeLineSource(state)` in `gmoccapy-shell.js`.
|
||||||
|
- Added a titlebar element:
|
||||||
|
- class: `current-line-indicator`
|
||||||
|
- data attribute: `data-current-gcode-line`
|
||||||
|
- source attribute: `data-current-gcode-source`
|
||||||
|
- Updated the G-code header to show `Executing line <line>` and expose the same
|
||||||
|
`data-current-gcode-line` value.
|
||||||
|
3. Styled the realtime indicator.
|
||||||
|
- Updated `app/src/styles/gmoccapy.css`.
|
||||||
|
- Added compact, stable sizing for the titlebar realtime line display.
|
||||||
|
4. Validation completed.
|
||||||
|
- `node --check web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
|
||||||
|
- `node --check web-rtcp-5axis-sim-plan/app/src/state/store.js`
|
||||||
|
- `npm --prefix web-rtcp-5axis-sim-plan/app run build`
|
||||||
|
- `node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs`
|
||||||
|
- `node qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs`
|
||||||
|
5. Browser test result:
|
||||||
|
- `test_linuxcnc_source_run_status=PASS`
|
||||||
|
- Realtime current-line evidence remained synchronized:
|
||||||
|
- `每行执行高亮同步`: PASS
|
||||||
|
- `程序列表显示每行执行过程`: PASS
|
||||||
|
|
||||||
|
Execution timestamp:
|
||||||
|
|
||||||
|
- `2026-06-22 22:09:20 EDT`
|
||||||
|
|
||||||
|
Files modified or generated by this execution:
|
||||||
|
|
||||||
|
```text
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
|
||||||
|
qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report.json
|
||||||
|
qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report-2026-06-23.docx
|
||||||
|
qa/web-rtcp-5axis-site-test/screenshots/test-linuxcnc-source-run/*.png
|
||||||
|
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2026-06-22 test_linuxcnc_source RUN completion
|
||||||
|
|
||||||
|
User request: continue completing RUN execution using files under
|
||||||
|
`/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/working_run/test_linuxcnc_source`,
|
||||||
|
execute actual G-code lines with actual feed, keep each axis data correct,
|
||||||
|
and show each line's execution process in the program list.
|
||||||
|
|
||||||
|
Process summary:
|
||||||
|
|
||||||
|
1. Inspected workspace rules and project structure.
|
||||||
|
- Confirmed `AGENTS.md` requires appending process logs to this file.
|
||||||
|
- Confirmed test source files:
|
||||||
|
- `working_run/test_linuxcnc_source/xyzac-trt.ini`
|
||||||
|
- `working_run/test_linuxcnc_source/impeller-7bl-xyzac.ngc`
|
||||||
|
2. Reviewed existing RUN/task-HAL flow.
|
||||||
|
- Read `app/src/state/store.js`, `app/src/runtime/linuxcnc-task-hal-runtime.js`,
|
||||||
|
`app/src/ui/gmoccapy-shell.js`, and `app/src/styles/gmoccapy.css`.
|
||||||
|
- Confirmed task/HAL already drives runtime feedback from the loaded motion plan,
|
||||||
|
including active line, axis pose, task/servo cycles, and current feed velocity.
|
||||||
|
3. Implemented per-line execution state.
|
||||||
|
- Added `programLineExecution` in `app/src/state/store.js`.
|
||||||
|
- Updated interpreter init, playback fallback, reload/load reset paths, and
|
||||||
|
task/HAL status application to record per-line status, feed, XYZABC axis pose,
|
||||||
|
task cycle, servo cycle, sample index, and source mode.
|
||||||
|
4. Updated the program list UI.
|
||||||
|
- Updated `app/src/ui/gmoccapy-shell.js` so each G-code row shows execution
|
||||||
|
process text such as status, feed, XYZAC values, and task/servo cycle.
|
||||||
|
- Updated `app/src/styles/gmoccapy.css` for running/done/pending line display.
|
||||||
|
5. Ensured the browser test actually uses the requested test source directory.
|
||||||
|
- Added `sourceTextOverrides` support in `app/src/runtime/linuxcnc-machine-file-staging.js`.
|
||||||
|
- Updated `qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs`
|
||||||
|
to stage `xyzac-trt.ini` and override the selected impeller G-code with the
|
||||||
|
text from `working_run/test_linuxcnc_source/impeller-7bl-xyzac.ngc`.
|
||||||
|
- Added checks that loaded source bytes match the test source file.
|
||||||
|
6. Added browser-test evidence for program-list execution process.
|
||||||
|
- Captured active-row execution text from the DOM.
|
||||||
|
- Added a PASS/FAIL check named `程序列表显示每行执行过程`.
|
||||||
|
7. Validation completed.
|
||||||
|
- `node --check web-rtcp-5axis-sim-plan/app/src/runtime/linuxcnc-machine-file-staging.js`
|
||||||
|
- `node --check web-rtcp-5axis-sim-plan/app/src/state/store.js`
|
||||||
|
- `node --check web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
|
||||||
|
- `node --check qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs`
|
||||||
|
- `node web-rtcp-5axis-sim-plan/tests/node/verify_impeller_feed_task_hal_run.mjs`
|
||||||
|
- `node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs`
|
||||||
|
- `node web-rtcp-5axis-sim-plan/tests/node/verify_machine_file_staging.mjs`
|
||||||
|
- `npm --prefix web-rtcp-5axis-sim-plan/app run build`
|
||||||
|
- `node qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs`
|
||||||
|
8. Browser test result:
|
||||||
|
- `test_linuxcnc_source_run_status=PASS`
|
||||||
|
- JSON report:
|
||||||
|
`/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report.json`
|
||||||
|
- DOCX report:
|
||||||
|
`/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report-2026-06-23.docx`
|
||||||
|
9. Key PASS evidence from the generated report:
|
||||||
|
- `test_linuxcnc_source G-code hash matches loaded source`
|
||||||
|
- `刀具路径使用完整 canonical motion 点`
|
||||||
|
- `每行执行高亮同步`
|
||||||
|
- `程序列表显示每行执行过程`
|
||||||
|
- `实时轴值来自 task/HAL feedback`
|
||||||
|
- `RUN feed 不是固定 3600 mm/min`
|
||||||
|
- `RUN feed 随实际 G-code F/G93 段变化`
|
||||||
|
- `RUN 使用 G93 inverse-time feed`
|
||||||
|
- `task/HAL cycle 推进`
|
||||||
|
|
||||||
|
Execution timestamp:
|
||||||
|
|
||||||
|
- `2026-06-22 22:00:22 EDT`
|
||||||
|
|
||||||
|
Files modified or generated by this execution:
|
||||||
|
|
||||||
|
```text
|
||||||
|
qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs
|
||||||
|
qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report.json
|
||||||
|
qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report-2026-06-23.docx
|
||||||
|
qa/web-rtcp-5axis-site-test/screenshots/test-linuxcnc-source-run/*.png
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/runtime/linuxcnc-machine-file-staging.js
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/state/store.js
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
|
||||||
|
web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
|
||||||
|
web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
|
||||||
|
```
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ await verifyRunFeedbackLoop({
|
|||||||
stopAction: "STOP",
|
stopAction: "STOP",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await verifyRunReadySequence({
|
||||||
|
profileId: "xyzac-trt",
|
||||||
|
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins_test_1.ngc",
|
||||||
|
});
|
||||||
|
|
||||||
await verifyRunFeedbackLoop({
|
await verifyRunFeedbackLoop({
|
||||||
profileId: "xyzac-trt",
|
profileId: "xyzac-trt",
|
||||||
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins_test_1.ngc",
|
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins_test_1.ngc",
|
||||||
@@ -35,6 +40,7 @@ await verifyRunFeedbackLoop({
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log("run_feedback_status_loop_smoke=ok");
|
console.log("run_feedback_status_loop_smoke=ok");
|
||||||
|
console.log("run_ready_sequence_smoke=ok");
|
||||||
|
|
||||||
async function verifyRunFeedbackLoop({ profileId, sourceRel, stopAction }) {
|
async function verifyRunFeedbackLoop({ profileId, sourceRel, stopAction }) {
|
||||||
const sdk = await createLinuxCncTaskHalSdk({
|
const sdk = await createLinuxCncTaskHalSdk({
|
||||||
@@ -138,6 +144,65 @@ async function verifyRunFeedbackLoop({ profileId, sourceRel, stopAction }) {
|
|||||||
assert.equal(state.taskHalStatus.ui.taskCycle >= cycleAfterRun, true);
|
assert.equal(state.taskHalStatus.ui.taskCycle >= cycleAfterRun, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function verifyRunReadySequence({ profileId, sourceRel }) {
|
||||||
|
const sdk = await createLinuxCncTaskHalSdk({
|
||||||
|
wasmBinary: readFileSync(wasmPath),
|
||||||
|
print() {},
|
||||||
|
printErr(message) {
|
||||||
|
console.error(message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const profile = getFiveAxisProfile(profileId);
|
||||||
|
const iniText = readFileSync(resolve(rootDir, "wasm-port/vendor/linuxcnc", profile.iniPath), "utf8");
|
||||||
|
const iniConfig = parseLinuxCncIni(iniText, {
|
||||||
|
path: profile.iniPath,
|
||||||
|
profileId: profile.id,
|
||||||
|
});
|
||||||
|
const store = createSimulationStore();
|
||||||
|
|
||||||
|
store.dispatch({ type: "ATTACH_INI_CONFIG", profileId: profile.id, iniConfig });
|
||||||
|
store.dispatch({
|
||||||
|
type: "ATTACH_KINEMATICS_RUNTIME",
|
||||||
|
runtime: await createLinuxCncKinematicsRuntime({ moduleId: iniConfig.kinematicsModuleId }),
|
||||||
|
});
|
||||||
|
store.dispatch({
|
||||||
|
type: "ATTACH_INTERPRETER_RUNTIME",
|
||||||
|
runtime: await createLinuxCncInterpreterRuntime(),
|
||||||
|
});
|
||||||
|
store.dispatch({ type: "ATTACH_TASK_HAL_RUNTIME", runtime: wrapTaskHalSdk(sdk) });
|
||||||
|
await store.stageMachineFiles();
|
||||||
|
store.dispatch({ type: "LOAD_LINUXCNC_GCODE_SOURCE", sourceRel });
|
||||||
|
await waitForState(store, (state) => (
|
||||||
|
state.taskHalSession?.programPath?.endsWith(sourceRel.split("/").at(-1)) &&
|
||||||
|
state.rtcpFrame?.sourceMode === "source-derived-kinematics-wasm"
|
||||||
|
));
|
||||||
|
|
||||||
|
store.dispatch({ type: "RUN_READY" });
|
||||||
|
await waitForTaskHalCommand(store);
|
||||||
|
await waitForState(store, (state) => state.machine.taskState === "on" && state.machine.mode === "auto");
|
||||||
|
|
||||||
|
let state = store.getState();
|
||||||
|
assert.equal(state.machine.powerOn, true);
|
||||||
|
assert.equal(state.machine.taskState, "on");
|
||||||
|
assert.equal(state.machine.mode, "auto");
|
||||||
|
assert.equal(state.machine.allHomed, true);
|
||||||
|
assert.equal(state.rtcpState, "on");
|
||||||
|
assert.equal(String(state.kinsType).startsWith("tcp-"), true);
|
||||||
|
|
||||||
|
store.dispatch({ type: "RUN" });
|
||||||
|
await waitForTaskHalCommand(store);
|
||||||
|
await waitForState(store, (nextState) => distinctActiveLines(nextState.programRuntimeFeedbackHistory).length >= 2);
|
||||||
|
|
||||||
|
state = store.getState();
|
||||||
|
assert.equal(state.programExecutionSourceMode, "linuxcnc-task-motion-hal-wasm");
|
||||||
|
assert.equal(state.programRuntimeFeedback.sourceMode, "linuxcnc-task-motion-hal-wasm");
|
||||||
|
assert.notEqual(state.operatorMessage, "run blocked: home machine first");
|
||||||
|
assert.equal(state.machine.allHomed, true);
|
||||||
|
|
||||||
|
store.dispatch({ type: "STOP" });
|
||||||
|
await waitForTaskHalCommand(store);
|
||||||
|
}
|
||||||
|
|
||||||
async function waitForTaskHalCommand(store) {
|
async function waitForTaskHalCommand(store) {
|
||||||
for (let attempt = 0; attempt < 80; attempt += 1) {
|
for (let attempt = 0; attempt < 80; attempt += 1) {
|
||||||
if (!store.getState().taskHalExecutionPending) {
|
if (!store.getState().taskHalExecutionPending) {
|
||||||
|
|||||||
@@ -132,6 +132,11 @@ assert.equal(gate.operatorMessage, "run blocked: machine must be on");
|
|||||||
store.dispatch({ type: "TOGGLE_POWER" });
|
store.dispatch({ type: "TOGGLE_POWER" });
|
||||||
gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
|
gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
|
||||||
assert.equal(gate.allowed, false);
|
assert.equal(gate.allowed, false);
|
||||||
|
assert.equal(gate.operatorMessage, "run blocked: switch to auto mode first");
|
||||||
|
|
||||||
|
store.dispatch({ type: "SET_MODE", mode: "auto" });
|
||||||
|
gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
|
||||||
|
assert.equal(gate.allowed, false);
|
||||||
assert.equal(gate.operatorMessage, "run blocked: home machine first");
|
assert.equal(gate.operatorMessage, "run blocked: home machine first");
|
||||||
|
|
||||||
store.dispatch({ type: "HOME" });
|
store.dispatch({ type: "HOME" });
|
||||||
|
|||||||