Advance AXIS-style Three.js simulation UI

This commit is contained in:
2026-06-17 09:59:22 +08:00
parent d6a623308b
commit 656cddf73a
24 changed files with 84658 additions and 75 deletions

View File

@@ -1655,7 +1655,7 @@ const releaseArtifactMountResult = mountIniPanelShellWorkflowOverviewReleaseRead
rowsNode: releaseArtifactRowsNode,
});
assert.equal(releaseArtifactRenderResult.rendered, true);
assert.equal(releaseArtifactRenderResult.rowCount, 8);
assert.equal(releaseArtifactRenderResult.rowCount, 9);
assert.equal(releaseArtifactRenderResult.dataset.handoffScope, "workflow-overview-release-readiness-artifact");
assert.equal(
releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"),
@@ -1669,8 +1669,12 @@ assert.equal(
releaseArtifactRenderResult.rowIds.includes("gate-action-plan"),
true,
);
assert.equal(
releaseArtifactRenderResult.rowIds.includes("axis-screenshot-artifacts"),
true,
);
assert.equal(releaseArtifactMountResult.ready, true);
assert.equal(releaseArtifactMountResult.renderResult.rowCount, 8);
assert.equal(releaseArtifactMountResult.renderResult.rowCount, 9);
assert.deepEqual(
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson("{").missing,
["artifact-json"],
@@ -1711,7 +1715,7 @@ assert.equal(
);
assert.equal(releaseArtifactUrlWorkflow.renderState.statusLine, "Ready: No blocking reasons");
assert.equal(releaseArtifactUrlWorkflow.mountResult.ready, true);
assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 8);
assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 9);
const releaseArtifactUrlWorkflowSummary =
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
releaseArtifactUrlWorkflow,

View File

