新增仿真执行过程回放

结论:真实浏览器仿真页面新增 G-code 执行过程和刀具运动回放,支持 reset、step、play、finish,逐帧高亮 G-code 与 motion row,并渲染已执行刀路和移动 toolhead。
This commit is contained in:
2026-06-16 21:31:50 +08:00
parent ddd5b78999
commit 9e149107dc
8 changed files with 345 additions and 16 deletions

View File

@@ -92,14 +92,56 @@
if (!api?.getPrograms || !api?.runProgramById) {
throw new Error("simulation API missing program controls");
}
if (!api?.resetPlayback || !api?.stepPlayback || !api?.finishPlayback) {
throw new Error("simulation API missing playback controls");
}
const programs = api.getPrograms();
if (programs.length < 5) {
throw new Error("simulation test program inventory too small");
}
api.resetPlayback();
if (doc.body.dataset.playbackIndex !== "0") {
throw new Error("simulation playback did not reset to first frame");
}
const firstHead = doc.querySelector("[data-toolpath-head]");
const firstCx = firstHead?.getAttribute("cx");
const firstCy = firstHead?.getAttribute("cy");
const firstExecutedPoints = doc.querySelector("[data-toolpath-executed-polyline]")?.getAttribute("points") ?? "";
if (!doc.querySelector('[data-program-line="1"]') || doc.querySelector('[data-program-line="2"]')?.dataset.active !== "false") {
throw new Error("simulation reset playback did not render first active program line");
}
const secondFrame = api.stepPlayback(1);
if (secondFrame.index !== 1 || secondFrame.activeLine !== 2) {
throw new Error(`simulation step playback drift: ${JSON.stringify(secondFrame)}`);
}
const secondExecutedPoints = doc.querySelector("[data-toolpath-executed-polyline]")?.getAttribute("points") ?? "";
if (secondExecutedPoints === firstExecutedPoints || !secondExecutedPoints.includes("1,0")) {
throw new Error("simulation executed toolpath did not advance on step");
}
if (firstHead?.getAttribute("cx") === firstCx && firstHead?.getAttribute("cy") === firstCy) {
throw new Error("simulation toolhead did not move on playback step");
}
if (doc.querySelector('[data-program-line="2"]')?.dataset.active !== "true") {
throw new Error("simulation step playback did not highlight active G-code line");
}
if (doc.querySelector('[data-motion-row="1"]')?.dataset.active !== "true") {
throw new Error("simulation step playback did not highlight active motion row");
}
const finishedFrame = api.finishPlayback();
if (finishedFrame.index !== state.motion.length - 1 || doc.body.dataset.playbackComplete !== "true") {
throw new Error("simulation finish playback did not reach final frame");
}
for (const program of programs) {
const nextState = await api.runProgramById(program.id);
assertRenderedState(doc, nextState);
const resetFrame = api.resetPlayback();
if (resetFrame.index !== 0 || doc.body.dataset.playbackIndex !== "0") {
throw new Error(`simulation playback reset failed for ${program.id}`);
}
if (nextState.program.id !== program.id) {
throw new Error(`simulation API returned wrong program id for ${program.id}`);
}

View File

@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import {
DEFAULT_SIMULATION_PROGRAM,
DEFAULT_SIMULATION_PROGRAM_ID,
createPlaybackFrame,
createSimulationSummary,
createToolpathPolylinePoints,
getSimulationTestProgram,
@@ -80,4 +81,27 @@ assert.ok(summary.motionTypes.includes("ARC_FEED"));
assert.equal(summary.finalAxes.x, 0);
assert.equal(summary.finalAxes.y, 0);
const playbackState = {
motion: arcMotion,
};
const firstFrame = createPlaybackFrame(playbackState, 0);
assert.equal(firstFrame.apiName, "real-browser-simulation-playback-frame");
assert.equal(firstFrame.index, 0);
assert.equal(firstFrame.step, 1);
assert.equal(firstFrame.total, 3);
assert.equal(firstFrame.activeLine, 1);
assert.equal(firstFrame.visibleMotion.length, 1);
const arcFrame = createPlaybackFrame(playbackState, 1);
assert.equal(arcFrame.index, 1);
assert.equal(arcFrame.activeLine, 3);
assert.equal(arcFrame.activeStatement, "G2 X1 Y1 I1 J0 F80");
assert.equal(arcFrame.axes.x, 1);
assert.equal(arcFrame.axes.y, 1);
assert.equal(arcFrame.visibleMotion.length, 2);
const clampedFrame = createPlaybackFrame(playbackState, 99);
assert.equal(clampedFrame.index, 2);
assert.equal(clampedFrame.progress, 100);
console.log("real_simulation_programs_node_smoke=ok");