继续实现仿真测试程序
结论:真实浏览器仿真页面新增多测试程序选择和运行能力,覆盖直线、Z 轴轮廓、增量、圆弧和 G81 钻孔,并接入 Node、browser 与 release gate 验证。
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 page MVP
|
- Latest completed batch: real browser simulation test program selector
|
||||||
- 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
|
||||||
@@ -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. |
|
| 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, runs a representative G-code program through LinuxCNC interpreter WASM, renders program lines, final axis readout, canonical event table, raw canonical output, and SVG toolpath preview. |
|
| 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, renders program lines, final axis readout, canonical event table, raw canonical output, and SVG toolpath preview. Current program set covers square contour, Z pocket contour, incremental loop, G2/G3 arcs, and G81 drilling. |
|
||||||
| 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. |
|
||||||
@@ -82,6 +82,9 @@ Current first priority:
|
|||||||
|
|
||||||
- Build a real UI/browser CNC simulation page as quickly as possible.
|
- Build a real UI/browser CNC simulation page as quickly as possible.
|
||||||
- First MVP exists at `wasm-port/runtime/ui/simulation/index.html`.
|
- First MVP exists at `wasm-port/runtime/ui/simulation/index.html`.
|
||||||
|
- The page now includes selectable test programs for square contour, Z pocket
|
||||||
|
contour, incremental loop, G2/G3 arcs, and G81 drilling, with browser smoke
|
||||||
|
coverage for each program.
|
||||||
- The page must be visible and operator-oriented, not another release dashboard.
|
- The page must be visible and operator-oriented, not another release dashboard.
|
||||||
- It should use OPFS/session persistence, staged G-code, and existing
|
- It should use OPFS/session persistence, staged G-code, and existing
|
||||||
LinuxCNC-backed WASM SDK execution.
|
LinuxCNC-backed WASM SDK execution.
|
||||||
|
|||||||
53
text15.txt
53
text15.txt
@@ -1630,3 +1630,56 @@ project_release_handoff_docs_node_smoke=ok
|
|||||||
- JS 只调用 LinuxCNC-backed WASM SDK,并把 LinuxCNC-produced canonical output 渲染成
|
- JS 只调用 LinuxCNC-backed WASM SDK,并把 LinuxCNC-produced canonical output 渲染成
|
||||||
program state、axis readout、motion table 和 SVG preview;
|
program state、axis readout、motion table 和 SVG preview;
|
||||||
- canonical output 中坐标字段的读取只用于 UI 渲染,不作为 CNC 解释或运动规划来源。
|
- canonical output 中坐标字段的读取只用于 UI 渲染,不作为 CNC 解释或运动规划来源。
|
||||||
|
|
||||||
|
七十九、2026-06-16 继续执行记录:real browser simulation 多测试程序
|
||||||
|
|
||||||
|
本批继续围绕真实 browser CNC simulation page 推进,把页面从单一内置程序扩展为可选择
|
||||||
|
多组 LinuxCNC-backed 测试程序的仿真入口。
|
||||||
|
|
||||||
|
本批新增实质能力:
|
||||||
|
|
||||||
|
- `runtime/ui/simulation/simulation-app.js` 新增 `SIMULATION_TEST_PROGRAMS`;
|
||||||
|
- 当前测试程序覆盖:
|
||||||
|
- square contour;
|
||||||
|
- pocket with Z moves;
|
||||||
|
- incremental loop;
|
||||||
|
- G2/G3 arc path;
|
||||||
|
- G81 drill pattern;
|
||||||
|
- `runtime/ui/simulation/index.html` 新增程序选择器和 Run 按钮;
|
||||||
|
- 页面 API 新增:
|
||||||
|
- `linuxCncRealSimulationApi.getPrograms()`;
|
||||||
|
- `linuxCncRealSimulationApi.runProgramById(programId)`;
|
||||||
|
- `linuxCncRealSimulationApi.getState()`;
|
||||||
|
- browser smoke 现在会逐个运行所有测试程序,验证每个程序都经由 LinuxCNC WASM 执行,
|
||||||
|
DOM program rows、motion rows、canonical output、toolpath polyline 和 motion types 与
|
||||||
|
LinuxCNC-produced output 保持一致;
|
||||||
|
- 增强 canonical motion 渲染解析:`ARC_FEED` 使用 LinuxCNC canonical arc endpoint 字段映射
|
||||||
|
到当前 active plane 的坐标轴,只作为 UI toolpath 渲染输入;
|
||||||
|
- 新增 UI Node smoke:
|
||||||
|
- `tests/ui/node/verify_real_simulation_programs.mjs`;
|
||||||
|
- `tests/ui/node/verify_real_simulation_programs.sh`;
|
||||||
|
- `tests/ui/node/verify_ui_node_smokes.sh` 接入该 smoke;
|
||||||
|
- README、real-browser priority docs、PROJECT_COMPLETION_TRACKER 同步记录多测试程序能力。
|
||||||
|
|
||||||
|
本批新增/更新可执行验证:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wasm-port/tests/ui/node/verify_real_simulation_programs.sh
|
||||||
|
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
|
||||||
|
SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
关键通过输出:
|
||||||
|
|
||||||
|
```text
|
||||||
|
real_simulation_programs_node_smoke=ok
|
||||||
|
ui_node_smokes=ok
|
||||||
|
browser_real_simulation_page_smoke=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
本批仍保持 LinuxCNC 语义边界:
|
||||||
|
|
||||||
|
- 新增测试程序均通过 `createLinuxCncInterpSdk()` / `interp.runProgram()` 执行;
|
||||||
|
- JS 没有解释 G-code,也没有实现 planner、kinematics、tool、parameter、remap 语义;
|
||||||
|
- JS 只保存测试程序文本、调用 LinuxCNC WASM、解析 LinuxCNC-produced canonical output 以渲染
|
||||||
|
UI 状态和刀路终点。
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ LinuxCNC-backed interpreter execution, and browser smoke coverage. This does
|
|||||||
not relax the LinuxCNC semantic boundary; browser code still must not implement
|
not relax the LinuxCNC semantic boundary; browser code still must not implement
|
||||||
G-code, tool, parameter, kinematics, remap, or planner semantics.
|
G-code, tool, parameter, kinematics, remap, or planner semantics.
|
||||||
The first real simulation entry point is `runtime/ui/simulation/index.html`,
|
The first real simulation entry point is `runtime/ui/simulation/index.html`,
|
||||||
validated by `tests/browser/verify_real_simulation_browser.sh`.
|
validated by `tests/browser/verify_real_simulation_browser.sh`. It now exposes
|
||||||
|
multiple LinuxCNC-backed test programs from the page selector: square contour,
|
||||||
|
Z pocket contour, incremental loop, G2/G3 arc path, and G81 drill pattern.
|
||||||
|
|
||||||
The project release gate for the current scope is:
|
The project release gate for the current scope is:
|
||||||
|
|
||||||
@@ -46,6 +48,7 @@ opfs_session_docs_node_smoke=ok
|
|||||||
sim_configs_coverage_docs_node_smoke=ok
|
sim_configs_coverage_docs_node_smoke=ok
|
||||||
host_runtime_boundary_docs_node_smoke=ok
|
host_runtime_boundary_docs_node_smoke=ok
|
||||||
real_browser_simulation_priority_docs_node_smoke=ok
|
real_browser_simulation_priority_docs_node_smoke=ok
|
||||||
|
real_simulation_programs_node_smoke=ok
|
||||||
sdk_surface_node_smoke=ok
|
sdk_surface_node_smoke=ok
|
||||||
host_wasm_opfs_browser_smokes=ok
|
host_wasm_opfs_browser_smokes=ok
|
||||||
project_release_gate=ok
|
project_release_gate=ok
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ Its browser gate is:
|
|||||||
wasm-port/tests/browser/verify_real_simulation_browser.sh
|
wasm-port/tests/browser/verify_real_simulation_browser.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The current page includes a real LinuxCNC-backed test-program selector. The
|
||||||
|
browser gate runs each listed program through the WASM interpreter and verifies
|
||||||
|
that rendered program rows, canonical output, motion rows, toolpath points, and
|
||||||
|
declared motion types stay aligned with LinuxCNC-produced output. The first
|
||||||
|
program set covers linear contouring, Z moves, incremental mode, G2/G3 arcs,
|
||||||
|
and G81 drilling.
|
||||||
|
|
||||||
## 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:
|
||||||
@@ -73,6 +80,7 @@ The priority is enforced by:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
wasm-port/tests/docs/node/verify_real_browser_simulation_priority_docs.sh
|
wasm-port/tests/docs/node/verify_real_browser_simulation_priority_docs.sh
|
||||||
|
wasm-port/tests/ui/node/verify_real_simulation_programs.sh
|
||||||
wasm-port/tests/host/verify_host_smokes.sh
|
wasm-port/tests/host/verify_host_smokes.sh
|
||||||
wasm-port/tests/host/verify_project_release_gate.sh
|
wasm-port/tests/host/verify_project_release_gate.sh
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
padding: 14px 20px;
|
padding: 14px 20px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) minmax(240px, auto) auto;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@@ -63,6 +63,38 @@
|
|||||||
color: var(--green);
|
color: var(--green);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
.program-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: end;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
select, button {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fbfcfd;
|
||||||
|
color: var(--text);
|
||||||
|
font: 13px/1.2 "Segoe UI", sans-serif;
|
||||||
|
min-height: 32px;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
min-width: 220px;
|
||||||
|
max-width: 320px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-weight: 650;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
border-color: var(--blue);
|
||||||
|
}
|
||||||
|
button:disabled {
|
||||||
|
cursor: wait;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
.workspace {
|
.workspace {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(280px, 0.75fr) minmax(420px, 1.35fr) minmax(300px, 0.9fr);
|
grid-template-columns: minmax(280px, 0.75fr) minmax(420px, 1.35fr) minmax(300px, 0.9fr);
|
||||||
@@ -238,6 +270,8 @@
|
|||||||
}
|
}
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
header { grid-template-columns: 1fr; }
|
header { grid-template-columns: 1fr; }
|
||||||
|
.program-controls { justify-content: stretch; }
|
||||||
|
.program-controls select { min-width: 0; width: 100%; }
|
||||||
.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)); }
|
||||||
dl { grid-template-columns: 1fr; }
|
dl { grid-template-columns: 1fr; }
|
||||||
@@ -248,6 +282,10 @@
|
|||||||
<main>
|
<main>
|
||||||
<header>
|
<header>
|
||||||
<h1>LinuxCNC Browser Simulation</h1>
|
<h1>LinuxCNC Browser Simulation</h1>
|
||||||
|
<div class="program-controls">
|
||||||
|
<select data-program-selector aria-label="Simulation test program"></select>
|
||||||
|
<button type="button" data-run-program>Run</button>
|
||||||
|
</div>
|
||||||
<div class="status-strip">
|
<div class="status-strip">
|
||||||
<span class="badge">Simulation <strong data-simulation-status>loading</strong></span>
|
<span class="badge">Simulation <strong data-simulation-status>loading</strong></span>
|
||||||
<span class="badge" data-runtime-status>LinuxCNC interpreter WASM</span>
|
<span class="badge" data-runtime-status>LinuxCNC interpreter WASM</span>
|
||||||
@@ -257,7 +295,7 @@
|
|||||||
<section aria-label="G-code program">
|
<section aria-label="G-code program">
|
||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<h2>Program</h2>
|
<h2>Program</h2>
|
||||||
<p class="hint">Executed through the LinuxCNC-backed WASM interpreter.</p>
|
<p class="hint" data-selected-program>Executed through the LinuxCNC-backed WASM interpreter.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="program-lines" data-program-lines></div>
|
<div class="program-lines" data-program-lines></div>
|
||||||
</section>
|
</section>
|
||||||
@@ -319,14 +357,51 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { runRealBrowserSimulation } from "./simulation-app.js";
|
import {
|
||||||
|
DEFAULT_SIMULATION_PROGRAM_ID,
|
||||||
|
getSimulationTestPrograms,
|
||||||
|
runRealBrowserSimulation,
|
||||||
|
} from "./simulation-app.js";
|
||||||
|
|
||||||
|
const runButton = document.querySelector("[data-run-program]");
|
||||||
|
const programSelector = document.querySelector("[data-program-selector]");
|
||||||
|
|
||||||
|
async function runSelectedProgram(programId = programSelector?.value ?? DEFAULT_SIMULATION_PROGRAM_ID) {
|
||||||
|
document.body.dataset.simulationReady = "false";
|
||||||
|
document.querySelector("[data-simulation-status]").textContent = "running";
|
||||||
|
if (runButton) {
|
||||||
|
runButton.disabled = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const state = await runRealBrowserSimulation({ documentRef: document, programId });
|
||||||
|
window.linuxCncRealSimulationState = state;
|
||||||
|
return state;
|
||||||
|
} finally {
|
||||||
|
if (runButton) {
|
||||||
|
runButton.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const state = await runRealBrowserSimulation({ documentRef: document });
|
const state = await runSelectedProgram(DEFAULT_SIMULATION_PROGRAM_ID);
|
||||||
window.linuxCncRealSimulationState = state;
|
window.linuxCncRealSimulationState = state;
|
||||||
window.linuxCncRealSimulationApi = {
|
window.linuxCncRealSimulationApi = {
|
||||||
getState: () => window.linuxCncRealSimulationState,
|
getState: () => window.linuxCncRealSimulationState,
|
||||||
|
getPrograms: getSimulationTestPrograms,
|
||||||
|
runProgramById: async (programId) => {
|
||||||
|
const nextState = await runSelectedProgram(programId);
|
||||||
|
window.linuxCncRealSimulationState = nextState;
|
||||||
|
return nextState;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
runButton?.addEventListener("click", () => {
|
||||||
|
window.linuxCncRealSimulationApi.runProgramById(programSelector.value).catch((error) => {
|
||||||
|
document.body.dataset.simulationReady = "false";
|
||||||
|
document.querySelector("[data-simulation-status]").textContent = "failed";
|
||||||
|
document.querySelector("[data-canonical-output]").textContent = error?.stack ?? error?.message ?? String(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
document.body.dataset.simulationReady = "false";
|
document.body.dataset.simulationReady = "false";
|
||||||
document.querySelector("[data-simulation-status]").textContent = "failed";
|
document.querySelector("[data-simulation-status]").textContent = "failed";
|
||||||
|
|||||||
@@ -1,16 +1,94 @@
|
|||||||
import { createLinuxCncInterpSdk } from "../../sdk/src/index.js";
|
import { createLinuxCncInterpSdk } from "../../sdk/src/index.js";
|
||||||
|
|
||||||
export const DEFAULT_SIMULATION_PROGRAM = [
|
export const SIMULATION_TEST_PROGRAMS = [
|
||||||
"G0 X0 Y0 Z0",
|
{
|
||||||
|
id: "square-linear",
|
||||||
|
label: "Square contour",
|
||||||
|
category: "Linear",
|
||||||
|
text: [
|
||||||
|
"G90 G17 G0 X0 Y0 Z0",
|
||||||
"G1 X1 Y0 Z0 F100",
|
"G1 X1 Y0 Z0 F100",
|
||||||
"G1 X1 Y1 Z0",
|
"G1 X1 Y1 Z0",
|
||||||
"G1 X0 Y1 Z0",
|
"G1 X0 Y1 Z0",
|
||||||
"G1 X0 Y0 Z0",
|
"G1 X0 Y0 Z0",
|
||||||
"M2",
|
"M2",
|
||||||
"",
|
"",
|
||||||
].join("\n");
|
].join("\n"),
|
||||||
|
expectedMotionTypes: ["STRAIGHT_TRAVERSE", "STRAIGHT_FEED"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "pocket-z",
|
||||||
|
label: "Pocket with Z moves",
|
||||||
|
category: "Linear",
|
||||||
|
text: [
|
||||||
|
"G90 G17 G0 X0 Y0 Z1",
|
||||||
|
"G1 Z-0.25 F40",
|
||||||
|
"G1 X2 Y0 F120",
|
||||||
|
"G1 X2 Y1",
|
||||||
|
"G1 X0 Y1",
|
||||||
|
"G1 X0 Y0",
|
||||||
|
"G0 Z1",
|
||||||
|
"M2",
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
expectedMotionTypes: ["STRAIGHT_TRAVERSE", "STRAIGHT_FEED"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "incremental-loop",
|
||||||
|
label: "Incremental loop",
|
||||||
|
category: "Modal",
|
||||||
|
text: [
|
||||||
|
"G90 G17 G0 X0 Y0 Z0",
|
||||||
|
"G91 G1 X0.5 Y0 F60",
|
||||||
|
"G1 X0 Y0.5",
|
||||||
|
"G1 X-0.5 Y0",
|
||||||
|
"G90 G0 X0 Y0 Z0",
|
||||||
|
"M2",
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
expectedMotionTypes: ["STRAIGHT_TRAVERSE", "STRAIGHT_FEED"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "arc-g2-g3",
|
||||||
|
label: "G2/G3 arc path",
|
||||||
|
category: "Arc",
|
||||||
|
text: [
|
||||||
|
"G90 G17 G0 X0 Y0 Z0",
|
||||||
|
"G91.1",
|
||||||
|
"G2 X1 Y1 I1 J0 F80",
|
||||||
|
"G3 X0 Y0 I0 J-1",
|
||||||
|
"M2",
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
expectedMotionTypes: ["STRAIGHT_TRAVERSE", "ARC_FEED"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "drill-g81",
|
||||||
|
label: "G81 drill pattern",
|
||||||
|
category: "Cycle",
|
||||||
|
text: [
|
||||||
|
"G90 G17 G0 X0 Y0 Z1",
|
||||||
|
"G81 X0.5 Y0.5 Z-0.25 R0.1 F20",
|
||||||
|
"X1.0 Y0.5",
|
||||||
|
"X1.0 Y1.0",
|
||||||
|
"G80",
|
||||||
|
"G0 X0 Y0 Z1",
|
||||||
|
"M2",
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
expectedMotionTypes: ["STRAIGHT_TRAVERSE", "STRAIGHT_FEED"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const DEFAULT_SIMULATION_PROGRAM_ID = SIMULATION_TEST_PROGRAMS[0].id;
|
||||||
|
export const DEFAULT_SIMULATION_PROGRAM = SIMULATION_TEST_PROGRAMS[0].text;
|
||||||
|
|
||||||
const AXES = ["x", "y", "z", "a", "b", "c", "u", "v", "w"];
|
const AXES = ["x", "y", "z", "a", "b", "c", "u", "v", "w"];
|
||||||
|
const PLANE_AXIS_MAP = {
|
||||||
|
170: ["x", "y", "z"],
|
||||||
|
180: ["x", "z", "y"],
|
||||||
|
190: ["y", "z", "x"],
|
||||||
|
};
|
||||||
|
|
||||||
function readCanonicalNumber(line, name) {
|
function readCanonicalNumber(line, name) {
|
||||||
const match = line.match(new RegExp(`(?:^| )${name}=([-+0-9.eE]+)`));
|
const match = line.match(new RegExp(`(?:^| )${name}=([-+0-9.eE]+)`));
|
||||||
@@ -29,19 +107,41 @@ export function parseLinuxCncCanonicalMotion(resultText, programText = "") {
|
|||||||
const axes = Object.fromEntries(AXES.map((axis) => [axis, 0]));
|
const axes = Object.fromEntries(AXES.map((axis) => [axis, 0]));
|
||||||
const sourceLines = programLineMap(programText);
|
const sourceLines = programLineMap(programText);
|
||||||
const motion = [];
|
const motion = [];
|
||||||
|
let activePlane = 170;
|
||||||
|
|
||||||
for (const line of String(resultText).split("\n")) {
|
for (const line of String(resultText).split("\n")) {
|
||||||
|
const plane = readCanonicalNumber(line, "plane");
|
||||||
|
if (plane && PLANE_AXIS_MAP[plane]) {
|
||||||
|
activePlane = plane;
|
||||||
|
}
|
||||||
|
|
||||||
const event = line.match(/^canon_event=(STRAIGHT_TRAVERSE|STRAIGHT_FEED|ARC_FEED)\b/);
|
const event = line.match(/^canon_event=(STRAIGHT_TRAVERSE|STRAIGHT_FEED|ARC_FEED)\b/);
|
||||||
if (!event) {
|
if (!event) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event[1] === "ARC_FEED") {
|
||||||
|
const [firstAxis, secondAxis, thirdAxis] = PLANE_AXIS_MAP[activePlane] ?? PLANE_AXIS_MAP[170];
|
||||||
|
const firstEnd = readCanonicalNumber(line, "first_end");
|
||||||
|
const secondEnd = readCanonicalNumber(line, "second_end");
|
||||||
|
const axisEndPoint = readCanonicalNumber(line, "axis_end_point");
|
||||||
|
if (Number.isFinite(firstEnd)) {
|
||||||
|
axes[firstAxis] = firstEnd;
|
||||||
|
}
|
||||||
|
if (Number.isFinite(secondEnd)) {
|
||||||
|
axes[secondAxis] = secondEnd;
|
||||||
|
}
|
||||||
|
if (Number.isFinite(axisEndPoint)) {
|
||||||
|
axes[thirdAxis] = axisEndPoint;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
for (const axis of AXES) {
|
for (const axis of AXES) {
|
||||||
const value = readCanonicalNumber(line, axis);
|
const value = readCanonicalNumber(line, axis);
|
||||||
if (value !== null && Number.isFinite(value)) {
|
if (value !== null && Number.isFinite(value)) {
|
||||||
axes[axis] = value;
|
axes[axis] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const sourceLine = readCanonicalNumber(line, "line");
|
const sourceLine = readCanonicalNumber(line, "line");
|
||||||
motion.push({
|
motion.push({
|
||||||
@@ -59,6 +159,7 @@ export function parseLinuxCncCanonicalMotion(resultText, programText = "") {
|
|||||||
export function createSimulationSummary({ programText, resultText, motion }) {
|
export function createSimulationSummary({ programText, resultText, motion }) {
|
||||||
const canonicalLines = String(resultText).split("\n").filter((line) => line.startsWith("canon_event="));
|
const canonicalLines = String(resultText).split("\n").filter((line) => line.startsWith("canon_event="));
|
||||||
const finalAxes = motion.at(-1)?.axes ?? Object.fromEntries(AXES.map((axis) => [axis, 0]));
|
const finalAxes = motion.at(-1)?.axes ?? Object.fromEntries(AXES.map((axis) => [axis, 0]));
|
||||||
|
const motionTypes = [...new Set(motion.map(({ type }) => type))];
|
||||||
return {
|
return {
|
||||||
apiName: "real-browser-simulation-summary",
|
apiName: "real-browser-simulation-summary",
|
||||||
summaryVersion: 1,
|
summaryVersion: 1,
|
||||||
@@ -67,12 +168,14 @@ export function createSimulationSummary({ programText, resultText, motion }) {
|
|||||||
programLineCount: programText.split(/\r?\n/).filter(Boolean).length,
|
programLineCount: programText.split(/\r?\n/).filter(Boolean).length,
|
||||||
canonicalEventCount: canonicalLines.length,
|
canonicalEventCount: canonicalLines.length,
|
||||||
motionEventCount: motion.length,
|
motionEventCount: motion.length,
|
||||||
|
motionTypes,
|
||||||
finalAxes,
|
finalAxes,
|
||||||
rows: [
|
rows: [
|
||||||
{ id: "runtime", label: "Runtime", value: "LinuxCNC interpreter WASM" },
|
{ id: "runtime", label: "Runtime", value: "LinuxCNC interpreter WASM" },
|
||||||
{ id: "program-lines", label: "Program lines", value: `${programText.split(/\r?\n/).filter(Boolean).length}` },
|
{ id: "program-lines", label: "Program lines", value: `${programText.split(/\r?\n/).filter(Boolean).length}` },
|
||||||
{ id: "canonical-events", label: "Canonical events", value: `${canonicalLines.length}` },
|
{ id: "canonical-events", label: "Canonical events", value: `${canonicalLines.length}` },
|
||||||
{ id: "motion-events", label: "Motion events", value: `${motion.length}` },
|
{ id: "motion-events", label: "Motion events", value: `${motion.length}` },
|
||||||
|
{ id: "motion-types", label: "Motion types", value: motionTypes.join(", ") || "-" },
|
||||||
{ id: "final-position", label: "Final XYZ", value: formatPosition(finalAxes) },
|
{ id: "final-position", label: "Final XYZ", value: formatPosition(finalAxes) },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -133,6 +236,21 @@ function renderRows(documentRef, rows) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderProgramSelector(documentRef, programId) {
|
||||||
|
const selector = documentRef.querySelector("[data-program-selector]");
|
||||||
|
if (!selector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selector.textContent = "";
|
||||||
|
for (const program of SIMULATION_TEST_PROGRAMS) {
|
||||||
|
const option = documentRef.createElement("option");
|
||||||
|
option.value = program.id;
|
||||||
|
option.textContent = `${program.category}: ${program.label}`;
|
||||||
|
option.selected = program.id === programId;
|
||||||
|
selector.append(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderProgramLines(documentRef, programText, activeLine) {
|
function renderProgramLines(documentRef, programText, activeLine) {
|
||||||
const container = documentRef.querySelector("[data-program-lines]");
|
const container = documentRef.querySelector("[data-program-lines]");
|
||||||
if (!container) {
|
if (!container) {
|
||||||
@@ -205,11 +323,14 @@ function renderMotionTable(documentRef, motion) {
|
|||||||
|
|
||||||
export function renderSimulationState(documentRef, state) {
|
export function renderSimulationState(documentRef, state) {
|
||||||
documentRef.body.dataset.simulationReady = state.summary.ready ? "true" : "false";
|
documentRef.body.dataset.simulationReady = state.summary.ready ? "true" : "false";
|
||||||
|
documentRef.body.dataset.simulationProgramId = state.program?.id ?? "custom";
|
||||||
setText(documentRef, "[data-simulation-status]", state.summary.ready ? "ready" : "blocked");
|
setText(documentRef, "[data-simulation-status]", state.summary.ready ? "ready" : "blocked");
|
||||||
setText(documentRef, "[data-runtime-status]", state.summary.rows[0].value);
|
setText(documentRef, "[data-runtime-status]", state.summary.rows[0].value);
|
||||||
|
setText(documentRef, "[data-selected-program]", state.program?.label ?? "Custom program");
|
||||||
setText(documentRef, "[data-active-line]", `${state.motion.at(-1)?.line ?? "-"}`);
|
setText(documentRef, "[data-active-line]", `${state.motion.at(-1)?.line ?? "-"}`);
|
||||||
setText(documentRef, "[data-active-statement]", state.motion.at(-1)?.statement ?? "-");
|
setText(documentRef, "[data-active-statement]", state.motion.at(-1)?.statement ?? "-");
|
||||||
setText(documentRef, "[data-canonical-output]", state.resultText);
|
setText(documentRef, "[data-canonical-output]", state.resultText);
|
||||||
|
renderProgramSelector(documentRef, state.program?.id ?? "custom");
|
||||||
renderRows(documentRef, state.summary.rows);
|
renderRows(documentRef, state.summary.rows);
|
||||||
renderProgramLines(documentRef, state.programText, state.motion.at(-1)?.line ?? null);
|
renderProgramLines(documentRef, state.programText, state.motion.at(-1)?.line ?? null);
|
||||||
renderAxisReadout(documentRef, state.summary.finalAxes);
|
renderAxisReadout(documentRef, state.summary.finalAxes);
|
||||||
@@ -217,19 +338,35 @@ export function renderSimulationState(documentRef, state) {
|
|||||||
renderMotionTable(documentRef, state.motion);
|
renderMotionTable(documentRef, state.motion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSimulationTestPrograms() {
|
||||||
|
return SIMULATION_TEST_PROGRAMS.map((program) => ({ ...program }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSimulationTestProgram(programId = DEFAULT_SIMULATION_PROGRAM_ID) {
|
||||||
|
const program = SIMULATION_TEST_PROGRAMS.find(({ id }) => id === programId);
|
||||||
|
if (!program) {
|
||||||
|
throw new Error(`unknown simulation test program: ${programId}`);
|
||||||
|
}
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
export async function runRealBrowserSimulation({
|
export async function runRealBrowserSimulation({
|
||||||
documentRef = document,
|
documentRef = document,
|
||||||
programText = DEFAULT_SIMULATION_PROGRAM,
|
programId = DEFAULT_SIMULATION_PROGRAM_ID,
|
||||||
|
programText = null,
|
||||||
interpFactory = createLinuxCncInterpSdk,
|
interpFactory = createLinuxCncInterpSdk,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
|
const program = programText === null ? getSimulationTestProgram(programId) : null;
|
||||||
|
const resolvedProgramText = program?.text ?? programText ?? DEFAULT_SIMULATION_PROGRAM;
|
||||||
const interp = await interpFactory();
|
const interp = await interpFactory();
|
||||||
const resultText = interp.runProgram(programText);
|
const resultText = interp.runProgram(resolvedProgramText);
|
||||||
const motion = parseLinuxCncCanonicalMotion(resultText, programText);
|
const motion = parseLinuxCncCanonicalMotion(resultText, resolvedProgramText);
|
||||||
const summary = createSimulationSummary({ programText, resultText, motion });
|
const summary = createSimulationSummary({ programText: resolvedProgramText, resultText, motion });
|
||||||
const state = {
|
const state = {
|
||||||
apiName: "real-browser-simulation-state",
|
apiName: "real-browser-simulation-state",
|
||||||
stateVersion: 1,
|
stateVersion: 1,
|
||||||
programText,
|
program: program ? { ...program, text: undefined } : null,
|
||||||
|
programText: resolvedProgramText,
|
||||||
resultText,
|
resultText,
|
||||||
motion,
|
motion,
|
||||||
summary,
|
summary,
|
||||||
|
|||||||
@@ -33,44 +33,95 @@
|
|||||||
throw new Error("real simulation page did not expose ready state");
|
throw new Error("real simulation page did not expose ready state");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
function assertRenderedState(doc, state) {
|
||||||
const frame = await loadFrame("../../runtime/ui/simulation/index.html");
|
|
||||||
const doc = frame.contentDocument;
|
|
||||||
const state = await waitForState(frame);
|
|
||||||
const polyline = doc.querySelector("[data-toolpath-polyline]");
|
const polyline = doc.querySelector("[data-toolpath-polyline]");
|
||||||
const rows = [...doc.querySelectorAll("[data-motion-row]")];
|
const rows = [...doc.querySelectorAll("[data-motion-row]")];
|
||||||
const programRows = [...doc.querySelectorAll("[data-program-line]")];
|
const programRows = [...doc.querySelectorAll("[data-program-line]")];
|
||||||
const canonicalOutput = doc.querySelector("[data-canonical-output]")?.textContent ?? "";
|
const canonicalOutput = doc.querySelector("[data-canonical-output]")?.textContent ?? "";
|
||||||
|
|
||||||
if (doc.body.dataset.simulationReady !== "true") {
|
if (doc.body.dataset.simulationReady !== "true") {
|
||||||
throw new Error("simulation body readiness flag not true");
|
throw new Error(`simulation body readiness flag not true for ${state.program?.id}`);
|
||||||
}
|
}
|
||||||
if (state.apiName !== "real-browser-simulation-state") {
|
if (state.apiName !== "real-browser-simulation-state") {
|
||||||
throw new Error("simulation state API name drift");
|
throw new Error("simulation state API name drift");
|
||||||
}
|
}
|
||||||
if (state.summary.motionEventCount < 5) {
|
if (state.summary.motionEventCount < 2) {
|
||||||
throw new Error("simulation motion event count too low");
|
throw new Error(`simulation motion event count too low for ${state.program?.id}`);
|
||||||
}
|
}
|
||||||
|
if (!canonicalOutput.includes("canon_event=")) {
|
||||||
|
throw new Error(`simulation canonical output missing events for ${state.program?.id}`);
|
||||||
|
}
|
||||||
|
if (!polyline?.getAttribute("points")) {
|
||||||
|
throw new Error(`simulation toolpath polyline missing points for ${state.program?.id}`);
|
||||||
|
}
|
||||||
|
if (rows.length !== state.motion.length) {
|
||||||
|
throw new Error(`simulation motion table row count drift for ${state.program?.id}`);
|
||||||
|
}
|
||||||
|
if (programRows.length !== state.summary.programLineCount) {
|
||||||
|
throw new Error(`simulation program row count drift for ${state.program?.id}`);
|
||||||
|
}
|
||||||
|
if (!doc.querySelector("[data-toolpath-svg]")?.getAttribute("viewBox")) {
|
||||||
|
throw new Error(`simulation SVG viewBox missing for ${state.program?.id}`);
|
||||||
|
}
|
||||||
|
if (doc.body.dataset.simulationProgramId !== state.program?.id) {
|
||||||
|
throw new Error(`simulation body program id drift for ${state.program?.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const frame = await loadFrame("../../runtime/ui/simulation/index.html");
|
||||||
|
const doc = frame.contentDocument;
|
||||||
|
const state = await waitForState(frame);
|
||||||
|
const canonicalOutput = doc.querySelector("[data-canonical-output]")?.textContent ?? "";
|
||||||
|
const api = frame.contentWindow?.linuxCncRealSimulationApi;
|
||||||
|
|
||||||
|
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");
|
||||||
}
|
}
|
||||||
if (!polyline?.getAttribute("points")?.includes("1,0")) {
|
if (!doc.querySelector("[data-toolpath-polyline]")?.getAttribute("points")?.includes("1,0")) {
|
||||||
throw new Error("simulation toolpath polyline missing expected point");
|
throw new Error("simulation toolpath polyline missing expected point");
|
||||||
}
|
}
|
||||||
if (rows.length !== state.motion.length) {
|
|
||||||
throw new Error("simulation motion table row count drift");
|
|
||||||
}
|
|
||||||
if (programRows.length < 6) {
|
|
||||||
throw new Error("simulation program rows missing");
|
|
||||||
}
|
|
||||||
if (doc.querySelector('[data-axis="x"]')?.textContent !== "0.000") {
|
if (doc.querySelector('[data-axis="x"]')?.textContent !== "0.000") {
|
||||||
throw new Error("simulation final X axis drift");
|
throw new Error("simulation final X axis drift");
|
||||||
}
|
}
|
||||||
if (doc.querySelector('[data-axis="y"]')?.textContent !== "0.000") {
|
if (doc.querySelector('[data-axis="y"]')?.textContent !== "0.000") {
|
||||||
throw new Error("simulation final Y axis drift");
|
throw new Error("simulation final Y axis drift");
|
||||||
}
|
}
|
||||||
if (!doc.querySelector("[data-toolpath-svg]")?.getAttribute("viewBox")) {
|
|
||||||
throw new Error("simulation SVG viewBox missing");
|
if (!api?.getPrograms || !api?.runProgramById) {
|
||||||
|
throw new Error("simulation API missing program controls");
|
||||||
|
}
|
||||||
|
const programs = api.getPrograms();
|
||||||
|
if (programs.length < 5) {
|
||||||
|
throw new Error("simulation test program inventory too small");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const program of programs) {
|
||||||
|
const nextState = await api.runProgramById(program.id);
|
||||||
|
assertRenderedState(doc, nextState);
|
||||||
|
if (nextState.program.id !== program.id) {
|
||||||
|
throw new Error(`simulation API returned wrong program id for ${program.id}`);
|
||||||
|
}
|
||||||
|
for (const motionType of program.expectedMotionTypes) {
|
||||||
|
if (!nextState.summary.motionTypes.includes(motionType)) {
|
||||||
|
throw new Error(`simulation program ${program.id} missing motion type ${motionType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const arcState = await api.runProgramById("arc-g2-g3");
|
||||||
|
const arcPoints = doc.querySelector("[data-toolpath-polyline]")?.getAttribute("points") ?? "";
|
||||||
|
if (!arcState.summary.motionTypes.includes("ARC_FEED")) {
|
||||||
|
throw new Error("arc simulation did not produce ARC_FEED");
|
||||||
|
}
|
||||||
|
if (!arcPoints.includes("1,-1") || !arcPoints.includes("0,0")) {
|
||||||
|
throw new Error(`arc simulation toolpath points did not use canonical arc endpoints: ${arcPoints}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const drillState = await api.runProgramById("drill-g81");
|
||||||
|
if (drillState.motion.filter(({ type }) => type === "STRAIGHT_FEED").length < 3) {
|
||||||
|
throw new Error("drill simulation did not produce expected feed plunges");
|
||||||
}
|
}
|
||||||
|
|
||||||
status.textContent = "browser_real_simulation_page_smoke=ok";
|
status.textContent = "browser_real_simulation_page_smoke=ok";
|
||||||
|
|||||||
83
wasm-port/tests/ui/node/verify_real_simulation_programs.mjs
Normal file
83
wasm-port/tests/ui/node/verify_real_simulation_programs.mjs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DEFAULT_SIMULATION_PROGRAM,
|
||||||
|
DEFAULT_SIMULATION_PROGRAM_ID,
|
||||||
|
createSimulationSummary,
|
||||||
|
createToolpathPolylinePoints,
|
||||||
|
getSimulationTestProgram,
|
||||||
|
getSimulationTestPrograms,
|
||||||
|
parseLinuxCncCanonicalMotion,
|
||||||
|
} from "../../../runtime/ui/simulation/simulation-app.js";
|
||||||
|
|
||||||
|
const programs = getSimulationTestPrograms();
|
||||||
|
|
||||||
|
assert.equal(DEFAULT_SIMULATION_PROGRAM_ID, "square-linear");
|
||||||
|
assert.equal(getSimulationTestProgram(DEFAULT_SIMULATION_PROGRAM_ID).text, DEFAULT_SIMULATION_PROGRAM);
|
||||||
|
assert.ok(programs.length >= 5, "real simulation program inventory should cover multiple examples");
|
||||||
|
|
||||||
|
for (const program of programs) {
|
||||||
|
assert.ok(program.id, "program id should be present");
|
||||||
|
assert.ok(program.label, `program ${program.id} should have a label`);
|
||||||
|
assert.ok(program.category, `program ${program.id} should have a category`);
|
||||||
|
assert.ok(program.text.includes("M2"), `program ${program.id} should end through LinuxCNC program end`);
|
||||||
|
assert.ok(
|
||||||
|
program.expectedMotionTypes.length > 0,
|
||||||
|
`program ${program.id} should declare expected LinuxCNC motion types`,
|
||||||
|
);
|
||||||
|
assert.equal(getSimulationTestProgram(program.id).id, program.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ids = new Set(programs.map(({ id }) => id));
|
||||||
|
assert.equal(ids.size, programs.length, "program ids should be unique");
|
||||||
|
assert.ok(ids.has("arc-g2-g3"), "program inventory should include an arc fixture");
|
||||||
|
assert.ok(ids.has("drill-g81"), "program inventory should include a canned-cycle fixture");
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => getSimulationTestProgram("missing-program"),
|
||||||
|
/unknown simulation test program/,
|
||||||
|
"unknown program ids should be rejected",
|
||||||
|
);
|
||||||
|
|
||||||
|
const arcProgram = getSimulationTestProgram("arc-g2-g3");
|
||||||
|
const arcCanonical = [
|
||||||
|
"canon_event=UPDATE_TAG line=1 g0=-1 motion=0 plane=170 origin=530 feed=0 speed=0 flags=1048994",
|
||||||
|
"canon_event=STRAIGHT_TRAVERSE line=1 x=0 y=0 z=0 a=0 b=0 c=0 u=0 v=0 w=0",
|
||||||
|
"canon_event=ARC_FEED line=3 first_end=1 second_end=1 first_axis=1 second_axis=0 rotation=-1 axis_end_point=0 a=0 b=0 c=0 u=0 v=0 w=0",
|
||||||
|
"canon_event=ARC_FEED line=4 first_end=0 second_end=0 first_axis=1 second_axis=0 rotation=1 axis_end_point=0 a=0 b=0 c=0 u=0 v=0 w=0",
|
||||||
|
].join("\n");
|
||||||
|
const arcMotion = parseLinuxCncCanonicalMotion(arcCanonical, arcProgram.text);
|
||||||
|
|
||||||
|
assert.equal(arcMotion.length, 3);
|
||||||
|
assert.equal(arcMotion[1].type, "ARC_FEED");
|
||||||
|
assert.equal(arcMotion[1].statement, "G2 X1 Y1 I1 J0 F80");
|
||||||
|
assert.deepEqual(
|
||||||
|
{ x: arcMotion[1].axes.x, y: arcMotion[1].axes.y, z: arcMotion[1].axes.z },
|
||||||
|
{ x: 1, y: 1, z: 0 },
|
||||||
|
"arc parser should map LinuxCNC canonical arc endpoints onto active-plane axes",
|
||||||
|
);
|
||||||
|
assert.equal(createToolpathPolylinePoints(arcMotion), "0,0 1,-1 0,0");
|
||||||
|
|
||||||
|
const xzArcCanonical = [
|
||||||
|
"canon_event=UPDATE_TAG line=1 g0=-1 motion=0 plane=180 origin=530 feed=0 speed=0 flags=1048994",
|
||||||
|
"canon_event=STRAIGHT_TRAVERSE line=1 x=0 y=0 z=0 a=0 b=0 c=0 u=0 v=0 w=0",
|
||||||
|
"canon_event=ARC_FEED line=2 first_end=2 second_end=3 first_axis=1 second_axis=0 rotation=-1 axis_end_point=4 a=0 b=0 c=0 u=0 v=0 w=0",
|
||||||
|
].join("\n");
|
||||||
|
const xzArcMotion = parseLinuxCncCanonicalMotion(xzArcCanonical, "G18 G0 X0 Y0 Z0\nG2 X2 Z3 I1 K0\nM2\n");
|
||||||
|
assert.deepEqual(
|
||||||
|
{ x: xzArcMotion[1].axes.x, y: xzArcMotion[1].axes.y, z: xzArcMotion[1].axes.z },
|
||||||
|
{ x: 2, y: 4, z: 3 },
|
||||||
|
"G18 arc endpoints should use X/Z plus Y axis endpoint",
|
||||||
|
);
|
||||||
|
|
||||||
|
const summary = createSimulationSummary({
|
||||||
|
programText: arcProgram.text,
|
||||||
|
resultText: arcCanonical,
|
||||||
|
motion: arcMotion,
|
||||||
|
});
|
||||||
|
assert.equal(summary.ready, true);
|
||||||
|
assert.ok(summary.motionTypes.includes("ARC_FEED"));
|
||||||
|
assert.equal(summary.finalAxes.x, 0);
|
||||||
|
assert.equal(summary.finalAxes.y, 0);
|
||||||
|
|
||||||
|
console.log("real_simulation_programs_node_smoke=ok");
|
||||||
6
wasm-port/tests/ui/node/verify_real_simulation_programs.sh
Executable file
6
wasm-port/tests/ui/node/verify_real_simulation_programs.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||||
|
|
||||||
|
node "$ROOT_DIR/tests/ui/node/verify_real_simulation_programs.mjs"
|
||||||
@@ -19,5 +19,6 @@ ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
|||||||
"$ROOT_DIR/tests/ui/node/verify_ini_panel_ui_shell.sh"
|
"$ROOT_DIR/tests/ui/node/verify_ini_panel_ui_shell.sh"
|
||||||
"$ROOT_DIR/tests/ui/node/verify_ini_panel_shell_control_page_contract.sh"
|
"$ROOT_DIR/tests/ui/node/verify_ini_panel_shell_control_page_contract.sh"
|
||||||
"$ROOT_DIR/tests/ui/node/verify_ini_panel_entry_docs.sh"
|
"$ROOT_DIR/tests/ui/node/verify_ini_panel_entry_docs.sh"
|
||||||
|
"$ROOT_DIR/tests/ui/node/verify_real_simulation_programs.sh"
|
||||||
|
|
||||||
echo "ui_node_smokes=ok"
|
echo "ui_node_smokes=ok"
|
||||||
|
|||||||
Reference in New Issue
Block a user