实现AXIS风格仿真外壳
结论:真实浏览器仿真页面完成 AXIS-style Phase 1 外壳,新增菜单、工具栏、Manual/MDI、Preview/DRO、G-code pane、machine state 和状态栏,并保留 LinuxCNC WASM 执行与回放验证。
This commit is contained in:
@@ -9,7 +9,7 @@ turn-by-turn continuation notes and next-batch execution records.
|
|||||||
## Current Status
|
## Current Status
|
||||||
|
|
||||||
- Active continuation file: `text15.txt`
|
- Active continuation file: `text15.txt`
|
||||||
- Latest completed batch: real browser simulation test program selector
|
- Latest completed batch: AXIS-style browser simulation shell
|
||||||
- Latest relevant commit: use `git log -1 --oneline` after each committed batch
|
- Latest relevant commit: use `git log -1 --oneline` after each committed batch
|
||||||
- Working tree at tracker creation: clean
|
- Working tree at tracker creation: clean
|
||||||
- Required policy: LinuxCNC remains the only CNC semantic source
|
- Required policy: LinuxCNC remains the only CNC semantic source
|
||||||
@@ -41,7 +41,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. |
|
| 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 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 them through interpreter WASM, and now 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 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 them through interpreter WASM, and now 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 program set covers square contour, Z pocket contour, incremental loop, G2/G3 arcs, and G81 drilling. |
|
||||||
| AXIS-style simulation shell | Planned | Implementation plan exists at `wasm-port/docs/axis-style-simulation-implementation.md`; next batch should build the AXIS-inspired shell while preserving current LinuxCNC-backed execution and playback APIs. |
|
| 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. |
|
||||||
| Vendor/source guard | Stable | `verify_vendor_sync.sh` and standalone semantic guard are required every batch. |
|
| Vendor/source guard | Stable | `verify_vendor_sync.sh` and standalone semantic guard are required every batch. |
|
||||||
| Interpreter WASM smoke | Stable | `interp_wasm_node_smoke=ok`. |
|
| Interpreter WASM smoke | Stable | `interp_wasm_node_smoke=ok`. |
|
||||||
| Sim config inventory | Stable with skips | `executed=28`, `passed=28`, `skipped=131`, `unexpected_fail=0`; `docs/sim-configs-coverage-handoff.md` records the release-gate baseline. |
|
| Sim config inventory | Stable with skips | `executed=28`, `passed=28`, `skipped=131`, `unexpected_fail=0`; `docs/sim-configs-coverage-handoff.md` records the release-gate baseline. |
|
||||||
|
|||||||
60
text15.txt
60
text15.txt
@@ -1784,3 +1784,63 @@ real_browser_simulation_priority_docs_node_smoke=ok
|
|||||||
- Browser JS 只能负责 UI、OPFS/session、SDK/WASM 调用、LinuxCNC-produced output 渲染和播放;
|
- Browser JS 只能负责 UI、OPFS/session、SDK/WASM 调用、LinuxCNC-produced output 渲染和播放;
|
||||||
- 禁止在 JS 中实现 G-code、modal、tool compensation、parameter、kinematics、remap、planner 或
|
- 禁止在 JS 中实现 G-code、modal、tool compensation、parameter、kinematics、remap、planner 或
|
||||||
canonical motion 语义。
|
canonical motion 语义。
|
||||||
|
|
||||||
|
八十二、2026-06-16 继续执行记录:AXIS-style browser simulation shell Phase 1
|
||||||
|
|
||||||
|
本批按 `wasm-port/docs/axis-style-simulation-implementation.md` 的 Immediate Next Batch
|
||||||
|
实现 Phase 1,不移除当前 LinuxCNC-backed execution 和 playback API。
|
||||||
|
|
||||||
|
本批新增实质 UI:
|
||||||
|
|
||||||
|
- `runtime/ui/simulation/index.html` 从三列 MVP 改为 AXIS-inspired shell;
|
||||||
|
- 新增/调整区域:
|
||||||
|
- titlebar:`axis.ngc - AXIS 2.9.8 on LinuxCNC-BROWSER-SIM`;
|
||||||
|
- menubar:File / Machine / View / Help;
|
||||||
|
- toolbar:open/reload/run/playback/status/program selector;
|
||||||
|
- Manual Control / MDI tabs;
|
||||||
|
- Preview / DRO tabs;
|
||||||
|
- AXIS-like dark preview viewport;
|
||||||
|
- lower G-code pane;
|
||||||
|
- machine-state pane;
|
||||||
|
- bottom status bar:ESTOP / No tool / Position: Relative Actual;
|
||||||
|
- 保留并复用当前 data attributes:
|
||||||
|
- `data-program-selector`;
|
||||||
|
- `data-run-program`;
|
||||||
|
- playback buttons;
|
||||||
|
- `data-program-lines`;
|
||||||
|
- toolpath SVG / executed polyline / toolhead;
|
||||||
|
- motion rows;
|
||||||
|
- canonical output;
|
||||||
|
- `simulation-app.js` 的 `setText()` 改为同步所有匹配节点,支持 AXIS shell 中多个 DRO/状态显示;
|
||||||
|
- 页面脚本新增 Manual/MDI 和 Preview/DRO tab 切换;
|
||||||
|
- browser smoke 扩展验证:
|
||||||
|
- AXIS shell regions;
|
||||||
|
- File/Machine/View/Help menubar;
|
||||||
|
- Manual/MDI tab switching;
|
||||||
|
- Preview/DRO tab switching;
|
||||||
|
- statusbar;
|
||||||
|
- toolbar Step Forward 与 playback API 同步;
|
||||||
|
- 原有 LinuxCNC WASM program execution、多测试程序、回放、toolhead、executed path 仍通过。
|
||||||
|
|
||||||
|
本批新增/更新可执行验证:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh
|
||||||
|
wasm-port/tests/ui/node/verify_real_simulation_programs.sh
|
||||||
|
wasm-port/tests/docs/node/verify_axis_style_simulation_docs.sh
|
||||||
|
wasm-port/tests/docs/node/verify_real_browser_simulation_priority_docs.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
关键通过输出:
|
||||||
|
|
||||||
|
```text
|
||||||
|
browser_real_simulation_page_smoke=ok
|
||||||
|
real_simulation_programs_node_smoke=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
本批仍保持 LinuxCNC 语义边界:
|
||||||
|
|
||||||
|
- 新 shell 只是 UI layout 和 display state;
|
||||||
|
- Program execution 仍通过 `createLinuxCncInterpSdk()` / LinuxCNC WASM;
|
||||||
|
- Preview、DRO、G-code active line 和 playback 都来自 LinuxCNC-produced canonical motion event;
|
||||||
|
- 禁用的 manual/spindle/coolant/jog 控件只显示 blocked/readiness 状态,不伪造机器动作。
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ Z pocket contour, incremental loop, G2/G3 arc path, and G81 drill pattern. The
|
|||||||
page also includes playback controls that step or play through LinuxCNC
|
page also includes playback controls that step or play through LinuxCNC
|
||||||
canonical motion events, highlighting the active G-code line and moving the
|
canonical motion events, highlighting the active G-code line and moving the
|
||||||
toolhead over the executed toolpath.
|
toolhead over the executed toolpath.
|
||||||
The next UI implementation direction is the AXIS-style simulation shell in
|
The current UI direction is the AXIS-style simulation shell in
|
||||||
`docs/axis-style-simulation-implementation.md`.
|
`docs/axis-style-simulation-implementation.md`; Phase 1 is implemented in
|
||||||
|
`runtime/ui/simulation/index.html`.
|
||||||
|
|
||||||
The project release gate for the current scope is:
|
The project release gate for the current scope is:
|
||||||
|
|
||||||
|
|||||||
@@ -84,13 +84,16 @@ menu/toolbar/status badges
|
|||||||
└───────────────────────────────────────────────────────────────┘
|
└───────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
This should replace the current three-column MVP only after the new layout has
|
The Phase 1 implementation has replaced the former three-column MVP with this
|
||||||
equivalent smoke coverage.
|
AXIS-style shell while preserving the existing LinuxCNC-backed execution and
|
||||||
|
playback APIs.
|
||||||
|
|
||||||
## Implementation Phases
|
## Implementation Phases
|
||||||
|
|
||||||
### Phase 1: AXIS-Style Shell
|
### Phase 1: AXIS-Style Shell
|
||||||
|
|
||||||
|
Status: implemented.
|
||||||
|
|
||||||
Build the visual shell around the existing simulation state:
|
Build the visual shell around the existing simulation state:
|
||||||
|
|
||||||
- menu bar: File, Machine, View, Help;
|
- menu bar: File, Machine, View, Help;
|
||||||
@@ -109,6 +112,17 @@ Validation:
|
|||||||
- current program selection and playback still work;
|
- current program selection and playback still work;
|
||||||
- no existing simulation API is removed.
|
- no existing simulation API is removed.
|
||||||
|
|
||||||
|
Current implemented regions:
|
||||||
|
|
||||||
|
- `data-axis-shell="titlebar"`;
|
||||||
|
- `data-axis-shell="menubar"`;
|
||||||
|
- `data-axis-shell="toolbar"`;
|
||||||
|
- `data-axis-shell="manual-mdi"`;
|
||||||
|
- `data-axis-shell="preview-dro"`;
|
||||||
|
- `data-axis-shell="gcode-pane"`;
|
||||||
|
- `data-axis-shell="machine-state"`;
|
||||||
|
- `data-axis-shell="statusbar"`.
|
||||||
|
|
||||||
### Phase 2: Program Execution Workflow
|
### Phase 2: Program Execution Workflow
|
||||||
|
|
||||||
Upgrade the G-code pane and execution controls:
|
Upgrade the G-code pane and execution controls:
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ The AXIS-style simulation UI implementation plan is maintained in:
|
|||||||
docs/axis-style-simulation-implementation.md
|
docs/axis-style-simulation-implementation.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Phase 1 of that plan is implemented in `runtime/ui/simulation/index.html` with
|
||||||
|
an AXIS-inspired shell and browser smoke coverage for shell regions, tab
|
||||||
|
switching, toolbar playback, and LinuxCNC-backed program execution.
|
||||||
|
|
||||||
## Batch Priority
|
## Batch Priority
|
||||||
|
|
||||||
Unless a user explicitly changes priority, choose work in this order:
|
Unless a user explicitly changes priority, choose work in this order:
|
||||||
|
|||||||
@@ -7,55 +7,100 @@
|
|||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
--page: #eef2f5;
|
--page: #bfc5c9;
|
||||||
--surface: #ffffff;
|
--surface: #d7d7d7;
|
||||||
--line: #c8d3dc;
|
--panel: #cfcfcf;
|
||||||
--text: #1a252f;
|
--line: #8f969b;
|
||||||
--muted: #596875;
|
--line-light: #f4f4f4;
|
||||||
|
--text: #111820;
|
||||||
|
--muted: #4d565d;
|
||||||
--blue: #1263a3;
|
--blue: #1263a3;
|
||||||
--green: #188160;
|
--green: #16714f;
|
||||||
--steel: #6f7f8d;
|
--steel: #68747c;
|
||||||
--warn: #a9561b;
|
--warn: #a6491c;
|
||||||
|
--black: #030405;
|
||||||
}
|
}
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
font-family: "Segoe UI", "Noto Sans", sans-serif;
|
font-family: "Segoe UI", "Noto Sans", Arial, sans-serif;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
background: var(--page);
|
background: var(--page);
|
||||||
}
|
}
|
||||||
main {
|
main {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto minmax(0, 1fr);
|
grid-template-rows: auto auto minmax(0, 1fr) 164px auto;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
header {
|
.axis-titlebar {
|
||||||
|
border-bottom: 1px solid #777;
|
||||||
|
background: #d9d9d9;
|
||||||
|
padding: 4px 8px;
|
||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
background: var(--surface);
|
|
||||||
padding: 14px 20px;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(240px, auto) auto;
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
gap: 16px;
|
gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.axis-mark {
|
||||||
|
border: 1px solid #444;
|
||||||
|
background: #f6f6f6;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 18px;
|
font-size: 13px;
|
||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.window-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
color: #1b2229;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.axis-menubar {
|
||||||
|
display: flex;
|
||||||
|
gap: 18px;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
background: #e4e4e4;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.axis-menubar span {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
.axis-toolbar {
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
background: #d2d2d2;
|
||||||
|
padding: 4px 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.status-strip {
|
.status-strip {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: end;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
.badge {
|
.badge {
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 6px;
|
border-radius: 0;
|
||||||
padding: 5px 8px;
|
padding: 4px 7px;
|
||||||
background: #f8fafb;
|
background: #ededed;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font: 12px/1.2 "Segoe UI", sans-serif;
|
font: 12px/1.2 "Segoe UI", sans-serif;
|
||||||
}
|
}
|
||||||
@@ -67,16 +112,15 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: end;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
select, button {
|
select, button {
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 6px;
|
border-radius: 0;
|
||||||
background: #fbfcfd;
|
background: #eeeeee;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font: 13px/1.2 "Segoe UI", sans-serif;
|
font: 12px/1.2 "Segoe UI", sans-serif;
|
||||||
min-height: 32px;
|
min-height: 26px;
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
min-width: 220px;
|
min-width: 220px;
|
||||||
@@ -84,7 +128,7 @@
|
|||||||
padding: 5px 8px;
|
padding: 5px 8px;
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
padding: 6px 12px;
|
padding: 4px 8px;
|
||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -96,9 +140,6 @@
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
.playback-controls {
|
.playback-controls {
|
||||||
border-bottom: 1px solid var(--line);
|
|
||||||
background: #fbfcfd;
|
|
||||||
padding: 10px 20px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -112,29 +153,31 @@
|
|||||||
font: 12px/1.3 "SFMono-Regular", "Consolas", monospace;
|
font: 12px/1.3 "SFMono-Regular", "Consolas", monospace;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.workspace {
|
.axis-workspace {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(280px, 0.75fr) minmax(420px, 1.35fr) minmax(300px, 0.9fr);
|
grid-template-columns: 326px minmax(460px, 1fr);
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px;
|
||||||
|
background: var(--surface);
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
border-right: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
background: var(--surface);
|
background: var(--panel);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto minmax(0, 1fr);
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
section:last-child { border-right: 0; }
|
|
||||||
.section-head {
|
.section-head {
|
||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
padding: 12px 14px;
|
padding: 6px 8px;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
}
|
}
|
||||||
.hint {
|
.hint {
|
||||||
@@ -145,8 +188,8 @@
|
|||||||
}
|
}
|
||||||
.program-lines {
|
.program-lines {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 10px 0;
|
padding: 5px 0;
|
||||||
background: #fbfcfd;
|
background: #f8f8f8;
|
||||||
font: 13px/1.45 "SFMono-Regular", "Consolas", monospace;
|
font: 13px/1.45 "SFMono-Regular", "Consolas", monospace;
|
||||||
}
|
}
|
||||||
.program-lines div {
|
.program-lines div {
|
||||||
@@ -157,7 +200,7 @@
|
|||||||
border-left: 3px solid transparent;
|
border-left: 3px solid transparent;
|
||||||
}
|
}
|
||||||
.program-lines div[data-active="true"] {
|
.program-lines div[data-active="true"] {
|
||||||
background: #e9f4ef;
|
background: #d9f1df;
|
||||||
border-left-color: var(--green);
|
border-left-color: var(--green);
|
||||||
}
|
}
|
||||||
.program-lines span {
|
.program-lines span {
|
||||||
@@ -171,51 +214,51 @@
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
.preview {
|
.preview {
|
||||||
background: #f8fafb;
|
background: var(--panel);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: minmax(0, 1fr) auto;
|
grid-template-rows: minmax(0, 1fr) auto;
|
||||||
}
|
}
|
||||||
.toolpath-wrap {
|
.toolpath-wrap {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
padding: 18px;
|
padding: 4px;
|
||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
svg {
|
svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 320px;
|
min-height: 390px;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
background: #ffffff;
|
background: var(--black);
|
||||||
}
|
}
|
||||||
.grid-line {
|
.grid-line {
|
||||||
stroke: #edf1f4;
|
stroke: #7f1e1e;
|
||||||
stroke-width: 0.01;
|
stroke-width: 0.018;
|
||||||
}
|
}
|
||||||
.toolpath {
|
.toolpath {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: #b7c6d2;
|
stroke: #7cc4d5;
|
||||||
stroke-width: 0.028;
|
stroke-width: 0.018;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
stroke-linejoin: round;
|
stroke-linejoin: round;
|
||||||
}
|
}
|
||||||
.toolpath-executed {
|
.toolpath-executed {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: var(--blue);
|
stroke: #38d261;
|
||||||
stroke-width: 0.045;
|
stroke-width: 0.045;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
stroke-linejoin: round;
|
stroke-linejoin: round;
|
||||||
}
|
}
|
||||||
.toolhead {
|
.toolhead {
|
||||||
fill: var(--warn);
|
fill: #f1f1f1;
|
||||||
stroke: #ffffff;
|
stroke: #ffffff;
|
||||||
stroke-width: 0.025;
|
stroke-width: 0.025;
|
||||||
}
|
}
|
||||||
.readout {
|
.readout {
|
||||||
border-top: 1px solid var(--line);
|
border-top: 1px solid var(--line);
|
||||||
padding: 12px 14px;
|
padding: 8px;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
background: var(--surface);
|
background: var(--panel);
|
||||||
}
|
}
|
||||||
.axis-grid {
|
.axis-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -224,11 +267,11 @@
|
|||||||
}
|
}
|
||||||
.axis-grid div {
|
.axis-grid div {
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 6px;
|
border-radius: 0;
|
||||||
padding: 8px;
|
padding: 6px;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
background: #fbfcfd;
|
background: #ededed;
|
||||||
}
|
}
|
||||||
.axis-grid span:first-child {
|
.axis-grid span:first-child {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -254,6 +297,8 @@
|
|||||||
.motion-table-wrap {
|
.motion-table-wrap {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: minmax(0, 1fr) auto 132px;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -262,14 +307,14 @@
|
|||||||
}
|
}
|
||||||
th, td {
|
th, td {
|
||||||
border-bottom: 1px solid #edf1f4;
|
border-bottom: 1px solid #edf1f4;
|
||||||
padding: 7px 8px;
|
padding: 5px 6px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
background: #f8fafb;
|
background: #ededed;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@@ -288,49 +333,204 @@
|
|||||||
pre {
|
pre {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 12px;
|
padding: 8px;
|
||||||
background: #111820;
|
background: #111820;
|
||||||
color: #dbe7ef;
|
color: #dbe7ef;
|
||||||
font: 11px/1.45 "SFMono-Regular", "Consolas", monospace;
|
font: 11px/1.45 "SFMono-Regular", "Consolas", monospace;
|
||||||
max-height: 170px;
|
max-height: 132px;
|
||||||
|
}
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 3px 3px 0;
|
||||||
|
background: #c9c9c9;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
.tab {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-bottom: 0;
|
||||||
|
background: #e5e5e5;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
.tab[aria-selected="true"] {
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
.axis-left,
|
||||||
|
.axis-right {
|
||||||
|
display: grid;
|
||||||
|
min-height: 0;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
.manual-panel,
|
||||||
|
.mdi-panel,
|
||||||
|
.dro-panel {
|
||||||
|
padding: 8px;
|
||||||
|
display: grid;
|
||||||
|
gap: 9px;
|
||||||
|
align-content: start;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.manual-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.disabled-control {
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
.jog-buttons button,
|
||||||
|
.toolbar-button {
|
||||||
|
min-width: 28px;
|
||||||
|
min-height: 26px;
|
||||||
|
padding: 3px 7px;
|
||||||
|
}
|
||||||
|
.override-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 120px minmax(80px, 1fr);
|
||||||
|
gap: 6px 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.override-bar {
|
||||||
|
height: 16px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: linear-gradient(90deg, #aeb5bb 0 72%, #e8e8e8 72%);
|
||||||
|
}
|
||||||
|
.active-codes {
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
padding-top: 7px;
|
||||||
|
font: 12px/1.45 "SFMono-Regular", "Consolas", monospace;
|
||||||
|
}
|
||||||
|
.mdi-box {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: #eeeeee;
|
||||||
|
padding: 8px;
|
||||||
|
min-height: 86px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.right-panel {
|
||||||
|
display: grid;
|
||||||
|
min-height: 0;
|
||||||
|
grid-template-rows: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
.preview-panel {
|
||||||
|
min-height: 0;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
.axis-gcode-pane {
|
||||||
|
margin: 0 6px;
|
||||||
|
min-height: 0;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: #f8f8f8;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
.axis-gcode-head {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
background: #e5e5e5;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
.axis-statusbar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 120px minmax(120px, 1fr) minmax(180px, auto);
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
background: #e3e3e3;
|
||||||
|
min-height: 25px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.axis-statusbar span {
|
||||||
|
border-right: 1px solid var(--line);
|
||||||
|
padding: 4px 6px;
|
||||||
|
}
|
||||||
|
.axis-statusbar span:last-child {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
.dro-panel {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.dro-readout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(160px, 1fr));
|
||||||
|
gap: 8px 18px;
|
||||||
|
font: 14px/1.45 "SFMono-Regular", "Consolas", monospace;
|
||||||
|
}
|
||||||
|
.dro-readout div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 58px 74px 58px 74px;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
.dro-label {
|
||||||
|
color: #ffffff;
|
||||||
|
background: #111820;
|
||||||
|
padding: 1px 4px;
|
||||||
|
}
|
||||||
|
.dro-value {
|
||||||
|
color: #ffffff;
|
||||||
|
background: #111820;
|
||||||
|
padding: 1px 4px;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
@media (max-width: 1040px) {
|
@media (max-width: 1040px) {
|
||||||
.workspace { grid-template-columns: 1fr; }
|
main { grid-template-rows: auto auto auto minmax(0, 1fr) 220px auto; }
|
||||||
section { border-right: 0; border-bottom: 1px solid var(--line); }
|
.axis-workspace { grid-template-columns: 1fr; }
|
||||||
svg { min-height: 280px; }
|
svg { min-height: 280px; }
|
||||||
}
|
}
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
header { grid-template-columns: 1fr; }
|
.axis-titlebar { grid-template-columns: auto minmax(0, 1fr); }
|
||||||
|
.window-controls { display: none; }
|
||||||
|
.axis-toolbar { align-items: stretch; }
|
||||||
.program-controls { justify-content: stretch; }
|
.program-controls { justify-content: stretch; }
|
||||||
.program-controls select { min-width: 0; width: 100%; }
|
.program-controls select { min-width: 0; width: 100%; }
|
||||||
.playback-controls button { flex: 1 1 calc(50% - 8px); }
|
.playback-controls button { flex: 1 1 calc(50% - 8px); }
|
||||||
.playback-controls .spacer { display: none; }
|
.playback-controls .spacer { display: none; }
|
||||||
.status-strip { justify-content: start; }
|
.status-strip { justify-content: start; }
|
||||||
.axis-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
.axis-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
.dro-readout { grid-template-columns: 1fr; }
|
||||||
|
.axis-statusbar { grid-template-columns: 1fr; }
|
||||||
dl { grid-template-columns: 1fr; }
|
dl { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body data-simulation-ready="false">
|
<body data-simulation-ready="false">
|
||||||
<main>
|
<main>
|
||||||
<header>
|
<header class="axis-titlebar" data-axis-shell="titlebar">
|
||||||
<h1>LinuxCNC Browser Simulation</h1>
|
<div class="axis-mark">X</div>
|
||||||
|
<h1>axis.ngc - AXIS 2.9.8 on LinuxCNC-BROWSER-SIM</h1>
|
||||||
|
<div class="window-controls" aria-hidden="true"><span>⌃</span><span>−</span><span>□</span><span>×</span></div>
|
||||||
|
</header>
|
||||||
|
<nav class="axis-menubar" data-axis-shell="menubar" aria-label="AXIS menu">
|
||||||
|
<span>File</span>
|
||||||
|
<span>Machine</span>
|
||||||
|
<span>View</span>
|
||||||
|
<span>Help</span>
|
||||||
|
</nav>
|
||||||
|
<div class="axis-toolbar" data-axis-shell="toolbar" aria-label="AXIS toolbar">
|
||||||
|
<button type="button" class="toolbar-button" aria-label="Emergency stop">■</button>
|
||||||
|
<button type="button" class="toolbar-button" aria-label="Open program">▣</button>
|
||||||
|
<button type="button" class="toolbar-button" aria-label="Reload program">↻</button>
|
||||||
<div class="program-controls">
|
<div class="program-controls">
|
||||||
<select data-program-selector aria-label="Simulation test program"></select>
|
<select data-program-selector aria-label="Simulation test program"></select>
|
||||||
<button type="button" data-run-program>Run</button>
|
<button type="button" data-run-program>Run</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-strip">
|
<div class="playback-controls" aria-label="Simulation playback controls">
|
||||||
<span class="badge">Simulation <strong data-simulation-status>loading</strong></span>
|
<button type="button" class="toolbar-button" data-playback-reset>Reset</button>
|
||||||
<span class="badge" data-runtime-status>LinuxCNC interpreter WASM</span>
|
<button type="button" class="toolbar-button" data-playback-prev>Step Back</button>
|
||||||
|
<button type="button" class="toolbar-button" data-playback-play>Play</button>
|
||||||
|
<button type="button" class="toolbar-button" data-playback-next>Step Forward</button>
|
||||||
|
<button type="button" class="toolbar-button" data-playback-finish>Finish</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
|
||||||
<div class="playback-controls" aria-label="Simulation playback controls">
|
|
||||||
<button type="button" data-playback-reset>Reset</button>
|
|
||||||
<button type="button" data-playback-prev>Step Back</button>
|
|
||||||
<button type="button" data-playback-play>Play</button>
|
|
||||||
<button type="button" data-playback-next>Step Forward</button>
|
|
||||||
<button type="button" data-playback-finish>Finish</button>
|
|
||||||
<div class="spacer"></div>
|
|
||||||
<label class="playback-readout">
|
<label class="playback-readout">
|
||||||
Speed
|
Speed
|
||||||
<select data-playback-speed aria-label="Playback speed">
|
<select data-playback-speed aria-label="Playback speed">
|
||||||
@@ -341,48 +541,118 @@
|
|||||||
</label>
|
</label>
|
||||||
<span class="playback-readout">Step <span data-playback-step>0</span>/<span data-playback-total>0</span></span>
|
<span class="playback-readout">Step <span data-playback-step>0</span>/<span data-playback-total>0</span></span>
|
||||||
<span class="playback-readout" data-playback-progress>0%</span>
|
<span class="playback-readout" data-playback-progress>0%</span>
|
||||||
|
<div class="status-strip">
|
||||||
|
<span class="badge">Simulation <strong data-simulation-status>loading</strong></span>
|
||||||
|
<span class="badge" data-runtime-status>LinuxCNC interpreter WASM</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace">
|
<div class="axis-workspace" data-axis-shell="workspace">
|
||||||
<section aria-label="G-code program">
|
<aside class="axis-left" data-axis-shell="manual-mdi">
|
||||||
<div class="section-head">
|
<div class="tabs" role="tablist" aria-label="Machine control tabs">
|
||||||
<h2>Program</h2>
|
<button type="button" class="tab" role="tab" aria-selected="true" data-axis-tab="manual">Manual Control [F3]</button>
|
||||||
<p class="hint" data-selected-program>Executed through the LinuxCNC-backed WASM interpreter.</p>
|
<button type="button" class="tab" role="tab" aria-selected="false" data-axis-tab="mdi">MDI [F5]</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="program-lines" data-program-lines></div>
|
<div class="manual-panel" data-axis-panel="manual">
|
||||||
</section>
|
<div class="manual-row">Axis:
|
||||||
<section aria-label="Toolpath preview">
|
<label><input type="radio" checked disabled> X</label>
|
||||||
<div class="section-head">
|
<label><input type="radio" disabled> Y</label>
|
||||||
<h2>Toolpath Preview</h2>
|
<label><input type="radio" disabled> Z</label>
|
||||||
<p class="hint">Rendered from LinuxCNC canonical motion events.</p>
|
|
||||||
</div>
|
|
||||||
<div class="preview">
|
|
||||||
<div class="toolpath-wrap">
|
|
||||||
<svg data-toolpath-svg role="img" aria-label="LinuxCNC canonical toolpath preview" viewBox="-1 -1 2 2">
|
|
||||||
<line class="grid-line" x1="-10" y1="0" x2="10" y2="0"></line>
|
|
||||||
<line class="grid-line" x1="0" y1="-10" x2="0" y2="10"></line>
|
|
||||||
<polyline class="toolpath" data-toolpath-polyline points=""></polyline>
|
|
||||||
<polyline class="toolpath-executed" data-toolpath-executed-polyline points=""></polyline>
|
|
||||||
<circle class="toolhead" data-toolpath-head cx="0" cy="0" r="0.055"></circle>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="readout">
|
<div class="manual-row jog-buttons">
|
||||||
<div class="axis-grid">
|
<button type="button" disabled>-</button>
|
||||||
<div><span>X</span><span data-axis="x">0.000</span></div>
|
<button type="button" disabled>+</button>
|
||||||
<div><span>Y</span><span data-axis="y">0.000</span></div>
|
<select aria-label="Jog mode" disabled><option>Continuous</option></select>
|
||||||
<div><span>Z</span><span data-axis="z">0.000</span></div>
|
</div>
|
||||||
<div><span>A</span><span data-axis="a">0.000</span></div>
|
<div class="manual-row disabled-control">
|
||||||
<div><span>B</span><span data-axis="b">0.000</span></div>
|
<button type="button" disabled>Home All</button>
|
||||||
<div><span>C</span><span data-axis="c">0.000</span></div>
|
<button type="button" disabled>Touch Off</button>
|
||||||
|
</div>
|
||||||
|
<div class="manual-row disabled-control">
|
||||||
|
<button type="button" disabled>Tool Touch Off</button>
|
||||||
|
</div>
|
||||||
|
<div class="manual-row">Spindle:
|
||||||
|
<button type="button" disabled>↺</button>
|
||||||
|
<button type="button" disabled>Stop</button>
|
||||||
|
<button type="button" disabled>↻</button>
|
||||||
|
</div>
|
||||||
|
<div class="manual-row disabled-control">Coolant:
|
||||||
|
<label><input type="checkbox" disabled> Mist</label>
|
||||||
|
<label><input type="checkbox" disabled> Flood</label>
|
||||||
|
</div>
|
||||||
|
<div class="override-grid">
|
||||||
|
<span>Feed Override:</span><div class="override-bar" aria-label="Feed override 100 percent"></div>
|
||||||
|
<span>Rapid Override:</span><div class="override-bar" aria-label="Rapid override 100 percent"></div>
|
||||||
|
<span>Spindle Override:</span><div class="override-bar" aria-label="Spindle override 100 percent"></div>
|
||||||
|
<span>Jog Speed:</span><div>17 in/min</div>
|
||||||
|
<span>Max Velocity:</span><div>240 in/min</div>
|
||||||
|
</div>
|
||||||
|
<div class="active-codes" data-axis-shell="active-codes">
|
||||||
|
<strong>Active G-Codes:</strong><br>
|
||||||
|
G80 G17 G40 G20 G90 G94 G54 G49 G99 G64<br>
|
||||||
|
G97 G91.1 G8 M5 M9 M48 M53 M0 F0 S0
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mdi-panel" data-axis-panel="mdi" hidden>
|
||||||
|
<div class="mdi-box">MDI command entry will run only through LinuxCNC-backed WASM once connected.</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<section class="axis-right" data-axis-shell="preview-dro" aria-label="Preview and DRO">
|
||||||
|
<div class="tabs" role="tablist" aria-label="Preview tabs">
|
||||||
|
<button type="button" class="tab" role="tab" aria-selected="true" data-axis-tab="preview">Preview</button>
|
||||||
|
<button type="button" class="tab" role="tab" aria-selected="false" data-axis-tab="dro">DRO</button>
|
||||||
|
</div>
|
||||||
|
<div class="right-panel">
|
||||||
|
<div class="preview-panel" data-axis-panel="preview">
|
||||||
|
<div class="preview">
|
||||||
|
<div class="toolpath-wrap">
|
||||||
|
<svg data-toolpath-svg role="img" aria-label="LinuxCNC canonical toolpath preview" viewBox="-1 -1 2 2">
|
||||||
|
<line class="grid-line" x1="-10" y1="0" x2="10" y2="0"></line>
|
||||||
|
<line class="grid-line" x1="0" y1="-10" x2="0" y2="10"></line>
|
||||||
|
<polyline class="toolpath" data-toolpath-polyline points=""></polyline>
|
||||||
|
<polyline class="toolpath-executed" data-toolpath-executed-polyline points=""></polyline>
|
||||||
|
<circle class="toolhead" data-toolpath-head cx="0" cy="0" r="0.055"></circle>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="readout">
|
||||||
|
<div class="axis-grid">
|
||||||
|
<div><span>X</span><span data-axis="x">0.000</span></div>
|
||||||
|
<div><span>Y</span><span data-axis="y">0.000</span></div>
|
||||||
|
<div><span>Z</span><span data-axis="z">0.000</span></div>
|
||||||
|
<div><span>A</span><span data-axis="a">0.000</span></div>
|
||||||
|
<div><span>B</span><span data-axis="b">0.000</span></div>
|
||||||
|
<div><span>C</span><span data-axis="c">0.000</span></div>
|
||||||
|
</div>
|
||||||
|
<dl data-simulation-summary-rows></dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dro-panel" data-axis-panel="dro" hidden>
|
||||||
|
<div class="dro-readout" data-axis-shell="dro-readout">
|
||||||
|
<div><span class="dro-label">X:</span><span class="dro-value" data-axis="x">0.000</span><span class="dro-label">DTG X:</span><span class="dro-value">n/a</span></div>
|
||||||
|
<div><span class="dro-label">Y:</span><span class="dro-value" data-axis="y">0.000</span><span class="dro-label">DTG Y:</span><span class="dro-value">n/a</span></div>
|
||||||
|
<div><span class="dro-label">Z:</span><span class="dro-value" data-axis="z">0.000</span><span class="dro-label">DTG Z:</span><span class="dro-value">n/a</span></div>
|
||||||
|
<div><span class="dro-label">G54 X:</span><span class="dro-value">n/a</span><span class="dro-label">G92 X:</span><span class="dro-value">n/a</span></div>
|
||||||
|
<div><span class="dro-label">G54 Y:</span><span class="dro-value">n/a</span><span class="dro-label">G92 Y:</span><span class="dro-value">n/a</span></div>
|
||||||
|
<div><span class="dro-label">G54 Z:</span><span class="dro-value">n/a</span><span class="dro-label">G92 Z:</span><span class="dro-value">n/a</span></div>
|
||||||
|
<div><span class="dro-label">TLO X:</span><span class="dro-value">n/a</span><span class="dro-label">Vel:</span><span class="dro-value">n/a</span></div>
|
||||||
|
<div><span class="dro-label">TLO Y:</span><span class="dro-value">n/a</span><span class="dro-label">TLO Z:</span><span class="dro-value">n/a</span></div>
|
||||||
</div>
|
</div>
|
||||||
<dl data-simulation-summary-rows></dl>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section aria-label="Machine state">
|
</div>
|
||||||
<div class="section-head">
|
<section class="axis-gcode-pane" data-axis-shell="gcode-pane" aria-label="G-code program">
|
||||||
<h2>Machine State</h2>
|
<div class="axis-gcode-head">
|
||||||
<p class="hint">Active line, statement, motion rows, and canonical output.</p>
|
<span>Program: <span data-selected-program>Executed through the LinuxCNC-backed WASM interpreter.</span></span>
|
||||||
</div>
|
<span>Active line <span data-active-line>-</span></span>
|
||||||
|
</div>
|
||||||
|
<div class="program-lines" data-program-lines></div>
|
||||||
|
</section>
|
||||||
|
<section class="axis-gcode-pane" data-axis-shell="machine-state" aria-label="Machine state">
|
||||||
|
<div class="axis-gcode-head">
|
||||||
|
<span>Machine State</span>
|
||||||
|
<span>Statement <span data-active-statement>-</span></span>
|
||||||
|
</div>
|
||||||
<div class="motion-table-wrap">
|
<div class="motion-table-wrap">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -397,16 +667,20 @@
|
|||||||
</table>
|
</table>
|
||||||
<div class="readout">
|
<div class="readout">
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Active line</dt>
|
<dt>Run Status</dt>
|
||||||
<dd data-active-line>-</dd>
|
<dd data-simulation-status>loading</dd>
|
||||||
<dt>Statement</dt>
|
<dt>Statement</dt>
|
||||||
<dd data-active-statement>-</dd>
|
<dd data-active-statement>-</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<pre data-canonical-output></pre>
|
<pre data-canonical-output></pre>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
<footer class="axis-statusbar" data-axis-shell="statusbar">
|
||||||
|
<span>ESTOP</span>
|
||||||
|
<span>No tool</span>
|
||||||
|
<span>Position: Relative Actual</span>
|
||||||
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import {
|
import {
|
||||||
@@ -427,6 +701,28 @@
|
|||||||
let playbackTimer = null;
|
let playbackTimer = null;
|
||||||
let playbackIndex = 0;
|
let playbackIndex = 0;
|
||||||
|
|
||||||
|
function setAllText(selector, value) {
|
||||||
|
document.querySelectorAll(selector).forEach((node) => {
|
||||||
|
node.textContent = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mountTabGroup(tabSelector, panelSelector) {
|
||||||
|
const tabs = [...document.querySelectorAll(tabSelector)];
|
||||||
|
const panels = [...document.querySelectorAll(panelSelector)];
|
||||||
|
for (const tab of tabs) {
|
||||||
|
tab.addEventListener("click", () => {
|
||||||
|
const target = tab.dataset.axisTab;
|
||||||
|
for (const item of tabs) {
|
||||||
|
item.setAttribute("aria-selected", item === tab ? "true" : "false");
|
||||||
|
}
|
||||||
|
for (const panel of panels) {
|
||||||
|
panel.hidden = panel.dataset.axisPanel !== target;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function currentState() {
|
function currentState() {
|
||||||
return window.linuxCncRealSimulationState;
|
return window.linuxCncRealSimulationState;
|
||||||
}
|
}
|
||||||
@@ -495,7 +791,7 @@
|
|||||||
async function runSelectedProgram(programId = programSelector?.value ?? DEFAULT_SIMULATION_PROGRAM_ID) {
|
async function runSelectedProgram(programId = programSelector?.value ?? DEFAULT_SIMULATION_PROGRAM_ID) {
|
||||||
stopPlayback();
|
stopPlayback();
|
||||||
document.body.dataset.simulationReady = "false";
|
document.body.dataset.simulationReady = "false";
|
||||||
document.querySelector("[data-simulation-status]").textContent = "running";
|
setAllText("[data-simulation-status]", "running");
|
||||||
if (runButton) {
|
if (runButton) {
|
||||||
runButton.disabled = true;
|
runButton.disabled = true;
|
||||||
}
|
}
|
||||||
@@ -532,7 +828,7 @@
|
|||||||
runButton?.addEventListener("click", () => {
|
runButton?.addEventListener("click", () => {
|
||||||
window.linuxCncRealSimulationApi.runProgramById(programSelector.value).catch((error) => {
|
window.linuxCncRealSimulationApi.runProgramById(programSelector.value).catch((error) => {
|
||||||
document.body.dataset.simulationReady = "false";
|
document.body.dataset.simulationReady = "false";
|
||||||
document.querySelector("[data-simulation-status]").textContent = "failed";
|
setAllText("[data-simulation-status]", "failed");
|
||||||
document.querySelector("[data-canonical-output]").textContent = error?.stack ?? error?.message ?? String(error);
|
document.querySelector("[data-canonical-output]").textContent = error?.stack ?? error?.message ?? String(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -546,10 +842,12 @@
|
|||||||
startPlayback();
|
startPlayback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
mountTabGroup('[data-axis-tab="manual"], [data-axis-tab="mdi"]', '[data-axis-panel="manual"], [data-axis-panel="mdi"]');
|
||||||
|
mountTabGroup('[data-axis-tab="preview"], [data-axis-tab="dro"]', '[data-axis-panel="preview"], [data-axis-panel="dro"]');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
stopPlayback();
|
stopPlayback();
|
||||||
document.body.dataset.simulationReady = "false";
|
document.body.dataset.simulationReady = "false";
|
||||||
document.querySelector("[data-simulation-status]").textContent = "failed";
|
setAllText("[data-simulation-status]", "failed");
|
||||||
document.querySelector("[data-canonical-output]").textContent = error?.stack ?? error?.message ?? String(error);
|
document.querySelector("[data-canonical-output]").textContent = error?.stack ?? error?.message ?? String(error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,8 +244,7 @@ function formatAxis(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setText(documentRef, selector, value) {
|
function setText(documentRef, selector, value) {
|
||||||
const node = documentRef.querySelector(selector);
|
for (const node of documentRef.querySelectorAll(selector)) {
|
||||||
if (node) {
|
|
||||||
node.textContent = value;
|
node.textContent = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertAxisShell(doc) {
|
||||||
|
for (const region of [
|
||||||
|
"titlebar",
|
||||||
|
"menubar",
|
||||||
|
"toolbar",
|
||||||
|
"workspace",
|
||||||
|
"manual-mdi",
|
||||||
|
"preview-dro",
|
||||||
|
"active-codes",
|
||||||
|
"gcode-pane",
|
||||||
|
"machine-state",
|
||||||
|
"statusbar",
|
||||||
|
]) {
|
||||||
|
if (!doc.querySelector(`[data-axis-shell="${region}"]`)) {
|
||||||
|
throw new Error(`AXIS-style shell missing ${region}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const tab of ["manual", "mdi", "preview", "dro"]) {
|
||||||
|
if (!doc.querySelector(`[data-axis-tab="${tab}"]`)) {
|
||||||
|
throw new Error(`AXIS-style shell missing ${tab} tab`);
|
||||||
|
}
|
||||||
|
if (!doc.querySelector(`[data-axis-panel="${tab}"]`)) {
|
||||||
|
throw new Error(`AXIS-style shell missing ${tab} panel`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const menuText = doc.querySelector('[data-axis-shell="menubar"]')?.textContent ?? "";
|
||||||
|
for (const label of ["File", "Machine", "View", "Help"]) {
|
||||||
|
if (!menuText.includes(label)) {
|
||||||
|
throw new Error(`AXIS-style menu missing ${label}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const statusText = doc.querySelector('[data-axis-shell="statusbar"]')?.textContent ?? "";
|
||||||
|
if (!statusText.includes("ESTOP") || !statusText.includes("Position: Relative Actual")) {
|
||||||
|
throw new Error(`AXIS-style statusbar drift: ${statusText}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const frame = await loadFrame("../../runtime/ui/simulation/index.html");
|
const frame = await loadFrame("../../runtime/ui/simulation/index.html");
|
||||||
const doc = frame.contentDocument;
|
const doc = frame.contentDocument;
|
||||||
@@ -75,6 +112,7 @@
|
|||||||
const canonicalOutput = doc.querySelector("[data-canonical-output]")?.textContent ?? "";
|
const canonicalOutput = doc.querySelector("[data-canonical-output]")?.textContent ?? "";
|
||||||
const api = frame.contentWindow?.linuxCncRealSimulationApi;
|
const api = frame.contentWindow?.linuxCncRealSimulationApi;
|
||||||
|
|
||||||
|
assertAxisShell(doc);
|
||||||
assertRenderedState(doc, state);
|
assertRenderedState(doc, state);
|
||||||
if (!canonicalOutput.includes("canon_event=STRAIGHT_FEED line=2 x=1 y=0 z=0")) {
|
if (!canonicalOutput.includes("canon_event=STRAIGHT_FEED line=2 x=1 y=0 z=0")) {
|
||||||
throw new Error("simulation canonical output missing LinuxCNC feed event");
|
throw new Error("simulation canonical output missing LinuxCNC feed event");
|
||||||
@@ -100,6 +138,27 @@
|
|||||||
throw new Error("simulation test program inventory too small");
|
throw new Error("simulation test program inventory too small");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doc.querySelector('[data-axis-tab="mdi"]')?.click();
|
||||||
|
if (!doc.querySelector('[data-axis-panel="manual"]')?.hidden || doc.querySelector('[data-axis-panel="mdi"]')?.hidden) {
|
||||||
|
throw new Error("AXIS-style Manual/MDI tab switch failed");
|
||||||
|
}
|
||||||
|
doc.querySelector('[data-axis-tab="manual"]')?.click();
|
||||||
|
if (doc.querySelector('[data-axis-panel="manual"]')?.hidden || !doc.querySelector('[data-axis-panel="mdi"]')?.hidden) {
|
||||||
|
throw new Error("AXIS-style Manual tab restore failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.querySelector('[data-axis-tab="dro"]')?.click();
|
||||||
|
if (!doc.querySelector('[data-axis-panel="preview"]')?.hidden || doc.querySelector('[data-axis-panel="dro"]')?.hidden) {
|
||||||
|
throw new Error("AXIS-style Preview/DRO tab switch failed");
|
||||||
|
}
|
||||||
|
if (doc.querySelector('[data-axis-shell="dro-readout"]')?.textContent.includes("undefined")) {
|
||||||
|
throw new Error("AXIS-style DRO should not render undefined fields");
|
||||||
|
}
|
||||||
|
doc.querySelector('[data-axis-tab="preview"]')?.click();
|
||||||
|
if (doc.querySelector('[data-axis-panel="preview"]')?.hidden || !doc.querySelector('[data-axis-panel="dro"]')?.hidden) {
|
||||||
|
throw new Error("AXIS-style Preview tab restore failed");
|
||||||
|
}
|
||||||
|
|
||||||
api.resetPlayback();
|
api.resetPlayback();
|
||||||
if (doc.body.dataset.playbackIndex !== "0") {
|
if (doc.body.dataset.playbackIndex !== "0") {
|
||||||
throw new Error("simulation playback did not reset to first frame");
|
throw new Error("simulation playback did not reset to first frame");
|
||||||
@@ -130,6 +189,11 @@
|
|||||||
throw new Error("simulation step playback did not highlight active motion row");
|
throw new Error("simulation step playback did not highlight active motion row");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doc.querySelector("[data-playback-next]")?.click();
|
||||||
|
if (doc.body.dataset.playbackIndex !== "2") {
|
||||||
|
throw new Error("AXIS-style toolbar step button did not advance playback");
|
||||||
|
}
|
||||||
|
|
||||||
const finishedFrame = api.finishPlayback();
|
const finishedFrame = api.finishPlayback();
|
||||||
if (finishedFrame.index !== state.motion.length - 1 || doc.body.dataset.playbackComplete !== "true") {
|
if (finishedFrame.index !== state.motion.length - 1 || doc.body.dataset.playbackComplete !== "true") {
|
||||||
throw new Error("simulation finish playback did not reach final frame");
|
throw new Error("simulation finish playback did not reach final frame");
|
||||||
|
|||||||
Reference in New Issue
Block a user