新增仿真执行过程回放
结论:真实浏览器仿真页面新增 G-code 执行过程和刀具运动回放,支持 reset、step、play、finish,逐帧高亮 G-code 与 motion row,并渲染已执行刀路和移动 toolhead。
This commit is contained in:
@@ -204,6 +204,37 @@ export function createToolpathPolylinePoints(motion) {
|
||||
return motion.map(({ axes }) => `${axes.x ?? 0},${-(axes.y ?? 0)}`).join(" ");
|
||||
}
|
||||
|
||||
export function clampPlaybackIndex(motion, index) {
|
||||
if (motion.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
if (!Number.isFinite(index)) {
|
||||
return motion.length - 1;
|
||||
}
|
||||
return Math.min(Math.max(Math.trunc(index), 0), motion.length - 1);
|
||||
}
|
||||
|
||||
export function createPlaybackFrame(state, index = state.motion.length - 1) {
|
||||
const frameIndex = clampPlaybackIndex(state.motion, index);
|
||||
const activeMotion = frameIndex >= 0 ? state.motion[frameIndex] : null;
|
||||
const visibleMotion = frameIndex >= 0 ? state.motion.slice(0, frameIndex + 1) : [];
|
||||
const axes = activeMotion?.axes ?? Object.fromEntries(AXES.map((axis) => [axis, 0]));
|
||||
return {
|
||||
apiName: "real-browser-simulation-playback-frame",
|
||||
frameVersion: 1,
|
||||
index: frameIndex,
|
||||
step: frameIndex + 1,
|
||||
total: state.motion.length,
|
||||
progress: state.motion.length > 0 ? Math.round(((frameIndex + 1) / state.motion.length) * 100) : 0,
|
||||
activeMotion,
|
||||
activeLine: activeMotion?.line ?? null,
|
||||
activeStatement: activeMotion?.statement ?? "-",
|
||||
axes,
|
||||
visibleMotion,
|
||||
fullMotion: state.motion,
|
||||
};
|
||||
}
|
||||
|
||||
export function formatPosition(axes = {}) {
|
||||
return `X ${formatAxis(axes.x)} Y ${formatAxis(axes.y)} Z ${formatAxis(axes.z)}`;
|
||||
}
|
||||
@@ -279,9 +310,10 @@ function renderAxisReadout(documentRef, axes) {
|
||||
}
|
||||
}
|
||||
|
||||
function renderToolpath(documentRef, motion) {
|
||||
function renderToolpath(documentRef, motion, visibleMotion = motion) {
|
||||
const svg = documentRef.querySelector("[data-toolpath-svg]");
|
||||
const polyline = documentRef.querySelector("[data-toolpath-polyline]");
|
||||
const executedPolyline = documentRef.querySelector("[data-toolpath-executed-polyline]");
|
||||
const head = documentRef.querySelector("[data-toolpath-head]");
|
||||
if (!svg || !polyline || !head) {
|
||||
return;
|
||||
@@ -293,12 +325,13 @@ function renderToolpath(documentRef, motion) {
|
||||
`${viewBox.minX} ${-(viewBox.minY + viewBox.height)} ${viewBox.width} ${viewBox.height}`,
|
||||
);
|
||||
polyline.setAttribute("points", createToolpathPolylinePoints(motion));
|
||||
const last = motion.at(-1)?.axes ?? { x: 0, y: 0 };
|
||||
executedPolyline?.setAttribute("points", createToolpathPolylinePoints(visibleMotion));
|
||||
const last = visibleMotion.at(-1)?.axes ?? motion[0]?.axes ?? { x: 0, y: 0 };
|
||||
head.setAttribute("cx", `${last.x ?? 0}`);
|
||||
head.setAttribute("cy", `${-(last.y ?? 0)}`);
|
||||
}
|
||||
|
||||
function renderMotionTable(documentRef, motion) {
|
||||
function renderMotionTable(documentRef, motion, activeIndex = null) {
|
||||
const body = documentRef.querySelector("[data-motion-rows]");
|
||||
if (!body) {
|
||||
return;
|
||||
@@ -307,6 +340,8 @@ function renderMotionTable(documentRef, motion) {
|
||||
for (const [index, item] of motion.entries()) {
|
||||
const row = documentRef.createElement("tr");
|
||||
row.dataset.motionRow = `${index}`;
|
||||
row.dataset.active = activeIndex === index ? "true" : "false";
|
||||
row.dataset.executed = activeIndex !== null && index <= activeIndex ? "true" : "false";
|
||||
for (const value of [
|
||||
item.type,
|
||||
item.line ?? "-",
|
||||
@@ -321,21 +356,32 @@ function renderMotionTable(documentRef, motion) {
|
||||
}
|
||||
}
|
||||
|
||||
export function renderSimulationPlaybackFrame(documentRef, state, index = state.motion.length - 1) {
|
||||
const frame = createPlaybackFrame(state, index);
|
||||
documentRef.body.dataset.playbackIndex = `${frame.index}`;
|
||||
documentRef.body.dataset.playbackComplete = frame.index === frame.total - 1 ? "true" : "false";
|
||||
setText(documentRef, "[data-playback-step]", `${frame.step}`);
|
||||
setText(documentRef, "[data-playback-total]", `${frame.total}`);
|
||||
setText(documentRef, "[data-playback-progress]", `${frame.progress}%`);
|
||||
setText(documentRef, "[data-active-line]", `${frame.activeLine ?? "-"}`);
|
||||
setText(documentRef, "[data-active-statement]", frame.activeStatement);
|
||||
renderProgramLines(documentRef, state.programText, frame.activeLine);
|
||||
renderAxisReadout(documentRef, frame.axes);
|
||||
renderToolpath(documentRef, frame.fullMotion, frame.visibleMotion);
|
||||
renderMotionTable(documentRef, frame.fullMotion, frame.index);
|
||||
return frame;
|
||||
}
|
||||
|
||||
export function renderSimulationState(documentRef, state) {
|
||||
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-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-statement]", state.motion.at(-1)?.statement ?? "-");
|
||||
setText(documentRef, "[data-canonical-output]", state.resultText);
|
||||
renderProgramSelector(documentRef, state.program?.id ?? "custom");
|
||||
renderRows(documentRef, state.summary.rows);
|
||||
renderProgramLines(documentRef, state.programText, state.motion.at(-1)?.line ?? null);
|
||||
renderAxisReadout(documentRef, state.summary.finalAxes);
|
||||
renderToolpath(documentRef, state.motion);
|
||||
renderMotionTable(documentRef, state.motion);
|
||||
renderSimulationPlaybackFrame(documentRef, state, state.motion.length - 1);
|
||||
}
|
||||
|
||||
export function getSimulationTestPrograms() {
|
||||
|
||||
Reference in New Issue
Block a user