提交 xyzbc-trt 界面与验证更新

This commit is contained in:
mes123456
2026-07-02 20:25:37 -04:00
parent 68ecd05353
commit 370c344b96
868 changed files with 275426 additions and 39640 deletions

View File

@@ -121,46 +121,45 @@
throw new Error(`OPFS tool table save did not contain T2/Z10: ${savedToolDb.path}`);
}
const expectedAxisSections = {
".preview-panel": "preview-toolpath-and-machine-model",
".dro-panel": "coordinates-dro",
".gcode-panel": "program-and-mdi",
".status-sidebar": "status-mode-and-switchkins",
".info-tabs": "execution-tool-and-runtime-state",
".override-panel": "feed-rapid-override",
".spindle-coolant-panel": "spindle-coolant",
".bottom-controls": "run-jog-home-controls",
};
for (const [selector, section] of Object.entries(expectedAxisSections)) {
const element = doc.querySelector(selector);
if (!element) {
throw new Error(`missing AXIS UI section ${selector}`);
}
if (
element.dataset.axisUiSection !== section ||
element.dataset.axisUiProfile !== "xyzbc-trt" ||
element.dataset.axisUiCoordinates !== "XYZBC"
) {
throw new Error(`AXIS section dataset mismatch ${selector}: ${JSON.stringify(element.dataset)}`);
const expectedAxisRegions = ["menubar", "toolbar", "manual", "main-tabs", "pyvcp", "program", "statusbar"];
const regions = api.getRegions();
for (const region of expectedAxisRegions) {
if (regions[region] !== true || !doc.querySelector(`[data-region="${region}"]`)) {
throw new Error(`missing AXIS region ${region}: ${JSON.stringify(regions)}`);
}
}
if (!doc.querySelector('[data-shell="axis-xyzbc-trt"]')) {
throw new Error("AXIS xyzbc-trt shell missing");
}
if (!doc.querySelector('[data-action="mdi-input"]') || !doc.querySelector('[data-action="kins-tcp"]')) {
throw new Error("MDI input or switchkins TCP control missing from AXIS shell");
}
const firstScreen = await waitFor(
() => doc.querySelector('[data-axis-main-equivalence="xyzbc_trt_axis_first_screen_program_coordinates_status_mdi_switchkins_override_tool_preview_execution"]'),
"AXIS first-screen summary",
);
const capabilities = Array.from(firstScreen.querySelectorAll("[data-axis-ui-capability]"));
const capabilityIds = capabilities.map((element) => element.dataset.axisUiCapability);
for (const id of ["program", "coordinates", "status", "mdi", "switchkins", "override", "tool", "preview", "execution", "axis-buttons"]) {
if (!capabilityIds.includes(id)) {
throw new Error(`missing first-screen capability ${id}: ${capabilityIds.join(",")}`);
const buttonParity = api.getButtonParity();
if (!Array.isArray(buttonParity) || buttonParity.length < 50) {
throw new Error(`AXIS button parity list too small: ${buttonParity?.length}`);
}
for (const action of ["estop", "power", "run-ready", "run", "pause", "resume", "step", "stop", "home-all", "jog-plus", "jog-minus", "kins-identity", "kins-tcp", "kins-userk", "clear-preview"]) {
if (!buttonParity.some((item) => item.action === action && item.sourceSymbol && item.sourceLines && item.expected)) {
throw new Error(`missing source-referenced button parity for ${action}`);
}
}
if (firstScreen.dataset.axisFirstScreenReady !== "true") {
throw new Error(`first-screen summary not ready: ${firstScreen.dataset.axisFirstScreenMissing}`);
}
if (!doc.querySelector('[data-action="mdi-command"]') || !doc.querySelector('[data-action="kins-tcp"]')) {
throw new Error("MDI input or switchkins TCP control missing from first screen");
for (const selector of [
'[data-action="estop"]',
'[data-action="power"]',
'[data-action="run"]',
'[data-action="step"]',
'[data-action="stop"]',
'[data-action="jog-plus"]',
'[data-action="toggle-flood"]',
'[data-action="kins-tcp"]',
'[data-action="clear-preview"]',
'[data-menu-command="run-ready"]',
]) {
const control = doc.querySelector(selector);
if (!control?.dataset.axisSourceRef || !control?.dataset.axisExpectedEffect) {
throw new Error(`missing AXIS source metadata on ${selector}: ${JSON.stringify(control?.dataset || {})}`);
}
}
const canvas = await waitFor(() => doc.querySelector("[data-five-axis-canvas]"), "preview canvas");
@@ -198,6 +197,8 @@
}
assertCanvasNonblank(canvas);
await verifyAxisButtonLogic(doc, api);
if (runtimeErrors.length > 0) {
throw new Error(`runtime errors: ${runtimeErrors.join(" | ")}`);
}
@@ -245,6 +246,79 @@
}
}
async function verifyAxisButtonLogic(doc, api) {
const click = async (selector, label = selector) => {
const element = doc.querySelector(selector);
if (!element) throw new Error(`missing button for ${label}`);
element.click();
await wait(50);
return api.getState();
};
const waitState = (predicate, label) => waitFor(() => {
const next = api.getState();
return predicate(next) ? next : null;
}, label);
await click('[data-action="estop"]', "AXIS estop");
await waitState((state) => state.machine.estopActive === true && state.runState === "estopped", "estop active");
await click('[data-action="estop"]', "AXIS estop reset");
await waitState((state) => state.machine.estopActive === false && state.machine.taskState === "estop-reset", "estop reset");
await click('[data-action="power"]', "AXIS power");
await waitState((state) => state.machine.powerOn === true && state.machine.taskState === "on", "machine power on");
await click('[data-action="home-all"]', "AXIS home all");
await waitState((state) => state.machine.allHomed === true && state.machine.mode === "manual", "home all");
await click('[data-action="tab-mdi"]', "AXIS MDI tab");
await waitState((state) => state.machine.mode === "mdi", "MDI mode");
await click('[data-action="kins-tcp"]', "PyVCP TCP");
await waitState((state) => state.kinsType === "tcp-xyzbc" && state.rtcpState === "on" && state.mdiHistory[0] === "M428", "TCP kins");
await click('[data-action="kins-identity"]', "PyVCP identity");
await waitState((state) => state.kinsType === "identity" && state.rtcpState === "off" && state.mdiHistory[0] === "M429", "identity kins");
await click('[data-action="kins-userk"]', "PyVCP userk");
await waitState((state) => state.kinsType === "userk" && state.mdiHistory[0] === "M430", "userk kins");
await click('[data-action="tab-manual"]', "AXIS manual tab");
await waitState((state) => state.machine.mode === "manual", "manual mode");
const joint4 = doc.querySelector('[data-action="select-joint"][value="4"]');
joint4.checked = true;
joint4.dispatchEvent(new Event("change", { bubbles: true }));
await waitState((state) => state.machine.selectedJoint === 4 && state.machine.jogAxis === "c", "select C joint");
const beforeJogC = api.getState().axisPose.c;
await click('[data-action="jog-plus"]', "AXIS jog plus");
await waitState((state) => state.axisPose.c > beforeJogC, "jog C plus");
const beforeFeed = api.getState().feed.feedOverride;
await click('[data-action="feed-override-up"]', "feed override up");
await waitState((state) => state.feed.feedOverride === Math.min(beforeFeed + 10, 200), "feed override");
await click('[data-action="spindle-forward"]', "spindle forward");
await waitState((state) => state.spindle.direction === "forward" && state.spindle.enabled === true, "spindle forward");
await click('[data-action="spindle-stop"]', "spindle stop");
await waitState((state) => state.spindle.direction === "stop" && state.spindle.enabled === false, "spindle stop");
await click('[data-action="toggle-flood"]', "flood coolant");
await waitState((state) => state.coolant.flood === true, "flood coolant");
await click('[data-action="block-delete"]', "block delete");
await waitState((state) => state.gmoccapyGui.optionalBlocks === true, "block delete");
await click('[data-action="optional-stop"]', "optional stop");
await waitState((state) => state.gmoccapyGui.optionalStop === true, "optional stop");
await click('[data-action="ignore-limits"]', "ignore limits");
await waitState((state) => state.gmoccapyGui.ignoreLimits === true, "ignore limits");
await click('[data-action="view-x"]', "view X");
await waitState((state) => state.preview.selectedView === "x", "view X");
await click('[data-action="view-y"]', "view Y");
await waitState((state) => state.preview.selectedView === "y", "view Y");
await click('[data-action="view-z"]', "view Z");
await waitState((state) => state.preview.selectedView === "z", "view Z");
await click('[data-action="view-p"]', "view P");
await waitState((state) => state.preview.selectedView === "iso", "view P");
await click('[data-action="clear-preview"]', "clear preview");
await waitState((state) => state.preview.pathPoints === 0, "clear preview");
doc.querySelector('[data-menu-command="run-ready"]').click();
await waitState((state) => state.machine.powerOn === true && state.machine.allHomed === true && state.machine.mode === "auto", "run-ready menu");
}
async function readOpfsText(storage, path) {
if (!storage?.getDirectory) {
throw new Error("browser OPFS getDirectory is unavailable");

View File

@@ -18,6 +18,7 @@ import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
import { createSimulationStore } from "../../app/src/state/store.js";
import { createLinuxCncTaskHalSdk } from "../../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js";
import { buildVismachModelState, XYZBC_TRT_VISMACH_PINS } from "../../app/src/runtime/vismach-model-state.js";
import { AXIS_BUTTON_PARITY } from "../../app/src/ui/axis-shell.js";
import {
applyToolCommandSequence,
createToolRuntimeState,
@@ -222,6 +223,66 @@ assert.equal(stagedState.machineFileStaging.profileId, "xyzbc-trt");
assert.ok(stagedState.machineProject.projectRoot.startsWith("web-rtcp-5axis-xyzbc-trt-sim-plan/machines/xyzbc-trt"));
assert.ok(stagedState.machineFileStaging.gcodeSources.some((source) => source.filename === "xyzbc_switchkins.ngc"));
assert.equal(AXIS_BUTTON_PARITY.length >= 50, true);
for (const action of [
"estop",
"power",
"run-ready",
"run",
"pause",
"resume",
"step",
"stop",
"home-all",
"jog-plus",
"jog-minus",
"kins-identity",
"kins-tcp",
"kins-userk",
"clear-preview",
]) {
assert.ok(AXIS_BUTTON_PARITY.some((item) => item.action === action && item.sourceSymbol && item.sourceLines && item.expected), action);
}
const buttonStore = createSimulationStore();
buttonStore.dispatch({ type: "ESTOP" });
assert.equal(buttonStore.getState().machine.estopActive, true);
buttonStore.dispatch({ type: "RESET" });
assert.equal(buttonStore.getState().machine.taskState, "estop-reset");
buttonStore.dispatch({ type: "TOGGLE_POWER" });
assert.equal(buttonStore.getState().machine.powerOn, true);
buttonStore.dispatch({ type: "HOME" });
assert.equal(buttonStore.getState().machine.allHomed, true);
buttonStore.dispatch({ type: "SET_MODE", mode: "mdi" });
buttonStore.dispatch({ type: "RUN_MDI", command: "M428" });
assert.equal(buttonStore.getState().kinsType, "tcp-xyzbc");
buttonStore.dispatch({ type: "RUN_MDI", command: "M429" });
assert.equal(buttonStore.getState().kinsType, "identity");
buttonStore.dispatch({ type: "RUN_MDI", command: "M430" });
assert.equal(buttonStore.getState().kinsType, "userk");
buttonStore.dispatch({ type: "SET_MODE", mode: "manual" });
buttonStore.dispatch({ type: "SET_ACTIVE_JOINT", joint: 4 });
const cBeforeJog = buttonStore.getState().axisPose.c;
buttonStore.dispatch({ type: "JOG", axis: "c", direction: 1, increment: 1 });
assert.equal(buttonStore.getState().axisPose.c, cBeforeJog + 1);
const feedBefore = buttonStore.getState().feed.feedOverride;
buttonStore.dispatch({ type: "ADJUST_OVERRIDE", target: "feed", delta: 10 });
assert.equal(buttonStore.getState().feed.feedOverride, feedBefore + 10);
buttonStore.dispatch({ type: "SET_SPINDLE_DIRECTION", direction: "forward" });
assert.equal(buttonStore.getState().spindle.direction, "forward");
buttonStore.dispatch({ type: "TOGGLE_COOLANT", kind: "flood" });
assert.equal(buttonStore.getState().coolant.flood, true);
buttonStore.dispatch({ type: "SET_BLOCK_DELETE", enabled: true });
assert.equal(buttonStore.getState().gmoccapyGui.optionalBlocks, true);
buttonStore.dispatch({ type: "SET_OPTIONAL_STOP", enabled: true });
assert.equal(buttonStore.getState().gmoccapyGui.optionalStop, true);
buttonStore.dispatch({ type: "SET_IGNORE_LIMITS", enabled: true });
assert.equal(buttonStore.getState().gmoccapyGui.ignoreLimits, true);
buttonStore.dispatch({ type: "SET_VIEW", view: "x" });
assert.equal(buttonStore.getState().preview.selectedView, "x");
buttonStore.dispatch({ type: "CLEAR_PREVIEW" });
assert.equal(buttonStore.getState().preview.pathPoints, 0);
const stagingPlan = await createMachineFileStagingPlan({ profile });
assert.equal(stagingPlan.summary.remapFileCount >= 3, true);
assert.equal(stagingPlan.files.some((file) => file.sourceRel.endsWith("xyzbc.var")), true);