提交 xyzbc-trt 界面与验证更新
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user