@@ -7,12 +7,19 @@ import {
DEFAULT_SIMULATION_PROGRAM,
DEFAULT_SIMULATION_PROGRAM_ID,
createPlaybackFrame,
createDroState,
createMachineStatusState,
createModalState,
createSimulationSummary,
createToolpathPolylinePoints,
createToolpathViewBox,
getSimulationTestProgram,
getSimulationTestPrograms,
panToolpathViewBox,
parseLinuxCncCanonicalMotion,
runLinuxCncProgram,
runRealBrowserSimulation,
zoomToolpathViewBox,
} from "../../../runtime/ui/simulation/simulation-app.js";
import {
SIMULATION_TEST_PROGRAMS as directoryPrograms,
@@ -64,16 +71,59 @@ const arcCanonical = [
"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);
const arcModal = createModalState(arcCanonical);
const unavailableMachineStatus = createMachineStatusState(arcCanonical);
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(arcMotion[1].arc.planeAxes, ["x", "y", "z"]);
assert.equal(arcMotion[1].arc.center.x, 1);
assert.equal(arcMotion[1].arc.center.y, 0);
assert.equal(arcMotion[1].arc.rotation, -1);
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");
assert.equal(arcModal.apiName, "real-browser-simulation-modal-state");
assert.equal(arcModal.source, "linuxcnc-update-tag");
assert.ok(arcModal.raw.includes("canon_event=UPDATE_TAG line=1"));
assert.ok(arcModal.rows.some(({ id, value, source }) => id === "plane" && value === "plane=170" && source === "linuxcnc-update-tag"));
assert.equal(unavailableMachineStatus.spindle.state, "n/a");
assert.equal(unavailableMachineStatus.coolant.mist, "n/a");
const runtimeMachineStatus = createMachineStatusState([
"canon_event=SET_SPINDLE_SPEED spindle=0 speed=1200",
"canon_event=START_SPINDLE_CLOCKWISE spindle=0 wait_for_at_speed=1",
"canon_event=STOP_SPINDLE_TURNING spindle=0",
"canon_event=MIST_ON",
"canon_event=FLOOD_ON",
"canon_event=MIST_OFF",
"canon_event=FLOOD_OFF",
"canon_event=DISABLE_FEED_OVERRIDE",
"canon_event=ENABLE_FEED_OVERRIDE",
"canon_event=DISABLE_SPEED_OVERRIDE spindle=0",
"canon_event=ENABLE_SPEED_OVERRIDE spindle=0",
].join("\n"));
assert.equal(runtimeMachineStatus.spindle.state, "off");
assert.equal(runtimeMachineStatus.spindle.direction, "cw");
assert.equal(runtimeMachineStatus.spindle.speed, "1200");
assert.equal(runtimeMachineStatus.coolant.mist, "off");
assert.equal(runtimeMachineStatus.coolant.flood, "off");
assert.equal(runtimeMachineStatus.overrides.feed, "enabled");
assert.equal(runtimeMachineStatus.overrides.speed, "enabled");
const toolMachineStatus = createMachineStatusState([
"canon_event=SELECT_TOOL tool=2",
"canon_event=USE_TOOL_LENGTH_OFFSET x=0 y=0 z=1.25 a=0 b=0 c=0 u=0 v=0 w=0",
"canon_event=CHANGE_TOOL_NUMBER pocket=2",
].join("\n"));
assert.equal(toolMachineStatus.tool.selected, "2");
assert.equal(toolMachineStatus.tool.current, "2");
assert.equal(toolMachineStatus.tool.pocket, "2");
assert.equal(toolMachineStatus.tool.lengthOffset, "z=1.250");
const xzArcCanonical = [
"canon_event=UPDATE_TAG line=1 g0=-1 motion=0 plane=180 origin=530 feed=0 speed=0 flags=1048994",
@@ -101,12 +151,20 @@ const playbackState = {
motion: arcMotion,
};
const firstFrame = createPlaybackFrame(playbackState, 0);
const firstDro = createDroState(firstFrame);
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);
assert.equal(firstDro.apiName, "real-browser-simulation-dro-state");
assert.equal(firstDro.actual.x, "0.000");
assert.equal(firstDro.distanceToGo.x, "n/a");
assert.equal(firstDro.workOffsetG54.x, "n/a");
assert.equal(firstDro.g92Offset.x, "n/a");
assert.equal(firstDro.toolLengthOffset.x, "n/a");
assert.equal(firstDro.velocity, "n/a");
const arcFrame = createPlaybackFrame(playbackState, 1);
assert.equal(arcFrame.index, 1);
@@ -120,6 +178,34 @@ const clampedFrame = createPlaybackFrame(playbackState, 99);
assert.equal(clampedFrame.index, 2);
assert.equal(clampedFrame.progress, 100);
const fitViewBox = createToolpathViewBox(arcMotion);
const zoomedViewBox = zoomToolpathViewBox(fitViewBox, 2);
assert.ok(zoomedViewBox.width < fitViewBox.width, "zoomed toolpath viewBox should narrow around the fit center");
assert.ok(zoomedViewBox.height < fitViewBox.height, "zoomed toolpath viewBox should narrow around the fit center");
const pannedViewBox = panToolpathViewBox(zoomedViewBox, { x: 0.5, y: -0.25 });
assert.equal(pannedViewBox.minX, zoomedViewBox.minX + 0.5);
assert.equal(pannedViewBox.minY, zoomedViewBox.minY - 0.25);
assert.equal(pannedViewBox.width, zoomedViewBox.width);
assert.equal(pannedViewBox.height, zoomedViewBox.height);
const interpCalls = [];
const fakeInterp = {
runProgram(programText) {
interpCalls.push(["runProgram", programText]);
return arcCanonical;
},
runProgramWithIni(programText, iniPath) {
interpCalls.push(["runProgramWithIni", programText, iniPath]);
return arcCanonical;
},
};
assert.equal(runLinuxCncProgram(fakeInterp, arcProgram.text), arcCanonical);
assert.equal(runLinuxCncProgram(fakeInterp, arcProgram.text, { iniPath: "/work/axis.ini" }), arcCanonical);
assert.deepEqual(interpCalls, [
["runProgram", arcProgram.text],
["runProgramWithIni", arcProgram.text, "/work/axis.ini"],
]);
const customRunDocument = {
body: { dataset: {} },
querySelector: () => null,
@@ -133,5 +219,22 @@ assert.equal(customRun.program.id, "custom");
assert.equal(customRun.program.source, "custom");
assert.ok(customRun.resultText.includes("canon_event=STRAIGHT_FEED line=2 x=4.25 y=2.5"));
assert.ok(customRun.summary.ready);
assert.equal(customRun.modal.apiName, "real-browser-simulation-modal-state");
const sessionRunCalls = [];
const sessionRun = await runRealBrowserSimulation({
documentRef: customRunDocument,
programText: arcProgram.text,
interp: {
runProgramWithIni(programText, iniPath) {
sessionRunCalls.push([programText, iniPath]);
return arcCanonical;
},
},
iniPath: "/work/session.ini",
});
assert.equal(sessionRun.execution.mode, "linuxcnc-wasm-with-ini");
assert.equal(sessionRun.execution.iniPath, "/work/session.ini");
assert.deepEqual(sessionRunCalls, [[arcProgram.text, "/work/session.ini"]]);
console.log("real_simulation_programs_node_smoke=ok");