From d6a623308bcbac57920b91c612fd9204471941ec Mon Sep 17 00:00:00 2001 From: wangdequan Date: Tue, 16 Jun 2026 22:28:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=9C=9F=E5=AE=9EG=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=BC=96=E8=BE=91=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论:AXIS 风格浏览器仿真页面新增可编辑 G-code pane、Run Editor Text、get/setProgramText 和 runEditorProgramText,编辑后的真实 G-code 继续通过 LinuxCNC WASM 执行并纳入 browser gate。 --- PROJECT_COMPLETION_TRACKER.md | 2 +- text16.txt | 67 +++++++++- .../axis-style-simulation-implementation.md | 8 +- wasm-port/runtime/ui/simulation/index.html | 123 +++++++++++++++++- .../browser/real_simulation_page_smoke.html | 43 ++++++ 5 files changed, 233 insertions(+), 10 deletions(-) diff --git a/PROJECT_COMPLETION_TRACKER.md b/PROJECT_COMPLETION_TRACKER.md index ed5ec06..3566025 100644 --- a/PROJECT_COMPLETION_TRACKER.md +++ b/PROJECT_COMPLETION_TRACKER.md @@ -40,7 +40,7 @@ The project is complete for the current scope when all of the following are true | --- | --- | --- | | LinuxCNC semantic boundary | Stable | JS/browser remains glue, staging, OPFS, WASM/browser boundary, docs, and tests only. | | Real browser simulation priority | Active first priority | Future batches should prioritize the real UI/browser CNC simulation page, with visible machine/session loading, G-code program state, LinuxCNC-backed interpreter execution, machine readiness/status panels, toolpath/preview rendering from LinuxCNC-produced output or validated runtime events, and browser smoke coverage. First entry point: `wasm-port/runtime/ui/simulation/index.html`; gate: `wasm-port/tests/browser/verify_real_simulation_browser.sh`. See `wasm-port/docs/real-browser-simulation-priority.md`. | -| Real browser simulation page | MVP active | `runtime/ui/simulation/index.html` loads in Chromium, exposes a selector for multiple LinuxCNC-backed test programs, runs operator-provided G-code text/files through interpreter WASM, and includes reset/step/play/finish playback over LinuxCNC canonical motion events. It highlights active G-code and motion rows, updates live axis readout, moves the toolhead, and grows the executed SVG toolpath. Current built-in program set covers square contour, Z pocket contour, incremental loop, G2/G3 arcs, and G81 drilling. | +| Real browser simulation page | MVP active | `runtime/ui/simulation/index.html` loads in Chromium, exposes a selector for multiple LinuxCNC-backed test programs, runs operator-provided G-code text/files through interpreter WASM, includes an editable G-code pane with Run Editor Text, and includes reset/step/play/finish playback over LinuxCNC canonical motion events. It highlights active G-code and motion rows, updates live axis readout, moves the toolhead, and grows the executed SVG toolpath. Current built-in program set covers square contour, Z pocket contour, incremental loop, G2/G3 arcs, and G81 drilling. | | AXIS-style simulation shell | Phase 1 active | `runtime/ui/simulation/index.html` now uses an AXIS-inspired titlebar, menubar, toolbar, Manual/MDI tabs, Preview/DRO tabs, G-code pane, machine-state pane, and status bar while preserving current LinuxCNC-backed execution and playback APIs. Browser smoke verifies shell regions, tab switching, toolbar playback, and existing program execution. | | Simulation test program library | Active | Built-in browser simulation programs live in `wasm-port/runtime/ui/simulation/programs/`, one module per program, exported through `programs/index.js`; `verify_real_simulation_programs.sh` checks directory-backed inventory, IDs, defaults, and playback contracts. | | Vendor/source guard | Stable | `verify_vendor_sync.sh` and standalone semantic guard are required every batch. | diff --git a/text16.txt b/text16.txt index e254149..9d056fc 100644 --- a/text16.txt +++ b/text16.txt @@ -81,6 +81,12 @@ wasm-port/runtime/ui/simulation/programs/index.js - `linuxCncRealSimulationApi.runProgramText(programText, metadata)`; - `linuxCncRealSimulationApi.loadProgramFile(file)`; - toolbar `Open` file input for `.ngc/.nc/.tap/.gcode/.txt`. +- Editable AXIS-style G-code pane: + - loaded file/custom/built-in program text is visible in a textarea; + - editor text can be changed before execution; + - toolbar `Run Editor Text` executes the current editor contents; + - `getProgramText()`, `setProgramText()`, and `runEditorProgramText()` are + exposed on the browser API. - Program source metadata: - `program.source`; - `program.sourceLabel`; @@ -102,6 +108,9 @@ Current methods: - `runProgramById(programId)` - `runProgramText(programText, metadata)` - `loadProgramFile(file)` +- `getProgramText()` +- `setProgramText(text, metadata)` +- `runEditorProgramText()` - `getPlaybackFrame()` - `resetPlayback()` - `stepPlayback(delta)` @@ -169,7 +178,9 @@ Browser code must not implement: ### 1. Real G-code Editor Pane -Add an editable G-code text pane to the AXIS shell: +Status: complete. + +Implemented: - show loaded file/custom text in an editor area; - allow editing before run; @@ -177,12 +188,12 @@ Add an editable G-code text pane to the AXIS shell: - preserve line numbers and active-line highlighting; - expose `linuxCncRealSimulationApi.getProgramText()`; - expose `linuxCncRealSimulationApi.setProgramText(text, metadata)`. +- expose `linuxCncRealSimulationApi.runEditorProgramText()`. Validation: -- browser smoke edits a line, runs it, and verifies changed LinuxCNC canonical - output; -- Node smoke verifies editor text state helpers if factored into JS modules. +- browser smoke sets editor text, runs it, and verifies changed LinuxCNC + canonical output. ### 2. OPFS Program Persistence @@ -287,6 +298,48 @@ http://154.8.160.151:3000/wangdequan/cnc_wams.git ## Immediate Suggested Start -Start with "Real G-code Editor Pane" because the page can already execute -operator-provided text/files, but the text is not yet visible/editable inside -the AXIS shell after loading. +Start with "OPFS Program Persistence" because the page can now load, edit, and +run real G-code text/files, but programs are not yet saved to or restored from +OPFS from within the AXIS shell. + +## Execution Record + +### 一、2026-06-16 Real G-code Editor Pane + +按 `text16.txt` 的 Immediate Suggested Start 推进,完成 AXIS shell 内真实 G-code 编辑区。 + +本批新增实质能力: + +- `runtime/ui/simulation/index.html` 新增 editable G-code pane: + - `data-axis-shell="program-editor"`; + - `textarea[data-program-editor]`; + - `data-program-editor-status`; +- 工具栏新增: + - `Run Editor Text`; +- 页面状态新增 `programEditorState`; +- 内置程序加载、真实文本执行、真实文件加载都会同步到 editor textarea; +- editor textarea 修改后可直接运行; +- `window.linuxCncRealSimulationApi` 新增: + - `getProgramText()`; + - `setProgramText(text, metadata)`; + - `runEditorProgramText()`; +- browser smoke 新增验证: + - 初始内置程序会同步到 editor; + - `runProgramText()` 后 editor 同步自定义文本; + - `loadProgramFile()` 后 editor 同步文件文本和 filename metadata; + - `setProgramText()` 修改 editor 内容; + - `runEditorProgramText()` 通过 LinuxCNC WASM 执行修改后的 G-code,并验证 canonical output + 出现修改后的坐标。 + +关键验证: + +```text +browser_real_simulation_page_smoke=ok +real_simulation_programs_node_smoke=ok +``` + +语义边界: + +- editor 只保存和显示 G-code 文本; +- G-code 执行仍通过 LinuxCNC-backed WASM; +- JS 不解释 G-code、不规划刀路、不推导 modal/machine semantics。 diff --git a/wasm-port/docs/axis-style-simulation-implementation.md b/wasm-port/docs/axis-style-simulation-implementation.md index c43082d..6a5a73a 100644 --- a/wasm-port/docs/axis-style-simulation-implementation.md +++ b/wasm-port/docs/axis-style-simulation-implementation.md @@ -57,6 +57,7 @@ The current simulation page already has: - LinuxCNC interpreter WASM execution through `createLinuxCncInterpSdk()`; - selectable test programs under `runtime/ui/simulation/programs/`; - custom G-code text and browser File execution through LinuxCNC WASM; +- editable G-code pane plus run-editor-text workflow; - canonical output rendering; - program-line rendering; - motion table; @@ -126,7 +127,7 @@ Current implemented regions: ### Phase 2: Program Execution Workflow -Status: active; custom text/file execution is implemented. +Status: active; custom text/file execution and editor-text execution are implemented. Upgrade the G-code pane and execution controls: @@ -138,6 +139,8 @@ Upgrade the G-code pane and execution controls: - show run status, source program, motion count, and current frame. - load operator-provided G-code text through `runProgramText()`; - load browser `File` objects through `loadProgramFile()`; +- edit visible G-code in the AXIS shell and run it through + `runEditorProgramText()`; Built-in test programs must live in: @@ -231,6 +234,9 @@ Required methods: - `finishPlayback()`; - `runProgramText(programText, metadata)`; - `loadProgramFile(file)`; +- `getProgramText()`; +- `setProgramText(text, metadata)`; +- `runEditorProgramText()`; - future: `loadProgramText(text, metadata)`; - future: `loadMachineSessionFromOpfs(options)`; - future: `getMachineReadiness()`; diff --git a/wasm-port/runtime/ui/simulation/index.html b/wasm-port/runtime/ui/simulation/index.html index 331a2be..d521b14 100644 --- a/wasm-port/runtime/ui/simulation/index.html +++ b/wasm-port/runtime/ui/simulation/index.html @@ -441,6 +441,45 @@ font-size: 12px; font-weight: 650; } + .program-editor-wrap { + display: grid; + grid-template-columns: minmax(240px, 0.85fr) minmax(280px, 1fr); + min-height: 0; + } + .program-editor-panel { + min-height: 0; + border-right: 1px solid var(--line); + display: grid; + grid-template-rows: auto minmax(0, 1fr); + background: #ededed; + } + .program-editor-toolbar { + display: flex; + gap: 6px; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid var(--line); + padding: 4px 6px; + font-size: 12px; + } + .program-editor-toolbar div { + display: flex; + gap: 6px; + align-items: center; + flex-wrap: wrap; + } + .program-editor { + width: 100%; + min-height: 0; + border: 0; + resize: none; + padding: 8px 10px; + background: #fbfbfb; + color: var(--text); + font: 13px/1.45 "SFMono-Regular", "Consolas", monospace; + tab-size: 2; + outline: none; + } .axis-statusbar { display: grid; grid-template-columns: 120px minmax(120px, 1fr) minmax(180px, auto); @@ -485,6 +524,8 @@ @media (max-width: 1040px) { main { grid-template-rows: auto auto auto minmax(0, 1fr) 220px auto; } .axis-workspace { grid-template-columns: 1fr; } + .program-editor-wrap { grid-template-columns: 1fr; } + .program-editor-panel { border-right: 0; border-bottom: 1px solid var(--line); min-height: 180px; } svg { min-height: 280px; } } @media (max-width: 560px) { @@ -532,6 +573,7 @@
+
@@ -655,7 +697,18 @@ Program: Executed through the LinuxCNC-backed WASM interpreter. | Source: loading Active line -
-
+
+
+
+ Editable G-code +
+ loaded +
+
+ +
+
+
@@ -700,9 +753,11 @@ } from "./simulation-app.js"; const runButton = document.querySelector("[data-run-program]"); + const runEditorButton = document.querySelector("[data-run-editor-program]"); const programSelector = document.querySelector("[data-program-selector]"); const openProgramButton = document.querySelector("[data-open-program]"); const openProgramFile = document.querySelector("[data-open-program-file]"); + const programEditor = document.querySelector("[data-program-editor]"); const resetButton = document.querySelector("[data-playback-reset]"); const prevButton = document.querySelector("[data-playback-prev]"); const playButton = document.querySelector("[data-playback-play]"); @@ -711,6 +766,14 @@ const speedSelector = document.querySelector("[data-playback-speed]"); let playbackTimer = null; let playbackIndex = 0; + let programEditorState = { + text: "", + metadata: { + label: "Custom G-code program", + source: "editor", + sourceLabel: "Editor text", + }, + }; function setAllText(selector, value) { document.querySelectorAll(selector).forEach((node) => { @@ -738,6 +801,28 @@ return window.linuxCncRealSimulationState; } + function setProgramEditorText(text, metadata = {}) { + programEditorState = { + text, + metadata: { + ...programEditorState.metadata, + ...metadata, + }, + }; + if (programEditor && programEditor.value !== text) { + programEditor.value = text; + } + setAllText("[data-program-editor-status]", programEditorState.metadata.sourceLabel ?? "Editor text"); + return { ...programEditorState, metadata: { ...programEditorState.metadata } }; + } + + function getProgramEditorText() { + return { + text: programEditor?.value ?? programEditorState.text, + metadata: { ...programEditorState.metadata }, + }; + } + function stopPlayback() { if (playbackTimer) { clearInterval(playbackTimer); @@ -809,6 +894,11 @@ try { const state = await runRealBrowserSimulation({ documentRef: document, programId }); window.linuxCncRealSimulationState = state; + setProgramEditorText(state.programText, { + label: state.program?.label ?? "Built-in test program", + source: "builtin", + sourceLabel: state.program?.sourceLabel ?? "Built-in test program", + }); renderPlayback(0); return state; } finally { @@ -838,6 +928,7 @@ filename: metadata.filename ?? null, }; window.linuxCncRealSimulationState = state; + setProgramEditorText(programText, state.program); document.body.dataset.simulationProgramId = "custom"; document.body.dataset.simulationProgramSource = state.program.source; setAllText("[data-selected-program]", state.program.label); @@ -860,6 +951,15 @@ }); } + async function runEditorProgramText() { + const { text, metadata } = getProgramEditorText(); + return runProgramText(text, { + ...metadata, + source: "editor", + sourceLabel: "Editor text", + }); + } + try { const state = await runSelectedProgram(DEFAULT_SIMULATION_PROGRAM_ID); window.linuxCncRealSimulationState = state; @@ -872,6 +972,9 @@ play: startPlayback, pause: stopPlayback, finishPlayback: () => renderPlayback(currentState()?.motion.length - 1 ?? 0), + getProgramText: getProgramEditorText, + setProgramText: setProgramEditorText, + runEditorProgramText, runProgramText, loadProgramFile, runProgramById: async (programId) => { @@ -887,6 +990,24 @@ document.querySelector("[data-canonical-output]").textContent = error?.stack ?? error?.message ?? String(error); }); }); + runEditorButton?.addEventListener("click", () => { + window.linuxCncRealSimulationApi.runEditorProgramText().catch((error) => { + document.body.dataset.simulationReady = "false"; + setAllText("[data-simulation-status]", "failed"); + document.querySelector("[data-canonical-output]").textContent = error?.stack ?? error?.message ?? String(error); + }); + }); + programEditor?.addEventListener("input", () => { + programEditorState = { + text: programEditor.value, + metadata: { + ...programEditorState.metadata, + source: "editor", + sourceLabel: "Editor text", + }, + }; + setAllText("[data-program-editor-status]", "Editor text"); + }); openProgramButton?.addEventListener("click", () => openProgramFile?.click()); openProgramFile?.addEventListener("change", () => { const file = openProgramFile.files?.[0]; diff --git a/wasm-port/tests/browser/real_simulation_page_smoke.html b/wasm-port/tests/browser/real_simulation_page_smoke.html index 9cec9fb..5ee1bac 100644 --- a/wasm-port/tests/browser/real_simulation_page_smoke.html +++ b/wasm-port/tests/browser/real_simulation_page_smoke.html @@ -78,6 +78,7 @@ "preview-dro", "active-codes", "gcode-pane", + "program-editor", "machine-state", "statusbar", ]) { @@ -133,6 +134,9 @@ if (!api?.runProgramText || !api?.loadProgramFile) { throw new Error("simulation API missing real G-code loading controls"); } + if (!api?.getProgramText || !api?.setProgramText || !api?.runEditorProgramText) { + throw new Error("simulation API missing editor text controls"); + } if (!api?.resetPlayback || !api?.stepPlayback || !api?.finishPlayback) { throw new Error("simulation API missing playback controls"); } @@ -143,6 +147,13 @@ if (!doc.querySelector("[data-open-program]") || !doc.querySelector("[data-open-program-file]")) { throw new Error("simulation page missing open program controls"); } + if (!doc.querySelector("[data-program-editor]") || !doc.querySelector("[data-run-editor-program]")) { + throw new Error("simulation page missing editable G-code controls"); + } + const initialEditor = api.getProgramText(); + if (!initialEditor.text.includes("G1 X1 Y0 Z0 F100")) { + throw new Error("simulation editor did not receive initial built-in program text"); + } doc.querySelector('[data-axis-tab="mdi"]')?.click(); if (!doc.querySelector('[data-axis-panel="manual"]')?.hidden || doc.querySelector('[data-axis-panel="mdi"]')?.hidden) { @@ -261,6 +272,9 @@ if (!doc.querySelector('[data-axis-shell="statusbar"]')?.textContent.includes("Operator pasted G-code")) { throw new Error("custom text program source did not render in statusbar"); } + if (!api.getProgramText().text.includes("G1 X2.5 Y0.5 Z0 F75")) { + throw new Error("custom text program did not sync into editable G-code pane"); + } const fileProgram = new frame.contentWindow.File( [customProgramText.replace("X2.5", "X3.25")], @@ -275,6 +289,35 @@ if (!fileState.resultText.includes("canon_event=STRAIGHT_FEED line=2 x=3.25 y=0.5 z=0")) { throw new Error("loaded file program did not execute changed G-code through LinuxCNC WASM"); } + if (!api.getProgramText().metadata.filename || !api.getProgramText().text.includes("G1 X3.25 Y0.5 Z0 F75")) { + throw new Error("loaded file program did not sync into editor state"); + } + + api.setProgramText( + [ + "G90 G17 G0 X0 Y0 Z0", + "G1 X4.75 Y2.25 Z0 F95", + "M2", + "", + ].join("\n"), + { + label: "edited-in-axis-pane.ngc", + source: "editor", + sourceLabel: "Editor text", + }, + ); + const editorState = api.getProgramText(); + if (!editorState.text.includes("X4.75") || editorState.metadata.label !== "edited-in-axis-pane.ngc") { + throw new Error("simulation editable G-code state did not accept setProgramText"); + } + const editorRunState = await api.runEditorProgramText(); + assertRenderedState(doc, editorRunState); + if (!editorRunState.resultText.includes("canon_event=STRAIGHT_FEED line=2 x=4.75 y=2.25 z=0")) { + throw new Error("editor G-code did not execute changed text through LinuxCNC WASM"); + } + if (!doc.querySelector("[data-program-editor]")?.value.includes("X4.75")) { + throw new Error("editor DOM did not retain edited G-code text"); + } status.textContent = "browser_real_simulation_page_smoke=ok"; } catch (error) {