diff --git a/qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs b/qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs
new file mode 100644
index 0000000..5f82267
--- /dev/null
+++ b/qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs
@@ -0,0 +1,436 @@
+import fs from "node:fs/promises";
+import http from "node:http";
+import path from "node:path";
+import { randomUUID } from "node:crypto";
+import puppeteer from "puppeteer-core";
+import { PNG } from "pngjs";
+
+const REPO_ROOT = path.resolve("/home/meswork/cnc_wams");
+const QA_ROOT = path.join(REPO_ROOT, "qa/web-rtcp-5axis-site-test");
+const OUTPUT_DIR = path.join(QA_ROOT, "output");
+const SCREENSHOT_DIR = path.join(OUTPUT_DIR, "gmoccapy-xyzab");
+const CHROME_PATH = process.env.CHROME_PATH || process.env.CHROMIUM || "/usr/bin/google-chrome";
+const TARGET_URL = process.env.TARGET_URL || "";
+const APP_URL = process.env.APP_URL || "/web-rtcp-5axis-sim-plan/app/index.html";
+const JOB_ID = process.env.JOB_ID || `xyzab-${new Date().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14)}-${randomUUID().slice(0, 8)}`;
+const REPORT_ID = process.env.REPORT_ID || `report-${JOB_ID}`;
+
+await fs.mkdir(OUTPUT_DIR, { recursive: true });
+await fs.mkdir(SCREENSHOT_DIR, { recursive: true });
+
+let server = null;
+let targetUrl = TARGET_URL;
+if (!targetUrl) {
+ server = createStaticServer(REPO_ROOT);
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
+ const address = server.address();
+ if (!address || typeof address === "string") throw new Error("failed to start static server");
+ targetUrl = `http://127.0.0.1:${address.port}${APP_URL}`;
+}
+
+const browser = await puppeteer.launch({
+ headless: true,
+ executablePath: CHROME_PATH,
+ defaultViewport: { width: 1500, height: 1050, deviceScaleFactor: 1 },
+ ignoreHTTPSErrors: true,
+ args: [
+ "--ignore-certificate-errors",
+ "--disable-gpu",
+ "--enable-webgl",
+ "--use-angle=swiftshader",
+ "--enable-unsafe-swiftshader",
+ "--no-sandbox",
+ ],
+});
+
+const page = await browser.newPage();
+const consoleErrors = [];
+const pageErrors = [];
+page.on("console", (msg) => {
+ if (msg.type() === "error") consoleErrors.push(msg.text());
+});
+page.on("pageerror", (error) => pageErrors.push(error.message));
+
+const report = {
+ jobId: JOB_ID,
+ reportId: REPORT_ID,
+ evidenceScope: "gmoccapy-xyzab-function",
+ generatedAt: new Date().toISOString(),
+ targetUrl,
+ chromePath: CHROME_PATH,
+ screenshotsDir: SCREENSHOT_DIR,
+ steps: [],
+ checks: [],
+ consoleErrors,
+ pageErrors,
+};
+
+try {
+ await page.goto(report.targetUrl, { waitUntil: "networkidle2", timeout: 60000 });
+ await page.waitForSelector('[data-shell="gmoccapy-5axis"]', { timeout: 15000 });
+ await page.waitForFunction(() => Boolean(window.webRtcp5AxisSimulation?.getState), { timeout: 15000 });
+ await page.waitForFunction(() => document.querySelector("[data-five-axis-canvas]")?.dataset?.threeReady === "true", { timeout: 20000 });
+ await waitForState((state) => state.kinematicsRuntimeReadiness?.loaded === true, 30000, "initial runtime ready");
+ await captureStep("01-loaded", "Initial gmoccapy shell", "Default shell loaded with icon registry and diagnostics.");
+
+ await page.select('[data-action="select-profile"]', "gmoccapy-xyzab");
+ await waitForState((state) => state.machineProfile === "gmoccapy-xyzab", 15000, "gmoccapy XYZAB selected");
+ await wait(500);
+ await captureStep("02-profile-selected", "gmoccapy XYZAB selected", "Reference profile shows XYZAB/trivkins and disables TCP promotion.");
+
+ await click("RUN");
+ await waitForState((state) => state.operatorMessage === "run blocked: machine must be on", 5000, "RUN blocked before power");
+ await captureStep("03-run-blocked-power", "RUN blocked before power", "RUN remains clickable but reports LinuxCNC/gmoccapy gate reason.");
+
+ await click("power");
+ await waitForState((state) => state.machine.powerOn === true, 8000, "power on");
+ await click("HOME");
+ await waitForState((state) => state.machine.allHomed === true, 8000, "home complete");
+ await click("mode-auto");
+ await waitForState((state) => state.machine.mode === "auto", 8000, "auto mode");
+ await click("RUN");
+ await waitForState((state) => isReferenceRunBlockedReason(state.operatorMessage), 5000, "RUN blocked by reference evidence");
+ await captureStep("04-run-blocked-reference-evidence", "RUN blocked by reference evidence", "XYZAB passes power/home/auto gates but remains blocked without complete native INI/machine-file/runtime evidence.");
+
+ await click("mode-manual");
+ await waitForState((state) => state.machine.mode === "manual", 8000, "manual mode");
+ await click("spindle-forward");
+ await waitForState((state) => state.spindle.direction === "forward" && state.spindle.enabled === true, 5000, "spindle forward");
+ await click("spindle-stop");
+ await waitForState((state) => state.spindle.direction === "stop" && state.spindle.enabled === false, 5000, "spindle stop");
+ await click("toggle-flood");
+ await waitForState((state) => state.coolant.flood === false, 5000, "flood toggled");
+ await click("toggle-mist");
+ await waitForState((state) => state.coolant.mist === true, 5000, "mist toggled");
+ await captureStep("05-controls-active", "Spindle and coolant controls", "Spindle/coolant icons update through task policy guarded store actions.");
+
+ await captureStep("06-hal-diagnostics", "HAL and communication diagnostics", "Info panel exposes native NML/HAL/postgui references and Web runtime boundary.");
+ addChecks();
+ report.status = report.checks.every((check) => check.pass) && pageErrors.length === 0 ? "PASS" : "FAIL";
+
+ const jsonPath = path.join(OUTPUT_DIR, "gmoccapy-xyzab-function-report.json");
+ const pdfPath = path.join(OUTPUT_DIR, "gmoccapy-xyzab-function-report.pdf");
+ report.jsonPath = jsonPath;
+ report.pdfPath = pdfPath;
+ await fs.writeFile(jsonPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
+ await writePdfReport(pdfPath, report);
+
+ console.log(`gmoccapy_xyzab_function_status=${report.status}`);
+ console.log(`gmoccapy_xyzab_function_job_id=${report.jobId}`);
+ console.log(`gmoccapy_xyzab_function_report_id=${report.reportId}`);
+ console.log(`gmoccapy_xyzab_function_json=${jsonPath}`);
+ console.log(`gmoccapy_xyzab_function_pdf=${pdfPath}`);
+ console.log(`gmoccapy_xyzab_function_screenshots=${SCREENSHOT_DIR}`);
+ if (report.status !== "PASS") process.exitCode = 1;
+} finally {
+ await page.close().catch(() => {});
+ await browser.close().catch(() => {});
+ if (server) await new Promise((resolve) => server.close(resolve));
+}
+
+async function captureStep(name, title, description) {
+ const screenshotPath = path.join(SCREENSHOT_DIR, `${name}.png`);
+ await page.screenshot({ path: screenshotPath, fullPage: true });
+ const [state, dom, pixelStats] = await Promise.all([
+ getState(),
+ getDomEvidence(),
+ analyzePng(screenshotPath),
+ ]);
+ const step = {
+ name,
+ title,
+ description,
+ screenshotPath,
+ pixelStats,
+ state: summarizeState(state),
+ dom,
+ };
+ report.steps.push(step);
+ return step;
+}
+
+async function getDomEvidence() {
+ return page.evaluate(() => {
+ const text = (selector) => document.querySelector(selector)?.textContent?.trim() || "";
+ const button = (action) => {
+ const element = document.querySelector(`[data-action="${action}"]`);
+ return {
+ exists: Boolean(element),
+ iconName: element?.dataset?.iconName || null,
+ iconVariant: element?.dataset?.iconVariant || null,
+ buttonId: element?.dataset?.gmoccapyButtonId || null,
+ active: element?.dataset?.active || null,
+ commandReady: element?.dataset?.commandReady || null,
+ title: element?.getAttribute("title") || "",
+ };
+ };
+ return {
+ buttons: Object.fromEntries([
+ "estop",
+ "power",
+ "mode-manual",
+ "mode-auto",
+ "mode-mdi",
+ "RUN",
+ "STOP",
+ "PAUSE",
+ "HOME",
+ "spindle-forward",
+ "spindle-stop",
+ "spindle-reverse",
+ "toggle-flood",
+ "toggle-mist",
+ "view-x",
+ ].map((action) => [action, button(action)])),
+ profileSummary: text('[data-linuxcnc-boundary="profile-summary"]'),
+ iniKins: text('[data-linuxcnc-ini="kins"]'),
+ taskGates: text('[data-linuxcnc-task-policy="gates"]'),
+ gmoccapyCommBoundary: text('[data-gmoccapy-comm="boundary"]'),
+ gmoccapyNativeCommand: text('[data-gmoccapy-comm="native-command"]'),
+ gmoccapyWebPath: text('[data-gmoccapy-comm="web-path"]'),
+ gmoccapyPostgui: text('[data-gmoccapy-comm="postgui"]'),
+ gmoccapyHalBoundary: text('[data-gmoccapy-hal="boundary"]'),
+ gmoccapyHalPostgui: text('[data-gmoccapy-hal="postgui"]'),
+ operatorMessage: text("[data-operator-message]"),
+ };
+ });
+}
+
+async function writePdfReport(pdfPath, data) {
+ const reportPage = await browser.newPage();
+ const rows = data.checks.map((item) => `
+
+ | ${escapeHtml(item.name)} |
+ ${item.pass ? "PASS" : "FAIL"} |
+ ${escapeHtml(item.detail)} |
+
+ `).join("");
+ const steps = data.steps.map((step) => `
+
+ ${escapeHtml(step.name)} - ${escapeHtml(step.title)}
+ ${escapeHtml(step.description)}
+ Screenshot: ${escapeHtml(step.screenshotPath)}
+ State: ${escapeHtml(JSON.stringify(step.state))}
+
+ `).join("");
+ await reportPage.setContent(`
+
+
+
+
+
+
+ gmoccapy XYZAB Function Evidence
+
+ Checks
+
+ | Check | Status | Evidence |
+ ${rows}
+
+ Steps
+ ${steps}
+
+ `, { waitUntil: "load" });
+ await reportPage.pdf({
+ path: pdfPath,
+ format: "A4",
+ printBackground: true,
+ margin: { top: "12mm", right: "10mm", bottom: "12mm", left: "10mm" },
+ });
+ await reportPage.close();
+}
+
+async function click(action) {
+ await page.evaluate((selector) => {
+ const button = document.querySelector(selector);
+ if (!button) throw new Error(`missing button ${selector}`);
+ button.click();
+ }, `[data-action="${action}"]`);
+}
+
+async function getState() {
+ return page.evaluate(() => JSON.parse(JSON.stringify(window.webRtcp5AxisSimulation.getState())));
+}
+
+async function waitForState(predicate, timeoutMs, label) {
+ const started = Date.now();
+ let lastState = null;
+ while (Date.now() - started < timeoutMs) {
+ lastState = await getState();
+ if (predicate(lastState)) return lastState;
+ await wait(50);
+ }
+ throw new Error(`timeout waiting for ${label}: ${JSON.stringify(summarizeState(lastState || {}))}`);
+}
+
+function addChecks() {
+ const byName = Object.fromEntries(report.steps.map((step) => [step.name, step]));
+ const profile = byName["02-profile-selected"];
+ const blockedPower = byName["03-run-blocked-power"];
+ const blockedReference = byName["04-run-blocked-reference-evidence"];
+ const controls = byName["05-controls-active"];
+ const diagnostics = byName["06-hal-diagnostics"];
+ const buttons = diagnostics?.dom?.buttons || {};
+
+ report.checks.push(
+ check("XYZAB profile selected", profile?.state?.machineProfile === "gmoccapy-xyzab" && profile?.state?.coordinates === "XYZAB", JSON.stringify(profile?.state)),
+ check("Reference profile blocks RTCP promotion", profile?.state?.rtcpState === "off" && profile?.state?.profileTcpCapable === false, JSON.stringify(profile?.state)),
+ check("RUN blocked before power", blockedPower?.state?.operatorMessage === "run blocked: machine must be on", blockedPower?.state?.operatorMessage),
+ check("RUN blocked by reference evidence", isReferenceRunBlockedReason(blockedReference?.state?.operatorMessage), blockedReference?.state?.operatorMessage),
+ check("Core icons rendered", ["estop", "power", "mode-manual", "mode-auto", "RUN", "STOP", "HOME", "toggle-flood", "toggle-mist"].every((action) => buttons[action]?.iconName), JSON.stringify(buttons)),
+ check("Spindle/coolant state updates", controls?.state?.spindle?.direction === "stop" && controls?.state?.coolant?.mist === true, JSON.stringify(controls?.state)),
+ check("NML command diagnostic", diagnostics?.dom?.gmoccapyNativeCommand?.includes("NML emcCommand"), diagnostics?.dom?.gmoccapyNativeCommand),
+ check("Web runtime diagnostic", diagnostics?.dom?.gmoccapyWebPath?.includes("store.dispatch"), diagnostics?.dom?.gmoccapyWebPath),
+ check("HAL postgui diagnostic", diagnostics?.dom?.gmoccapyHalPostgui?.includes("tool-change-loop"), diagnostics?.dom?.gmoccapyHalPostgui),
+ check("Screenshots are nonblank", report.steps.every((step) => step.pixelStats.nonBlackRatio > 0.1), report.steps.map((step) => `${step.name}:${step.pixelStats.nonBlackRatio}`).join(", ")),
+ );
+}
+
+function summarizeState(state = {}) {
+ return {
+ machineProfile: state.machineProfile,
+ coordinates: state.profile?.traj?.coordinates,
+ profileTcpCapable: state.profile?.tcpCapable,
+ profileRtcpProof: state.profile?.rtcpProof,
+ runState: state.runState,
+ rtcpState: state.rtcpState,
+ kinsType: state.kinsType,
+ machine: {
+ powerOn: state.machine?.powerOn,
+ taskState: state.machine?.taskState,
+ mode: state.machine?.mode,
+ allHomed: state.machine?.allHomed,
+ interpState: state.machine?.interpState,
+ },
+ spindle: {
+ enabled: state.spindle?.enabled,
+ direction: state.spindle?.direction,
+ rpm: state.spindle?.rpm,
+ },
+ coolant: {
+ flood: state.coolant?.flood,
+ mist: state.coolant?.mist,
+ },
+ taskPolicy: {
+ canRunAuto: state.linuxCncTaskPolicy?.canRunAuto,
+ canRunAutoStrict: state.linuxCncTaskPolicy?.canRunAutoStrict,
+ iniLoaded: state.linuxCncTaskPolicy?.iniLoaded,
+ machineFileStaged: state.linuxCncTaskPolicy?.machineFileStaged,
+ taskHalRuntimeReady: state.linuxCncTaskPolicy?.taskHalRuntimeReady,
+ },
+ operatorMessage: state.operatorMessage,
+ };
+}
+
+function createStaticServer(rootDir) {
+ return http.createServer(async (request, response) => {
+ try {
+ const requestPath = decodeURIComponent(new URL(request.url || "/", "http://127.0.0.1").pathname);
+ const relativePath = requestPath === "/" ? "/index.html" : requestPath;
+ const targetPath = path.resolve(rootDir, `.${relativePath}`);
+ if (!targetPath.startsWith(rootDir)) {
+ response.writeHead(403);
+ response.end("forbidden");
+ return;
+ }
+ let filePath = targetPath;
+ let stat = await fs.stat(filePath).catch(() => null);
+ if (stat?.isDirectory()) {
+ filePath = path.join(filePath, "index.html");
+ stat = await fs.stat(filePath).catch(() => null);
+ }
+ if (!stat?.isFile()) {
+ response.writeHead(404);
+ response.end("not found");
+ return;
+ }
+ const body = await fs.readFile(filePath);
+ response.writeHead(200, {
+ "Content-Type": contentTypeFor(filePath),
+ "Content-Length": String(body.byteLength),
+ "Cache-Control": "no-store",
+ });
+ response.end(body);
+ } catch (error) {
+ response.writeHead(500);
+ response.end(error instanceof Error ? error.message : String(error));
+ }
+ });
+}
+
+function contentTypeFor(filePath) {
+ const ext = path.extname(filePath).toLowerCase();
+ return {
+ ".css": "text/css; charset=utf-8",
+ ".html": "text/html; charset=utf-8",
+ ".js": "text/javascript; charset=utf-8",
+ ".json": "application/json; charset=utf-8",
+ ".mjs": "text/javascript; charset=utf-8",
+ ".svg": "image/svg+xml",
+ ".png": "image/png",
+ ".wasm": "application/wasm",
+ ".xml": "application/xml; charset=utf-8",
+ }[ext] || "application/octet-stream";
+}
+
+async function analyzePng(filePath) {
+ const png = PNG.sync.read(await fs.readFile(filePath));
+ let luminanceSum = 0;
+ let nonBlack = 0;
+ for (let index = 0; index < png.data.length; index += 4) {
+ const luminance = png.data[index] * 0.2126 + png.data[index + 1] * 0.7152 + png.data[index + 2] * 0.0722;
+ luminanceSum += luminance;
+ if (luminance > 8) nonBlack += 1;
+ }
+ const total = png.width * png.height;
+ return {
+ width: png.width,
+ height: png.height,
+ averageLuminance: Number((luminanceSum / total).toFixed(2)),
+ nonBlackRatio: Number((nonBlack / total).toFixed(4)),
+ };
+}
+
+function check(name, pass, detail) {
+ return { name, pass: Boolean(pass), detail: String(detail ?? "-") };
+}
+
+function isReferenceRunBlockedReason(message) {
+ return [
+ "run blocked: LinuxCNC INI not loaded",
+ "run blocked: LinuxCNC machine files not staged",
+ "run blocked: no machine-file G-code opened for task/HAL session",
+ "run blocked: task/HAL runtime not ready",
+ ].includes(String(message || ""));
+}
+
+function escapeHtml(value) {
+ return String(value ?? "")
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+}
+
+function wait(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
diff --git a/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json
new file mode 100644
index 0000000..acda5a1
--- /dev/null
+++ b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json
@@ -0,0 +1,1238 @@
+{
+ "jobId": "xyzab-20260626214346-f4b4aa36",
+ "reportId": "report-xyzab-20260626214346-f4b4aa36",
+ "evidenceScope": "gmoccapy-xyzab-function",
+ "generatedAt": "2026-06-26T21:43:46.310Z",
+ "targetUrl": "http://127.0.0.1:36983/web-rtcp-5axis-sim-plan/app/index.html",
+ "chromePath": "/usr/bin/google-chrome",
+ "screenshotsDir": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab",
+ "steps": [
+ {
+ "name": "01-loaded",
+ "title": "Initial gmoccapy shell",
+ "description": "Default shell loaded with icon registry and diagnostics.",
+ "screenshotPath": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/01-loaded.png",
+ "pixelStats": {
+ "width": 1500,
+ "height": 1050,
+ "averageLuminance": 130.04,
+ "nonBlackRatio": 0.7823
+ },
+ "state": {
+ "machineProfile": "xyzac-trt",
+ "coordinates": "XYZAC",
+ "runState": "idle",
+ "rtcpState": "on",
+ "kinsType": "tcp-xyzac",
+ "machine": {
+ "powerOn": false,
+ "taskState": "estop-reset",
+ "mode": "manual",
+ "allHomed": false,
+ "interpState": "idle"
+ },
+ "spindle": {
+ "enabled": true,
+ "direction": "forward",
+ "rpm": 1600
+ },
+ "coolant": {
+ "flood": true,
+ "mist": false
+ },
+ "taskPolicy": {
+ "canRunAuto": false,
+ "canRunAutoStrict": false,
+ "iniLoaded": true,
+ "machineFileStaged": true,
+ "taskHalRuntimeReady": true
+ },
+ "operatorMessage": "LinuxCNC task/HAL session ready /work/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt/demos/boat-xyzac.ngc"
+ },
+ "dom": {
+ "buttons": {
+ "estop": {
+ "exists": true,
+ "iconName": "main_switch_off",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_estop",
+ "active": "false",
+ "commandReady": null,
+ "title": "Estop the machine / F1"
+ },
+ "power": {
+ "exists": true,
+ "iconName": "power_off",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_on",
+ "active": "false",
+ "commandReady": null,
+ "title": "Turn the machine on/off / F2"
+ },
+ "mode-manual": {
+ "exists": true,
+ "iconName": "mode_manual_active",
+ "iconVariant": "active",
+ "buttonId": "rbt_manual",
+ "active": "true",
+ "commandReady": null,
+ "title": "Enter manual mode"
+ },
+ "mode-auto": {
+ "exists": true,
+ "iconName": "mode_auto_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_auto",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter auto mode"
+ },
+ "mode-mdi": {
+ "exists": true,
+ "iconName": "mode_mdi_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_mdi",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter MDI mode"
+ },
+ "RUN": {
+ "exists": true,
+ "iconName": "play",
+ "iconVariant": "inactive",
+ "buttonId": "btn_run",
+ "active": "false",
+ "commandReady": "false",
+ "title": "run blocked: machine must be on"
+ },
+ "STOP": {
+ "exists": true,
+ "iconName": "stop",
+ "iconVariant": "inactive",
+ "buttonId": "btn_stop",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Stop"
+ },
+ "PAUSE": {
+ "exists": true,
+ "iconName": "pause",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_pause",
+ "active": "false",
+ "commandReady": "false",
+ "title": "pause blocked: machine must be on"
+ },
+ "HOME": {
+ "exists": true,
+ "iconName": "ref_all",
+ "iconVariant": "inactive",
+ "buttonId": "btn_homing",
+ "active": "false",
+ "commandReady": "false",
+ "title": "home blocked: machine must be on"
+ },
+ "spindle-forward": {
+ "exists": true,
+ "iconName": "spindle_right_on",
+ "iconVariant": "active",
+ "buttonId": "rbt_forward",
+ "active": "true",
+ "commandReady": "false",
+ "title": "spindle blocked: machine must be on"
+ },
+ "spindle-stop": {
+ "exists": true,
+ "iconName": "spindle_stop",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_stop",
+ "active": "false",
+ "commandReady": "false",
+ "title": "spindle blocked: machine must be on"
+ },
+ "spindle-reverse": {
+ "exists": true,
+ "iconName": "spindle_left",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_reverse",
+ "active": "false",
+ "commandReady": "false",
+ "title": "spindle blocked: machine must be on"
+ },
+ "toggle-flood": {
+ "exists": true,
+ "iconName": "coolant_flood_active",
+ "iconVariant": "active",
+ "buttonId": "tbtn_flood",
+ "active": "true",
+ "commandReady": "false",
+ "title": "coolant blocked: machine must be on"
+ },
+ "toggle-mist": {
+ "exists": true,
+ "iconName": "coolant_mist_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_mist",
+ "active": "false",
+ "commandReady": "false",
+ "title": "coolant blocked: machine must be on"
+ },
+ "view-x": {
+ "exists": true,
+ "iconName": "tool_axis_x",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_view_x",
+ "active": "false",
+ "commandReady": null,
+ "title": "View X"
+ }
+ },
+ "profileSummary": "XYZAC / 5 joints / 10 tools",
+ "iniKins": "xyzac-trt-kins / XYZAC",
+ "taskGates": "jog blocked / home blocked / auto blocked / mdi blocked / pause blocked / resume blocked",
+ "gmoccapyCommBoundary": "native_gmoccapy_nml_hal_reference_web_store_runtime_mapping",
+ "gmoccapyNativeCommand": "NML command: gmoccapy -> linuxcnc.command() -> NML emcCommand -> milltask",
+ "gmoccapyWebPath": "button -> store.dispatch -> linuxcnc-task-policy -> runtime/status snapshot -> UI/Three.js",
+ "gmoccapyPostgui": "halcomp.ready before POSTGUI_HALFILE",
+ "gmoccapyHalBoundary": "gmoccapy_hal_pin_postgui_reference_web_diagnostic_only",
+ "gmoccapyHalPostgui": "spindle-abs / spindle-at-speed / tooloffset-x / tooloffset-z / tool-change-loop",
+ "operatorMessage": "LinuxCNC task/HAL session ready /work/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt/demos/boat-xyzac.ngc"
+ }
+ },
+ {
+ "name": "02-profile-selected",
+ "title": "gmoccapy XYZAB selected",
+ "description": "Reference profile shows XYZAB/trivkins and disables TCP promotion.",
+ "screenshotPath": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/02-profile-selected.png",
+ "pixelStats": {
+ "width": 1500,
+ "height": 1050,
+ "averageLuminance": 130.01,
+ "nonBlackRatio": 0.7815
+ },
+ "state": {
+ "machineProfile": "gmoccapy-xyzab",
+ "coordinates": "XYZAB",
+ "profileTcpCapable": false,
+ "profileRtcpProof": false,
+ "runState": "idle",
+ "rtcpState": "off",
+ "kinsType": "identity",
+ "machine": {
+ "powerOn": false,
+ "taskState": "estop-reset",
+ "mode": "manual",
+ "allHomed": false,
+ "interpState": "idle"
+ },
+ "spindle": {
+ "enabled": true,
+ "direction": "forward",
+ "rpm": 1600
+ },
+ "coolant": {
+ "flood": true,
+ "mist": false
+ },
+ "taskPolicy": {
+ "canRunAuto": false,
+ "canRunAutoStrict": false,
+ "iniLoaded": false,
+ "machineFileStaged": false,
+ "taskHalRuntimeReady": true
+ },
+ "operatorMessage": "frame source fixture-ui-only"
+ },
+ "dom": {
+ "buttons": {
+ "estop": {
+ "exists": true,
+ "iconName": "main_switch_off",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_estop",
+ "active": "false",
+ "commandReady": null,
+ "title": "Estop the machine / F1"
+ },
+ "power": {
+ "exists": true,
+ "iconName": "power_off",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_on",
+ "active": "false",
+ "commandReady": null,
+ "title": "Turn the machine on/off / F2"
+ },
+ "mode-manual": {
+ "exists": true,
+ "iconName": "mode_manual_active",
+ "iconVariant": "active",
+ "buttonId": "rbt_manual",
+ "active": "true",
+ "commandReady": null,
+ "title": "Enter manual mode"
+ },
+ "mode-auto": {
+ "exists": true,
+ "iconName": "mode_auto_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_auto",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter auto mode"
+ },
+ "mode-mdi": {
+ "exists": true,
+ "iconName": "mode_mdi_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_mdi",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter MDI mode"
+ },
+ "RUN": {
+ "exists": true,
+ "iconName": "play",
+ "iconVariant": "inactive",
+ "buttonId": "btn_run",
+ "active": "false",
+ "commandReady": "false",
+ "title": "run blocked: machine must be on"
+ },
+ "STOP": {
+ "exists": true,
+ "iconName": "stop",
+ "iconVariant": "inactive",
+ "buttonId": "btn_stop",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Stop"
+ },
+ "PAUSE": {
+ "exists": true,
+ "iconName": "pause",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_pause",
+ "active": "false",
+ "commandReady": "false",
+ "title": "pause blocked: machine must be on"
+ },
+ "HOME": {
+ "exists": true,
+ "iconName": "ref_all",
+ "iconVariant": "inactive",
+ "buttonId": "btn_homing",
+ "active": "false",
+ "commandReady": "false",
+ "title": "home blocked: machine must be on"
+ },
+ "spindle-forward": {
+ "exists": true,
+ "iconName": "spindle_right_on",
+ "iconVariant": "active",
+ "buttonId": "rbt_forward",
+ "active": "true",
+ "commandReady": "false",
+ "title": "spindle blocked: machine must be on"
+ },
+ "spindle-stop": {
+ "exists": true,
+ "iconName": "spindle_stop",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_stop",
+ "active": "false",
+ "commandReady": "false",
+ "title": "spindle blocked: machine must be on"
+ },
+ "spindle-reverse": {
+ "exists": true,
+ "iconName": "spindle_left",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_reverse",
+ "active": "false",
+ "commandReady": "false",
+ "title": "spindle blocked: machine must be on"
+ },
+ "toggle-flood": {
+ "exists": true,
+ "iconName": "coolant_flood_active",
+ "iconVariant": "active",
+ "buttonId": "tbtn_flood",
+ "active": "true",
+ "commandReady": "false",
+ "title": "coolant blocked: machine must be on"
+ },
+ "toggle-mist": {
+ "exists": true,
+ "iconName": "coolant_mist_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_mist",
+ "active": "false",
+ "commandReady": "false",
+ "title": "coolant blocked: machine must be on"
+ },
+ "view-x": {
+ "exists": true,
+ "iconName": "tool_axis_x",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_view_x",
+ "active": "false",
+ "commandReady": null,
+ "title": "View X"
+ }
+ },
+ "profileSummary": "XYZAB / 5 joints / 17 tools",
+ "iniKins": "- / -",
+ "taskGates": "jog blocked / home blocked / auto blocked / mdi blocked / pause blocked / resume blocked",
+ "gmoccapyCommBoundary": "native_gmoccapy_nml_hal_reference_web_store_runtime_mapping",
+ "gmoccapyNativeCommand": "NML command: gmoccapy -> linuxcnc.command() -> NML emcCommand -> milltask",
+ "gmoccapyWebPath": "button -> store.dispatch -> linuxcnc-task-policy -> runtime/status snapshot -> UI/Three.js",
+ "gmoccapyPostgui": "halcomp.ready before POSTGUI_HALFILE",
+ "gmoccapyHalBoundary": "gmoccapy_hal_pin_postgui_reference_web_diagnostic_only",
+ "gmoccapyHalPostgui": "spindle-abs / spindle-at-speed / tooloffset-x / tooloffset-z / tool-change-loop",
+ "operatorMessage": "frame source fixture-ui-only"
+ }
+ },
+ {
+ "name": "03-run-blocked-power",
+ "title": "RUN blocked before power",
+ "description": "RUN remains clickable but reports LinuxCNC/gmoccapy gate reason.",
+ "screenshotPath": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/03-run-blocked-power.png",
+ "pixelStats": {
+ "width": 1500,
+ "height": 1050,
+ "averageLuminance": 130.01,
+ "nonBlackRatio": 0.7815
+ },
+ "state": {
+ "machineProfile": "gmoccapy-xyzab",
+ "coordinates": "XYZAB",
+ "profileTcpCapable": false,
+ "profileRtcpProof": false,
+ "runState": "idle",
+ "rtcpState": "off",
+ "kinsType": "identity",
+ "machine": {
+ "powerOn": false,
+ "taskState": "estop-reset",
+ "mode": "manual",
+ "allHomed": false,
+ "interpState": "idle"
+ },
+ "spindle": {
+ "enabled": true,
+ "direction": "forward",
+ "rpm": 1600
+ },
+ "coolant": {
+ "flood": true,
+ "mist": false
+ },
+ "taskPolicy": {
+ "canRunAuto": false,
+ "canRunAutoStrict": false,
+ "iniLoaded": false,
+ "machineFileStaged": false,
+ "taskHalRuntimeReady": true
+ },
+ "operatorMessage": "run blocked: machine must be on"
+ },
+ "dom": {
+ "buttons": {
+ "estop": {
+ "exists": true,
+ "iconName": "main_switch_off",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_estop",
+ "active": "false",
+ "commandReady": null,
+ "title": "Estop the machine / F1"
+ },
+ "power": {
+ "exists": true,
+ "iconName": "power_off",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_on",
+ "active": "false",
+ "commandReady": null,
+ "title": "Turn the machine on/off / F2"
+ },
+ "mode-manual": {
+ "exists": true,
+ "iconName": "mode_manual_active",
+ "iconVariant": "active",
+ "buttonId": "rbt_manual",
+ "active": "true",
+ "commandReady": null,
+ "title": "Enter manual mode"
+ },
+ "mode-auto": {
+ "exists": true,
+ "iconName": "mode_auto_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_auto",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter auto mode"
+ },
+ "mode-mdi": {
+ "exists": true,
+ "iconName": "mode_mdi_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_mdi",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter MDI mode"
+ },
+ "RUN": {
+ "exists": true,
+ "iconName": "play",
+ "iconVariant": "inactive",
+ "buttonId": "btn_run",
+ "active": "false",
+ "commandReady": "false",
+ "title": "run blocked: machine must be on"
+ },
+ "STOP": {
+ "exists": true,
+ "iconName": "stop",
+ "iconVariant": "inactive",
+ "buttonId": "btn_stop",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Stop"
+ },
+ "PAUSE": {
+ "exists": true,
+ "iconName": "pause",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_pause",
+ "active": "false",
+ "commandReady": "false",
+ "title": "pause blocked: machine must be on"
+ },
+ "HOME": {
+ "exists": true,
+ "iconName": "ref_all",
+ "iconVariant": "inactive",
+ "buttonId": "btn_homing",
+ "active": "false",
+ "commandReady": "false",
+ "title": "home blocked: machine must be on"
+ },
+ "spindle-forward": {
+ "exists": true,
+ "iconName": "spindle_right_on",
+ "iconVariant": "active",
+ "buttonId": "rbt_forward",
+ "active": "true",
+ "commandReady": "false",
+ "title": "spindle blocked: machine must be on"
+ },
+ "spindle-stop": {
+ "exists": true,
+ "iconName": "spindle_stop",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_stop",
+ "active": "false",
+ "commandReady": "false",
+ "title": "spindle blocked: machine must be on"
+ },
+ "spindle-reverse": {
+ "exists": true,
+ "iconName": "spindle_left",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_reverse",
+ "active": "false",
+ "commandReady": "false",
+ "title": "spindle blocked: machine must be on"
+ },
+ "toggle-flood": {
+ "exists": true,
+ "iconName": "coolant_flood_active",
+ "iconVariant": "active",
+ "buttonId": "tbtn_flood",
+ "active": "true",
+ "commandReady": "false",
+ "title": "coolant blocked: machine must be on"
+ },
+ "toggle-mist": {
+ "exists": true,
+ "iconName": "coolant_mist_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_mist",
+ "active": "false",
+ "commandReady": "false",
+ "title": "coolant blocked: machine must be on"
+ },
+ "view-x": {
+ "exists": true,
+ "iconName": "tool_axis_x",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_view_x",
+ "active": "false",
+ "commandReady": null,
+ "title": "View X"
+ }
+ },
+ "profileSummary": "XYZAB / 5 joints / 17 tools",
+ "iniKins": "- / -",
+ "taskGates": "jog blocked / home blocked / auto blocked / mdi blocked / pause blocked / resume blocked",
+ "gmoccapyCommBoundary": "native_gmoccapy_nml_hal_reference_web_store_runtime_mapping",
+ "gmoccapyNativeCommand": "NML command: gmoccapy -> linuxcnc.command() -> NML emcCommand -> milltask",
+ "gmoccapyWebPath": "button -> store.dispatch -> linuxcnc-task-policy -> runtime/status snapshot -> UI/Three.js",
+ "gmoccapyPostgui": "halcomp.ready before POSTGUI_HALFILE",
+ "gmoccapyHalBoundary": "gmoccapy_hal_pin_postgui_reference_web_diagnostic_only",
+ "gmoccapyHalPostgui": "spindle-abs / spindle-at-speed / tooloffset-x / tooloffset-z / tool-change-loop",
+ "operatorMessage": "run blocked: machine must be on"
+ }
+ },
+ {
+ "name": "04-run-blocked-reference-evidence",
+ "title": "RUN blocked by reference evidence",
+ "description": "XYZAB passes power/home/auto gates but remains blocked without complete native INI/machine-file/runtime evidence.",
+ "screenshotPath": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/04-run-blocked-reference-evidence.png",
+ "pixelStats": {
+ "width": 1500,
+ "height": 1050,
+ "averageLuminance": 130.05,
+ "nonBlackRatio": 0.7815
+ },
+ "state": {
+ "machineProfile": "gmoccapy-xyzab",
+ "coordinates": "XYZAB",
+ "profileTcpCapable": false,
+ "profileRtcpProof": false,
+ "runState": "idle",
+ "rtcpState": "off",
+ "kinsType": "identity",
+ "machine": {
+ "powerOn": true,
+ "taskState": "on",
+ "mode": "auto",
+ "allHomed": true,
+ "interpState": "idle"
+ },
+ "spindle": {
+ "enabled": true,
+ "direction": "forward",
+ "rpm": 1600
+ },
+ "coolant": {
+ "flood": true,
+ "mist": false
+ },
+ "taskPolicy": {
+ "canRunAuto": true,
+ "canRunAutoStrict": false,
+ "iniLoaded": false,
+ "machineFileStaged": false,
+ "taskHalRuntimeReady": true
+ },
+ "operatorMessage": "run blocked: LinuxCNC INI not loaded"
+ },
+ "dom": {
+ "buttons": {
+ "estop": {
+ "exists": true,
+ "iconName": "main_switch_off",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_estop",
+ "active": "false",
+ "commandReady": null,
+ "title": "Estop the machine / F1"
+ },
+ "power": {
+ "exists": true,
+ "iconName": "power_on",
+ "iconVariant": "active",
+ "buttonId": "tbtn_on",
+ "active": "true",
+ "commandReady": null,
+ "title": "Turn the machine on/off / F2"
+ },
+ "mode-manual": {
+ "exists": true,
+ "iconName": "mode_manual_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_manual",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter manual mode"
+ },
+ "mode-auto": {
+ "exists": true,
+ "iconName": "mode_auto_active",
+ "iconVariant": "active",
+ "buttonId": "rbt_auto",
+ "active": "true",
+ "commandReady": null,
+ "title": "Enter auto mode"
+ },
+ "mode-mdi": {
+ "exists": true,
+ "iconName": "mode_mdi_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_mdi",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter MDI mode"
+ },
+ "RUN": {
+ "exists": true,
+ "iconName": "play",
+ "iconVariant": "inactive",
+ "buttonId": "btn_run",
+ "active": "false",
+ "commandReady": "false",
+ "title": "run blocked: LinuxCNC INI not loaded"
+ },
+ "STOP": {
+ "exists": true,
+ "iconName": "stop",
+ "iconVariant": "inactive",
+ "buttonId": "btn_stop",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Stop"
+ },
+ "PAUSE": {
+ "exists": true,
+ "iconName": "pause",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_pause",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Pause"
+ },
+ "HOME": {
+ "exists": true,
+ "iconName": "ref_all",
+ "iconVariant": "active",
+ "buttonId": "btn_homing",
+ "active": "true",
+ "commandReady": "false",
+ "title": "home blocked: switch to manual mode first"
+ },
+ "spindle-forward": {
+ "exists": true,
+ "iconName": "spindle_right_on",
+ "iconVariant": "active",
+ "buttonId": "rbt_forward",
+ "active": "true",
+ "commandReady": "true",
+ "title": "Spindle forward"
+ },
+ "spindle-stop": {
+ "exists": true,
+ "iconName": "spindle_stop",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_stop",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Spindle stop"
+ },
+ "spindle-reverse": {
+ "exists": true,
+ "iconName": "spindle_left",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_reverse",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Spindle reverse"
+ },
+ "toggle-flood": {
+ "exists": true,
+ "iconName": "coolant_flood_active",
+ "iconVariant": "active",
+ "buttonId": "tbtn_flood",
+ "active": "true",
+ "commandReady": "true",
+ "title": "Flood coolant"
+ },
+ "toggle-mist": {
+ "exists": true,
+ "iconName": "coolant_mist_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_mist",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Mist coolant"
+ },
+ "view-x": {
+ "exists": true,
+ "iconName": "tool_axis_x",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_view_x",
+ "active": "false",
+ "commandReady": null,
+ "title": "View X"
+ }
+ },
+ "profileSummary": "XYZAB / 5 joints / 17 tools",
+ "iniKins": "- / -",
+ "taskGates": "jog blocked / home blocked / auto / mdi blocked / pause / resume blocked",
+ "gmoccapyCommBoundary": "native_gmoccapy_nml_hal_reference_web_store_runtime_mapping",
+ "gmoccapyNativeCommand": "NML command: gmoccapy -> linuxcnc.command() -> NML emcCommand -> milltask",
+ "gmoccapyWebPath": "button -> store.dispatch -> linuxcnc-task-policy -> runtime/status snapshot -> UI/Three.js",
+ "gmoccapyPostgui": "halcomp.ready before POSTGUI_HALFILE",
+ "gmoccapyHalBoundary": "gmoccapy_hal_pin_postgui_reference_web_diagnostic_only",
+ "gmoccapyHalPostgui": "spindle-abs / spindle-at-speed / tooloffset-x / tooloffset-z / tool-change-loop",
+ "operatorMessage": "run blocked: LinuxCNC INI not loaded"
+ }
+ },
+ {
+ "name": "05-controls-active",
+ "title": "Spindle and coolant controls",
+ "description": "Spindle/coolant icons update through task policy guarded store actions.",
+ "screenshotPath": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/05-controls-active.png",
+ "pixelStats": {
+ "width": 1500,
+ "height": 1050,
+ "averageLuminance": 130.04,
+ "nonBlackRatio": 0.7815
+ },
+ "state": {
+ "machineProfile": "gmoccapy-xyzab",
+ "coordinates": "XYZAB",
+ "profileTcpCapable": false,
+ "profileRtcpProof": false,
+ "runState": "idle",
+ "rtcpState": "off",
+ "kinsType": "identity",
+ "machine": {
+ "powerOn": true,
+ "taskState": "on",
+ "mode": "manual",
+ "allHomed": true,
+ "interpState": "idle"
+ },
+ "spindle": {
+ "enabled": false,
+ "direction": "stop",
+ "rpm": 1600
+ },
+ "coolant": {
+ "flood": false,
+ "mist": true
+ },
+ "taskPolicy": {
+ "canRunAuto": false,
+ "canRunAutoStrict": false,
+ "iniLoaded": false,
+ "machineFileStaged": false,
+ "taskHalRuntimeReady": true
+ },
+ "operatorMessage": "mist coolant toggled"
+ },
+ "dom": {
+ "buttons": {
+ "estop": {
+ "exists": true,
+ "iconName": "main_switch_off",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_estop",
+ "active": "false",
+ "commandReady": null,
+ "title": "Estop the machine / F1"
+ },
+ "power": {
+ "exists": true,
+ "iconName": "power_on",
+ "iconVariant": "active",
+ "buttonId": "tbtn_on",
+ "active": "true",
+ "commandReady": null,
+ "title": "Turn the machine on/off / F2"
+ },
+ "mode-manual": {
+ "exists": true,
+ "iconName": "mode_manual_active",
+ "iconVariant": "active",
+ "buttonId": "rbt_manual",
+ "active": "true",
+ "commandReady": null,
+ "title": "Enter manual mode"
+ },
+ "mode-auto": {
+ "exists": true,
+ "iconName": "mode_auto_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_auto",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter auto mode"
+ },
+ "mode-mdi": {
+ "exists": true,
+ "iconName": "mode_mdi_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_mdi",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter MDI mode"
+ },
+ "RUN": {
+ "exists": true,
+ "iconName": "play",
+ "iconVariant": "inactive",
+ "buttonId": "btn_run",
+ "active": "false",
+ "commandReady": "false",
+ "title": "run blocked: switch to auto mode first"
+ },
+ "STOP": {
+ "exists": true,
+ "iconName": "stop",
+ "iconVariant": "inactive",
+ "buttonId": "btn_stop",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Stop"
+ },
+ "PAUSE": {
+ "exists": true,
+ "iconName": "pause",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_pause",
+ "active": "false",
+ "commandReady": "false",
+ "title": "pause blocked: task mode must be auto or MDI"
+ },
+ "HOME": {
+ "exists": true,
+ "iconName": "ref_all",
+ "iconVariant": "active",
+ "buttonId": "btn_homing",
+ "active": "true",
+ "commandReady": "true",
+ "title": "Home"
+ },
+ "spindle-forward": {
+ "exists": true,
+ "iconName": "spindle_right",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_forward",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Spindle forward"
+ },
+ "spindle-stop": {
+ "exists": true,
+ "iconName": "spindle_stop_on",
+ "iconVariant": "active",
+ "buttonId": "rbt_stop",
+ "active": "true",
+ "commandReady": "true",
+ "title": "Spindle stop"
+ },
+ "spindle-reverse": {
+ "exists": true,
+ "iconName": "spindle_left",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_reverse",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Spindle reverse"
+ },
+ "toggle-flood": {
+ "exists": true,
+ "iconName": "coolant_flood_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_flood",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Flood coolant"
+ },
+ "toggle-mist": {
+ "exists": true,
+ "iconName": "coolant_mist_active",
+ "iconVariant": "active",
+ "buttonId": "tbtn_mist",
+ "active": "true",
+ "commandReady": "true",
+ "title": "Mist coolant"
+ },
+ "view-x": {
+ "exists": true,
+ "iconName": "tool_axis_x",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_view_x",
+ "active": "false",
+ "commandReady": null,
+ "title": "View X"
+ }
+ },
+ "profileSummary": "XYZAB / 5 joints / 17 tools",
+ "iniKins": "- / -",
+ "taskGates": "jog / home / auto blocked / mdi blocked / pause blocked / resume blocked",
+ "gmoccapyCommBoundary": "native_gmoccapy_nml_hal_reference_web_store_runtime_mapping",
+ "gmoccapyNativeCommand": "NML command: gmoccapy -> linuxcnc.command() -> NML emcCommand -> milltask",
+ "gmoccapyWebPath": "button -> store.dispatch -> linuxcnc-task-policy -> runtime/status snapshot -> UI/Three.js",
+ "gmoccapyPostgui": "halcomp.ready before POSTGUI_HALFILE",
+ "gmoccapyHalBoundary": "gmoccapy_hal_pin_postgui_reference_web_diagnostic_only",
+ "gmoccapyHalPostgui": "spindle-abs / spindle-at-speed / tooloffset-x / tooloffset-z / tool-change-loop",
+ "operatorMessage": "mist coolant toggled"
+ }
+ },
+ {
+ "name": "06-hal-diagnostics",
+ "title": "HAL and communication diagnostics",
+ "description": "Info panel exposes native NML/HAL/postgui references and Web runtime boundary.",
+ "screenshotPath": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/06-hal-diagnostics.png",
+ "pixelStats": {
+ "width": 1500,
+ "height": 1050,
+ "averageLuminance": 130.04,
+ "nonBlackRatio": 0.7815
+ },
+ "state": {
+ "machineProfile": "gmoccapy-xyzab",
+ "coordinates": "XYZAB",
+ "profileTcpCapable": false,
+ "profileRtcpProof": false,
+ "runState": "idle",
+ "rtcpState": "off",
+ "kinsType": "identity",
+ "machine": {
+ "powerOn": true,
+ "taskState": "on",
+ "mode": "manual",
+ "allHomed": true,
+ "interpState": "idle"
+ },
+ "spindle": {
+ "enabled": false,
+ "direction": "stop",
+ "rpm": 1600
+ },
+ "coolant": {
+ "flood": false,
+ "mist": true
+ },
+ "taskPolicy": {
+ "canRunAuto": false,
+ "canRunAutoStrict": false,
+ "iniLoaded": false,
+ "machineFileStaged": false,
+ "taskHalRuntimeReady": true
+ },
+ "operatorMessage": "mist coolant toggled"
+ },
+ "dom": {
+ "buttons": {
+ "estop": {
+ "exists": true,
+ "iconName": "main_switch_off",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_estop",
+ "active": "false",
+ "commandReady": null,
+ "title": "Estop the machine / F1"
+ },
+ "power": {
+ "exists": true,
+ "iconName": "power_on",
+ "iconVariant": "active",
+ "buttonId": "tbtn_on",
+ "active": "true",
+ "commandReady": null,
+ "title": "Turn the machine on/off / F2"
+ },
+ "mode-manual": {
+ "exists": true,
+ "iconName": "mode_manual_active",
+ "iconVariant": "active",
+ "buttonId": "rbt_manual",
+ "active": "true",
+ "commandReady": null,
+ "title": "Enter manual mode"
+ },
+ "mode-auto": {
+ "exists": true,
+ "iconName": "mode_auto_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_auto",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter auto mode"
+ },
+ "mode-mdi": {
+ "exists": true,
+ "iconName": "mode_mdi_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_mdi",
+ "active": "false",
+ "commandReady": null,
+ "title": "Enter MDI mode"
+ },
+ "RUN": {
+ "exists": true,
+ "iconName": "play",
+ "iconVariant": "inactive",
+ "buttonId": "btn_run",
+ "active": "false",
+ "commandReady": "false",
+ "title": "run blocked: switch to auto mode first"
+ },
+ "STOP": {
+ "exists": true,
+ "iconName": "stop",
+ "iconVariant": "inactive",
+ "buttonId": "btn_stop",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Stop"
+ },
+ "PAUSE": {
+ "exists": true,
+ "iconName": "pause",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_pause",
+ "active": "false",
+ "commandReady": "false",
+ "title": "pause blocked: task mode must be auto or MDI"
+ },
+ "HOME": {
+ "exists": true,
+ "iconName": "ref_all",
+ "iconVariant": "active",
+ "buttonId": "btn_homing",
+ "active": "true",
+ "commandReady": "true",
+ "title": "Home"
+ },
+ "spindle-forward": {
+ "exists": true,
+ "iconName": "spindle_right",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_forward",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Spindle forward"
+ },
+ "spindle-stop": {
+ "exists": true,
+ "iconName": "spindle_stop_on",
+ "iconVariant": "active",
+ "buttonId": "rbt_stop",
+ "active": "true",
+ "commandReady": "true",
+ "title": "Spindle stop"
+ },
+ "spindle-reverse": {
+ "exists": true,
+ "iconName": "spindle_left",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_reverse",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Spindle reverse"
+ },
+ "toggle-flood": {
+ "exists": true,
+ "iconName": "coolant_flood_inactive",
+ "iconVariant": "inactive",
+ "buttonId": "tbtn_flood",
+ "active": "false",
+ "commandReady": "true",
+ "title": "Flood coolant"
+ },
+ "toggle-mist": {
+ "exists": true,
+ "iconName": "coolant_mist_active",
+ "iconVariant": "active",
+ "buttonId": "tbtn_mist",
+ "active": "true",
+ "commandReady": "true",
+ "title": "Mist coolant"
+ },
+ "view-x": {
+ "exists": true,
+ "iconName": "tool_axis_x",
+ "iconVariant": "inactive",
+ "buttonId": "rbt_view_x",
+ "active": "false",
+ "commandReady": null,
+ "title": "View X"
+ }
+ },
+ "profileSummary": "XYZAB / 5 joints / 17 tools",
+ "iniKins": "- / -",
+ "taskGates": "jog / home / auto blocked / mdi blocked / pause blocked / resume blocked",
+ "gmoccapyCommBoundary": "native_gmoccapy_nml_hal_reference_web_store_runtime_mapping",
+ "gmoccapyNativeCommand": "NML command: gmoccapy -> linuxcnc.command() -> NML emcCommand -> milltask",
+ "gmoccapyWebPath": "button -> store.dispatch -> linuxcnc-task-policy -> runtime/status snapshot -> UI/Three.js",
+ "gmoccapyPostgui": "halcomp.ready before POSTGUI_HALFILE",
+ "gmoccapyHalBoundary": "gmoccapy_hal_pin_postgui_reference_web_diagnostic_only",
+ "gmoccapyHalPostgui": "spindle-abs / spindle-at-speed / tooloffset-x / tooloffset-z / tool-change-loop",
+ "operatorMessage": "mist coolant toggled"
+ }
+ }
+ ],
+ "checks": [
+ {
+ "name": "XYZAB profile selected",
+ "pass": true,
+ "detail": "{\"machineProfile\":\"gmoccapy-xyzab\",\"coordinates\":\"XYZAB\",\"profileTcpCapable\":false,\"profileRtcpProof\":false,\"runState\":\"idle\",\"rtcpState\":\"off\",\"kinsType\":\"identity\",\"machine\":{\"powerOn\":false,\"taskState\":\"estop-reset\",\"mode\":\"manual\",\"allHomed\":false,\"interpState\":\"idle\"},\"spindle\":{\"enabled\":true,\"direction\":\"forward\",\"rpm\":1600},\"coolant\":{\"flood\":true,\"mist\":false},\"taskPolicy\":{\"canRunAuto\":false,\"canRunAutoStrict\":false,\"iniLoaded\":false,\"machineFileStaged\":false,\"taskHalRuntimeReady\":true},\"operatorMessage\":\"frame source fixture-ui-only\"}"
+ },
+ {
+ "name": "Reference profile blocks RTCP promotion",
+ "pass": true,
+ "detail": "{\"machineProfile\":\"gmoccapy-xyzab\",\"coordinates\":\"XYZAB\",\"profileTcpCapable\":false,\"profileRtcpProof\":false,\"runState\":\"idle\",\"rtcpState\":\"off\",\"kinsType\":\"identity\",\"machine\":{\"powerOn\":false,\"taskState\":\"estop-reset\",\"mode\":\"manual\",\"allHomed\":false,\"interpState\":\"idle\"},\"spindle\":{\"enabled\":true,\"direction\":\"forward\",\"rpm\":1600},\"coolant\":{\"flood\":true,\"mist\":false},\"taskPolicy\":{\"canRunAuto\":false,\"canRunAutoStrict\":false,\"iniLoaded\":false,\"machineFileStaged\":false,\"taskHalRuntimeReady\":true},\"operatorMessage\":\"frame source fixture-ui-only\"}"
+ },
+ {
+ "name": "RUN blocked before power",
+ "pass": true,
+ "detail": "run blocked: machine must be on"
+ },
+ {
+ "name": "RUN blocked by reference evidence",
+ "pass": true,
+ "detail": "run blocked: LinuxCNC INI not loaded"
+ },
+ {
+ "name": "Core icons rendered",
+ "pass": true,
+ "detail": "{\"estop\":{\"exists\":true,\"iconName\":\"main_switch_off\",\"iconVariant\":\"inactive\",\"buttonId\":\"tbtn_estop\",\"active\":\"false\",\"commandReady\":null,\"title\":\"Estop the machine / F1\"},\"power\":{\"exists\":true,\"iconName\":\"power_on\",\"iconVariant\":\"active\",\"buttonId\":\"tbtn_on\",\"active\":\"true\",\"commandReady\":null,\"title\":\"Turn the machine on/off / F2\"},\"mode-manual\":{\"exists\":true,\"iconName\":\"mode_manual_active\",\"iconVariant\":\"active\",\"buttonId\":\"rbt_manual\",\"active\":\"true\",\"commandReady\":null,\"title\":\"Enter manual mode\"},\"mode-auto\":{\"exists\":true,\"iconName\":\"mode_auto_inactive\",\"iconVariant\":\"inactive\",\"buttonId\":\"rbt_auto\",\"active\":\"false\",\"commandReady\":null,\"title\":\"Enter auto mode\"},\"mode-mdi\":{\"exists\":true,\"iconName\":\"mode_mdi_inactive\",\"iconVariant\":\"inactive\",\"buttonId\":\"rbt_mdi\",\"active\":\"false\",\"commandReady\":null,\"title\":\"Enter MDI mode\"},\"RUN\":{\"exists\":true,\"iconName\":\"play\",\"iconVariant\":\"inactive\",\"buttonId\":\"btn_run\",\"active\":\"false\",\"commandReady\":\"false\",\"title\":\"run blocked: switch to auto mode first\"},\"STOP\":{\"exists\":true,\"iconName\":\"stop\",\"iconVariant\":\"inactive\",\"buttonId\":\"btn_stop\",\"active\":\"false\",\"commandReady\":\"true\",\"title\":\"Stop\"},\"PAUSE\":{\"exists\":true,\"iconName\":\"pause\",\"iconVariant\":\"inactive\",\"buttonId\":\"tbtn_pause\",\"active\":\"false\",\"commandReady\":\"false\",\"title\":\"pause blocked: task mode must be auto or MDI\"},\"HOME\":{\"exists\":true,\"iconName\":\"ref_all\",\"iconVariant\":\"active\",\"buttonId\":\"btn_homing\",\"active\":\"true\",\"commandReady\":\"true\",\"title\":\"Home\"},\"spindle-forward\":{\"exists\":true,\"iconName\":\"spindle_right\",\"iconVariant\":\"inactive\",\"buttonId\":\"rbt_forward\",\"active\":\"false\",\"commandReady\":\"true\",\"title\":\"Spindle forward\"},\"spindle-stop\":{\"exists\":true,\"iconName\":\"spindle_stop_on\",\"iconVariant\":\"active\",\"buttonId\":\"rbt_stop\",\"active\":\"true\",\"commandReady\":\"true\",\"title\":\"Spindle stop\"},\"spindle-reverse\":{\"exists\":true,\"iconName\":\"spindle_left\",\"iconVariant\":\"inactive\",\"buttonId\":\"rbt_reverse\",\"active\":\"false\",\"commandReady\":\"true\",\"title\":\"Spindle reverse\"},\"toggle-flood\":{\"exists\":true,\"iconName\":\"coolant_flood_inactive\",\"iconVariant\":\"inactive\",\"buttonId\":\"tbtn_flood\",\"active\":\"false\",\"commandReady\":\"true\",\"title\":\"Flood coolant\"},\"toggle-mist\":{\"exists\":true,\"iconName\":\"coolant_mist_active\",\"iconVariant\":\"active\",\"buttonId\":\"tbtn_mist\",\"active\":\"true\",\"commandReady\":\"true\",\"title\":\"Mist coolant\"},\"view-x\":{\"exists\":true,\"iconName\":\"tool_axis_x\",\"iconVariant\":\"inactive\",\"buttonId\":\"rbt_view_x\",\"active\":\"false\",\"commandReady\":null,\"title\":\"View X\"}}"
+ },
+ {
+ "name": "Spindle/coolant state updates",
+ "pass": true,
+ "detail": "{\"machineProfile\":\"gmoccapy-xyzab\",\"coordinates\":\"XYZAB\",\"profileTcpCapable\":false,\"profileRtcpProof\":false,\"runState\":\"idle\",\"rtcpState\":\"off\",\"kinsType\":\"identity\",\"machine\":{\"powerOn\":true,\"taskState\":\"on\",\"mode\":\"manual\",\"allHomed\":true,\"interpState\":\"idle\"},\"spindle\":{\"enabled\":false,\"direction\":\"stop\",\"rpm\":1600},\"coolant\":{\"flood\":false,\"mist\":true},\"taskPolicy\":{\"canRunAuto\":false,\"canRunAutoStrict\":false,\"iniLoaded\":false,\"machineFileStaged\":false,\"taskHalRuntimeReady\":true},\"operatorMessage\":\"mist coolant toggled\"}"
+ },
+ {
+ "name": "NML command diagnostic",
+ "pass": true,
+ "detail": "NML command: gmoccapy -> linuxcnc.command() -> NML emcCommand -> milltask"
+ },
+ {
+ "name": "Web runtime diagnostic",
+ "pass": true,
+ "detail": "button -> store.dispatch -> linuxcnc-task-policy -> runtime/status snapshot -> UI/Three.js"
+ },
+ {
+ "name": "HAL postgui diagnostic",
+ "pass": true,
+ "detail": "spindle-abs / spindle-at-speed / tooloffset-x / tooloffset-z / tool-change-loop"
+ },
+ {
+ "name": "Screenshots are nonblank",
+ "pass": true,
+ "detail": "01-loaded:0.7823, 02-profile-selected:0.7815, 03-run-blocked-power:0.7815, 04-run-blocked-reference-evidence:0.7815, 05-controls-active:0.7815, 06-hal-diagnostics:0.7815"
+ }
+ ],
+ "consoleErrors": [
+ "Failed to load resource: the server responded with a status of 404 (Not Found)",
+ "Failed to load resource: the server responded with a status of 404 (Not Found)",
+ "Failed to load resource: the server responded with a status of 404 (Not Found)",
+ "Failed to load resource: the server responded with a status of 404 (Not Found)",
+ "Failed to load resource: the server responded with a status of 404 (Not Found)",
+ "Failed to load resource: the server responded with a status of 404 (Not Found)",
+ "Failed to load resource: the server responded with a status of 404 (Not Found)"
+ ],
+ "pageErrors": [],
+ "status": "PASS",
+ "jsonPath": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json",
+ "pdfPath": "/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.pdf"
+}
diff --git a/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.pdf b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.pdf
new file mode 100644
index 0000000..8735a8d
Binary files /dev/null and b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.pdf differ
diff --git a/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/01-loaded.png b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/01-loaded.png
new file mode 100644
index 0000000..6086e9b
Binary files /dev/null and b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/01-loaded.png differ
diff --git a/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/02-profile-selected.png b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/02-profile-selected.png
new file mode 100644
index 0000000..d6704db
Binary files /dev/null and b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/02-profile-selected.png differ
diff --git a/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/03-run-blocked-power.png b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/03-run-blocked-power.png
new file mode 100644
index 0000000..5811adf
Binary files /dev/null and b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/03-run-blocked-power.png differ
diff --git a/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/04-run-blocked-reference-evidence.png b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/04-run-blocked-reference-evidence.png
new file mode 100644
index 0000000..ec9df27
Binary files /dev/null and b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/04-run-blocked-reference-evidence.png differ
diff --git a/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/05-controls-active.png b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/05-controls-active.png
new file mode 100644
index 0000000..624f0e1
Binary files /dev/null and b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/05-controls-active.png differ
diff --git a/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/06-hal-diagnostics.png b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/06-hal-diagnostics.png
new file mode 100644
index 0000000..624f0e1
Binary files /dev/null and b/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/06-hal-diagnostics.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/package.json b/web-rtcp-5axis-sim-plan/app/package.json
index 88a3ac5..927fd85 100644
--- a/web-rtcp-5axis-sim-plan/app/package.json
+++ b/web-rtcp-5axis-sim-plan/app/package.json
@@ -7,7 +7,7 @@
"build": "node scripts/build-static.mjs",
"dev": "python3 -m http.server 4173",
"smoke": "bash ../tests/browser/verify_gmoccapy_shell_browser.sh && bash ../tests/browser/verify_gmoccapy_dist_browser.sh",
- "smoke:node": "node ../tests/node/verify_linuxcnc_kinematics_runtime.mjs && node ../tests/node/verify_linuxcnc_interpreter_runtime.mjs && node ../tests/node/verify_linuxcnc_ini_runtime.mjs && node ../tests/node/verify_run_preconditions.mjs && node ../tests/node/verify_run_feedback_loop.mjs && node ../tests/node/verify_linuxcnc_task_hal_runtime.mjs && node ../tests/node/verify_native_task_hal_audit.mjs && node ../tests/node/verify_full_linuxcnc_5axis_source.mjs && node ../tests/node/verify_real_linuxcnc_5axis_program_cases.mjs && node ../tests/node/verify_full_execution_boundary.mjs && node ../tests/node/verify_machine_file_staging.mjs && node ../tests/node/verify_five_axis_session.mjs && node ../tests/node/verify_rtcp_store.mjs && node ../tests/node/verify_profile_boundary.mjs && node ../tests/node/verify_linear_unit_conversion.mjs"
+ "smoke:node": "node ../tests/node/verify_linuxcnc_kinematics_runtime.mjs && node ../tests/node/verify_linuxcnc_interpreter_runtime.mjs && node ../tests/node/verify_linuxcnc_ini_runtime.mjs && node ../tests/node/verify_run_preconditions.mjs && node ../tests/node/verify_run_feedback_loop.mjs && node ../tests/node/verify_linuxcnc_task_hal_runtime.mjs && node ../tests/node/verify_native_task_hal_audit.mjs && node ../tests/node/verify_full_linuxcnc_5axis_source.mjs && node ../tests/node/verify_real_linuxcnc_5axis_program_cases.mjs && node ../tests/node/verify_full_execution_boundary.mjs && node ../tests/node/verify_machine_file_staging.mjs && node ../tests/node/verify_five_axis_session.mjs && node ../tests/node/verify_rtcp_store.mjs && node ../tests/node/verify_profile_boundary.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_icon_manifest.mjs && node ../tests/node/verify_gmoccapy_icon_registry.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs && node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs && node ../tests/node/verify_linear_unit_conversion.mjs"
},
"dependencies": {},
"devDependencies": {}
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_auto__mode_auto_inactive__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_auto__mode_auto_inactive__48.png
new file mode 100644
index 0000000..1305c0c
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_auto__mode_auto_inactive__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_auto_on__mode_auto_active__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_auto_on__mode_auto_active__48.png
new file mode 100644
index 0000000..1305c0c
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_auto_on__mode_auto_active__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_back_file_load__back_to_app__48.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_back_file_load__back_to_app__48.svg
new file mode 100644
index 0000000..d3cce96
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_back_file_load__back_to_app__48.svg
@@ -0,0 +1,230 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_back_tool__back_to_app__48.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_back_tool__back_to_app__48.svg
new file mode 100644
index 0000000..d3cce96
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_back_tool__back_to_app__48.svg
@@ -0,0 +1,230 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_close__logout__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_close__logout__48.png
new file mode 100644
index 0000000..f2b7e3d
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_close__logout__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_continuous__jog_continuous__24.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_continuous__jog_continuous__24.png
new file mode 100644
index 0000000..2a825a5
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_continuous__jog_continuous__24.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_coolant_off__coolant_flood_inactive__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_coolant_off__coolant_flood_inactive__48.png
new file mode 100644
index 0000000..02d3e87
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_coolant_off__coolant_flood_inactive__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_coolant_on__coolant_flood_active__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_coolant_on__coolant_flood_active__48.png
new file mode 100644
index 0000000..66c138c
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_coolant_on__coolant_flood_active__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_dimensions__dimensions__24.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_dimensions__dimensions__24.png
new file mode 100644
index 0000000..025648d
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_dimensions__dimensions__24.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_dir_up__chevron_up__32.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_dir_up__chevron_up__32.svg
new file mode 100644
index 0000000..54263df
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_dir_up__chevron_up__32.svg
@@ -0,0 +1,196 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_down__chevron_down__24.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_down__chevron_down__24.svg
new file mode 100644
index 0000000..efbaa1e
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_down__chevron_down__24.svg
@@ -0,0 +1,200 @@
+
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit-redo__edit_redo__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit-redo__edit_redo__32.png
new file mode 100644
index 0000000..3eb7b05
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit-redo__edit_redo__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit-undo__edit_undo__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit-undo__edit_undo__32.png
new file mode 100644
index 0000000..61b2ce9
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit-undo__edit_undo__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_comment__comment__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_comment__comment__32.png
new file mode 100644
index 0000000..4acca0a
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_comment__comment__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_calculator__calculator_open__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_calculator__calculator_open__32.png
new file mode 100644
index 0000000..e0bf102
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_calculator__calculator_open__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_close__back_to_app__48.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_close__back_to_app__48.svg
new file mode 100644
index 0000000..d3cce96
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_close__back_to_app__48.svg
@@ -0,0 +1,230 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_keyboard__keyboard__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_keyboard__keyboard__32.png
new file mode 100644
index 0000000..7522263
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_keyboard__keyboard__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_keyboard_hide__keyboard_hide__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_keyboard_hide__keyboard_hide__32.png
new file mode 100644
index 0000000..1c23d0f
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_keyboard_hide__keyboard_hide__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_new__new_document__32.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_new__new_document__32.svg
new file mode 100644
index 0000000..1bfdb16
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_new__new_document__32.svg
@@ -0,0 +1,448 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_reload__refresh__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_reload__refresh__32.png
new file mode 100644
index 0000000..1b1ab55
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_reload__refresh__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_save__save__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_save__save__32.png
new file mode 100644
index 0000000..98b49a0
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_save__save__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_save_as__save_as__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_save_as__save_as__32.png
new file mode 100644
index 0000000..b82ba95
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_edit_menu_save_as__save_as__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_editor__edit_code__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_editor__edit_code__32.png
new file mode 100644
index 0000000..81a1d30
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_editor__edit_code__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_emergency__main_switch_on__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_emergency__main_switch_on__48.png
new file mode 100644
index 0000000..3661dcc
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_emergency__main_switch_on__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_emergency_off__main_switch_off__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_emergency_off__main_switch_off__48.png
new file mode 100644
index 0000000..597330a
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_emergency_off__main_switch_off__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview0_close__fullscreen_close__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview0_close__fullscreen_close__48.png
new file mode 100644
index 0000000..c4bf484
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview0_close__fullscreen_close__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview0_open__fullscreen_open__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview0_open__fullscreen_open__48.png
new file mode 100644
index 0000000..c4bf484
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview0_open__fullscreen_open__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview1_close__fullscreen_close__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview1_close__fullscreen_close__48.png
new file mode 100644
index 0000000..c4bf484
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview1_close__fullscreen_close__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview1_open__fullscreen_open__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview1_open__fullscreen_open__48.png
new file mode 100644
index 0000000..c4bf484
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_fullsize_preview1_open__fullscreen_open__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_home__home_folder__32.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_home__home_folder__32.svg
new file mode 100644
index 0000000..4f16958
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_home__home_folder__32.svg
@@ -0,0 +1,445 @@
+
+
\ No newline at end of file
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_index_tool__mill_tool_set_num__48.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_index_tool__mill_tool_set_num__48.svg
new file mode 100644
index 0000000..7c1c2cd
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_index_tool__mill_tool_set_num__48.svg
@@ -0,0 +1,83 @@
+
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_jump_to__user_defined_folder__32.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_jump_to__user_defined_folder__32.svg
new file mode 100644
index 0000000..4832fe9
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_jump_to__user_defined_folder__32.svg
@@ -0,0 +1,204 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_machine_off__power_off__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_machine_off__power_off__48.png
new file mode 100644
index 0000000..127c9e9
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_machine_off__power_off__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_machine_on__power_on__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_machine_on__power_on__48.png
new file mode 100644
index 0000000..cf3b9b1
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_machine_on__power_on__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_calculator__calculator_open__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_calculator__calculator_open__32.png
new file mode 100644
index 0000000..e0bf102
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_calculator__calculator_open__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_keyboard__keyboard__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_keyboard__keyboard__32.png
new file mode 100644
index 0000000..7522263
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_keyboard__keyboard__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_keyboard_hide__keyboard_hide__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_keyboard_hide__keyboard_hide__32.png
new file mode 100644
index 0000000..1c23d0f
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_keyboard_hide__keyboard_hide__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_stop__stop__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_stop__stop__32.png
new file mode 100644
index 0000000..22d1e99
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_macro_menu_stop__stop__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_manual__mode_manual_inactive__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_manual__mode_manual_inactive__48.png
new file mode 100644
index 0000000..ca80ef1
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_manual__mode_manual_inactive__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_manual_on__mode_manual_active__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_manual_on__mode_manual_active__48.png
new file mode 100644
index 0000000..ca80ef1
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_manual_on__mode_manual_active__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mdi__mode_mdi_inactive__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mdi__mode_mdi_inactive__48.png
new file mode 100644
index 0000000..faede08
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mdi__mode_mdi_inactive__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mdi_on__mode_mdi_active__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mdi_on__mode_mdi_active__48.png
new file mode 100644
index 0000000..faede08
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mdi_on__mode_mdi_active__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mist_off__coolant_mist_inactive__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mist_off__coolant_mist_inactive__48.png
new file mode 100644
index 0000000..982ae72
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mist_off__coolant_mist_inactive__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mist_on__coolant_mist_active__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mist_on__coolant_mist_active__48.png
new file mode 100644
index 0000000..9e57d5a
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_mist_on__coolant_mist_active__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_offset_calculator__calculator_open__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_offset_calculator__calculator_open__32.png
new file mode 100644
index 0000000..e0bf102
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_offset_calculator__calculator_open__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_open__open_file__32.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_open__open_file__32.svg
new file mode 100644
index 0000000..55e6177
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_open__open_file__32.svg
@@ -0,0 +1,535 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_pause__pause__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_pause__pause__32.png
new file mode 100644
index 0000000..b34c592
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_pause__pause__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_pause_active__pause_active__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_pause_active__pause_active__32.png
new file mode 100644
index 0000000..8c3e971
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_pause_active__pause_active__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_rabbit_jog__jog_speed_fast__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_rabbit_jog__jog_speed_fast__32.png
new file mode 100644
index 0000000..927226d
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_rabbit_jog__jog_speed_fast__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_a__ref_a__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_a__ref_a__48.png
new file mode 100644
index 0000000..abe43a5
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_a__ref_a__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_all__ref_all__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_all__ref_all__48.png
new file mode 100644
index 0000000..d0f386f
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_all__ref_all__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_b__ref_b__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_b__ref_b__48.png
new file mode 100644
index 0000000..019725e
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_b__ref_b__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_menu__ref_all__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_menu__ref_all__48.png
new file mode 100644
index 0000000..d0f386f
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_menu__ref_all__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_menu_close__back_to_app__48.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_menu_close__back_to_app__48.svg
new file mode 100644
index 0000000..d3cce96
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_menu_close__back_to_app__48.svg
@@ -0,0 +1,230 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_paginate_next__chevron_right__32.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_paginate_next__chevron_right__32.svg
new file mode 100644
index 0000000..41e5990
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_paginate_next__chevron_right__32.svg
@@ -0,0 +1,192 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_paginate_prev__chevron_left__32.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_paginate_prev__chevron_left__32.svg
new file mode 100644
index 0000000..d5381de
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_paginate_prev__chevron_left__32.svg
@@ -0,0 +1,854 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_x__ref_x__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_x__ref_x__48.png
new file mode 100644
index 0000000..931a889
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_x__ref_x__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_y__ref_y__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_y__ref_y__48.png
new file mode 100644
index 0000000..4d929bc
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_y__ref_y__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_z__ref_z__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_z__ref_z__48.png
new file mode 100644
index 0000000..aeabddf
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_ref_z__ref_z__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_refresh_dir__refresh__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_refresh_dir__refresh__32.png
new file mode 100644
index 0000000..1b1ab55
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_refresh_dir__refresh__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_reload1__refresh__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_reload1__refresh__32.png
new file mode 100644
index 0000000..1b1ab55
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_reload1__refresh__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_run__play__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_run__play__32.png
new file mode 100644
index 0000000..0103eb3
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_run__play__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_run_from__run_from_line__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_run_from__run_from_line__48.png
new file mode 100644
index 0000000..0b4ce9f
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_run_from__run_from_line__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_sel_next__chevron_right__32.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_sel_next__chevron_right__32.svg
new file mode 100644
index 0000000..41e5990
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_sel_next__chevron_right__32.svg
@@ -0,0 +1,192 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_sel_prev__chevron_left__32.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_sel_prev__chevron_left__32.svg
new file mode 100644
index 0000000..d5381de
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_sel_prev__chevron_left__32.svg
@@ -0,0 +1,854 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_select__select_file__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_select__select_file__32.png
new file mode 100644
index 0000000..1ed6d98
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_select__select_file__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_settings__mode_settings_inactive__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_settings__mode_settings_inactive__48.png
new file mode 100644
index 0000000..69f9caf
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_settings__mode_settings_inactive__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_settings_on__mode_settings_active__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_settings_on__mode_settings_active__48.png
new file mode 100644
index 0000000..69f9caf
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_settings_on__mode_settings_active__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_skip_optional_active__skip_optional_active__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_skip_optional_active__skip_optional_active__32.png
new file mode 100644
index 0000000..98143ac
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_skip_optional_active__skip_optional_active__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_skip_optional_inactive__skip_optional_inactive__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_skip_optional_inactive__skip_optional_inactive__32.png
new file mode 100644
index 0000000..a850d4a
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_skip_optional_inactive__skip_optional_inactive__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_forward__spindle_right__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_forward__spindle_right__48.png
new file mode 100644
index 0000000..f1dd415
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_forward__spindle_right__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_forward_on__spindle_right_on__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_forward_on__spindle_right_on__48.png
new file mode 100644
index 0000000..0f251b3
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_forward_on__spindle_right_on__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_reverse__spindle_left__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_reverse__spindle_left__48.png
new file mode 100644
index 0000000..27909b4
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_reverse__spindle_left__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_reverse_on__spindle_left_on__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_reverse_on__spindle_left_on__48.png
new file mode 100644
index 0000000..db65ddb
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_reverse_on__spindle_left_on__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_stop__spindle_stop__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_stop__spindle_stop__48.png
new file mode 100644
index 0000000..9091d78
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_stop__spindle_stop__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_stop_on__spindle_stop_on__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_stop_on__spindle_stop_on__48.png
new file mode 100644
index 0000000..eb3fde2
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_spindle_stop_on__spindle_stop_on__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_split_view__split_view__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_split_view__split_view__32.png
new file mode 100644
index 0000000..4b99265
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_split_view__split_view__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_step__step__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_step__step__32.png
new file mode 100644
index 0000000..a3dd9c5
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_step__step__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_stop__stop__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_stop__stop__32.png
new file mode 100644
index 0000000..22d1e99
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_stop__stop__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_add__add__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_add__add__32.png
new file mode 100644
index 0000000..1098f03
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_add__add__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_by_no__mill_tool_change_num__48.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_by_no__mill_tool_change_num__48.svg
new file mode 100644
index 0000000..81d2dd3
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_by_no__mill_tool_change_num__48.svg
@@ -0,0 +1,33 @@
+
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_calculator__calculator_open__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_calculator__calculator_open__32.png
new file mode 100644
index 0000000..e0bf102
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_calculator__calculator_open__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_clear__clear__24.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_clear__clear__24.png
new file mode 100644
index 0000000..65ca514
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_clear__clear__24.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_delete__delete__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_delete__delete__32.png
new file mode 100644
index 0000000..34eb0ee
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_delete__delete__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_path__toolpath__24.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_path__toolpath__24.png
new file mode 100644
index 0000000..5eac0ae
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_path__toolpath__24.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_reload__refresh__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_reload__refresh__32.png
new file mode 100644
index 0000000..1b1ab55
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_reload__refresh__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_save__save__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_save__save__32.png
new file mode 100644
index 0000000..98b49a0
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tool_save__save__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_toolchange__mill_tool_change__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_toolchange__mill_tool_change__48.png
new file mode 100644
index 0000000..8953f04
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_toolchange__mill_tool_change__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tools__hsk_mill_tool__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tools__hsk_mill_tool__48.png
new file mode 100644
index 0000000..4453583
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_tools__hsk_mill_tool__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_a__touch_a__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_a__touch_a__48.png
new file mode 100644
index 0000000..dd7ed9e
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_a__touch_a__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_b__touch_b__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_b__touch_b__48.png
new file mode 100644
index 0000000..fd6ea00
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_b__touch_b__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_menu_close__back_to_app__48.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_menu_close__back_to_app__48.svg
new file mode 100644
index 0000000..d3cce96
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_menu_close__back_to_app__48.svg
@@ -0,0 +1,230 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_off__touch_off__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_off__touch_off__48.png
new file mode 100644
index 0000000..cad6d9e
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_off__touch_off__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_paginate_next__chevron_right__32.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_paginate_next__chevron_right__32.svg
new file mode 100644
index 0000000..41e5990
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_paginate_next__chevron_right__32.svg
@@ -0,0 +1,192 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_paginate_prev__chevron_left__32.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_paginate_prev__chevron_left__32.svg
new file mode 100644
index 0000000..d5381de
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_paginate_prev__chevron_left__32.svg
@@ -0,0 +1,854 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_x__touch_x__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_x__touch_x__48.png
new file mode 100644
index 0000000..4ad968c
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_x__touch_x__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_y__touch_y__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_y__touch_y__48.png
new file mode 100644
index 0000000..858ecc2
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_y__touch_y__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_z__touch_z__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_z__touch_z__48.png
new file mode 100644
index 0000000..3c5bb69
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_touch_z__touch_z__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_turtle_jog__jog_speed_slow__32.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_turtle_jog__jog_speed_slow__32.png
new file mode 100644
index 0000000..847ee1a
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_turtle_jog__jog_speed_slow__32.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_unref_all__unref_all__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_unref_all__unref_all__48.png
new file mode 100644
index 0000000..6e7b6a1
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_unref_all__unref_all__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_up__chevron_up__24.svg b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_up__chevron_up__24.svg
new file mode 100644
index 0000000..54263df
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_up__chevron_up__24.svg
@@ -0,0 +1,196 @@
+
+
+
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_user_tabs__mode_user_tabs_inactive__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_user_tabs__mode_user_tabs_inactive__48.png
new file mode 100644
index 0000000..531e086
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_user_tabs__mode_user_tabs_inactive__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_user_tabs_on__mode_user_tabs_active__48.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_user_tabs_on__mode_user_tabs_active__48.png
new file mode 100644
index 0000000..531e086
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_user_tabs_on__mode_user_tabs_active__48.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_p__tool_axis_p__24.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_p__tool_axis_p__24.png
new file mode 100644
index 0000000..5b1a892
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_p__tool_axis_p__24.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_x__tool_axis_x__24.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_x__tool_axis_x__24.png
new file mode 100644
index 0000000..563205b
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_x__tool_axis_x__24.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_y2__tool_axis_y_inv__24.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_y2__tool_axis_y_inv__24.png
new file mode 100644
index 0000000..a211b7e
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_y2__tool_axis_y_inv__24.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_y__tool_axis_y__24.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_y__tool_axis_y__24.png
new file mode 100644
index 0000000..3c885f2
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_y__tool_axis_y__24.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_z__tool_axis_z__24.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_z__tool_axis_z__24.png
new file mode 100644
index 0000000..31a7911
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_view_z__tool_axis_z__24.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_zoom_in__zoom_in__24.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_zoom_in__zoom_in__24.png
new file mode 100644
index 0000000..73083ae
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_zoom_in__zoom_in__24.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_zoom_out__zoom_out__24.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_zoom_out__zoom_out__24.png
new file mode 100644
index 0000000..8f1bcfd
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/img_zoom_out__zoom_out__24.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/macro_0__macro_image.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/macro_0__macro_image.png
new file mode 100644
index 0000000..df4866b
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/macro_0__macro_image.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/macro_4__macro_image.png b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/macro_4__macro_image.png
new file mode 100644
index 0000000..6239dc6
Binary files /dev/null and b/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/macro_4__macro_image.png differ
diff --git a/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js b/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js
new file mode 100644
index 0000000..048b333
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js
@@ -0,0 +1,332 @@
+import { getSourceReferencesForProfile } from "./source-reference-map.js";
+
+const sourceReferenceObjects = getSourceReferencesForProfile("gmoccapy-xyzab");
+
+export const gmoccapyXyzabPanelSchema = {
+ id: "gmoccapy-xyzab-hal-component",
+ profileId: "gmoccapy-xyzab",
+ title: "gmoccapy HAL component",
+ sourceXmlPath: "linuxcnc/share/gmoccapy/gmoccapy.glade",
+ postguiHalPath: "linuxcnc/configs/sim/gmoccapy/gmoccapy_postgui.hal",
+ boundary: "gmoccapy_hal_component_reference_only",
+ promotionAllowed: false,
+ groups: [
+ {
+ id: "task-mode-actions",
+ type: "gmoccapy-button-bank",
+ label: "Task and mode controls",
+ controls: [
+ {
+ id: "tbtn-estop",
+ type: "button",
+ text: "ESTOP",
+ halpin: "gmoccapy.h-button.button-0",
+ halNet: "gmoccapy-h-button-0",
+ target: "gmoccapy.command.STATE_ESTOP",
+ nativeCommand: "STATE_ESTOP / STATE_ESTOP_RESET",
+ webAction: { type: "ESTOP" },
+ },
+ {
+ id: "tbtn-on",
+ type: "button",
+ text: "POWER",
+ halpin: "gmoccapy.h-button.button-1",
+ halNet: "gmoccapy-h-button-1",
+ target: "gmoccapy.command.STATE_ON",
+ nativeCommand: "STATE_ON / STATE_OFF",
+ webAction: { type: "TOGGLE_POWER" },
+ },
+ {
+ id: "rbt-manual",
+ type: "button",
+ text: "MANUAL",
+ halpin: "gmoccapy.v-button.button-0",
+ halNet: "gmoccapy-v-button-0",
+ target: "gmoccapy.command.MODE_MANUAL",
+ nativeCommand: "MODE_MANUAL",
+ webAction: { type: "SET_MODE", mode: "manual" },
+ },
+ {
+ id: "rbt-mdi",
+ type: "button",
+ text: "MDI",
+ halpin: "gmoccapy.v-button.button-1",
+ halNet: "gmoccapy-v-button-1",
+ target: "gmoccapy.command.MODE_MDI",
+ nativeCommand: "MODE_MDI",
+ webAction: { type: "SET_MODE", mode: "mdi" },
+ },
+ {
+ id: "rbt-auto",
+ type: "button",
+ text: "AUTO",
+ halpin: "gmoccapy.v-button.button-2",
+ halNet: "gmoccapy-v-button-2",
+ target: "gmoccapy.command.MODE_AUTO",
+ nativeCommand: "MODE_AUTO",
+ webAction: { type: "SET_MODE", mode: "auto" },
+ },
+ ],
+ },
+ {
+ id: "postgui-display-pins",
+ type: "postgui-hal",
+ label: "POSTGUI_HALFILE pins",
+ controls: [
+ {
+ id: "spindle-feedback-bar",
+ type: "hal-display",
+ text: "spindle feedback",
+ halpin: "gmoccapy.spindle_feedback_bar",
+ halNet: "spindle-abs",
+ target: "gmoccapy.spindle_feedback_bar",
+ },
+ {
+ id: "spindle-at-speed-led",
+ type: "hal-display",
+ text: "spindle at speed",
+ halpin: "gmoccapy.spindle_at_speed_led",
+ halNet: "spindle-at-speed",
+ target: "gmoccapy.spindle_at_speed_led",
+ },
+ {
+ id: "tooloffset-x",
+ type: "hal-display",
+ text: "tooloffset X",
+ halpin: "gmoccapy.tooloffset-x",
+ halNet: "tooloffset-x",
+ target: "gmoccapy.tooloffset-x",
+ },
+ {
+ id: "tooloffset-z",
+ type: "hal-display",
+ text: "tooloffset Z",
+ halpin: "gmoccapy.tooloffset-z",
+ halNet: "tooloffset-z",
+ target: "gmoccapy.tooloffset-z",
+ },
+ ],
+ },
+ ],
+};
+
+export const gmoccapyXyzabProfile = {
+ id: "gmoccapy-xyzab",
+ title: "gmoccapy XYZAB trivkins reference",
+ iniPath: "linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini",
+ pyvcpXmlPath: null,
+ postguiHalPath: "linuxcnc/configs/sim/gmoccapy/gmoccapy_postgui.hal",
+ generatedHalPath: null,
+ toolTablePath: "linuxcnc/configs/sim/gmoccapy/tool.tbl",
+ machineName: "gmoccapy XYZAB",
+ display: {
+ geometry: "XYZAB",
+ display: "gmoccapy",
+ cycleTimeMs: 100,
+ jogAxes: ["X", "Y", "Z", "A", "B"],
+ programPrefix: "../../nc_files/",
+ positionOffset: "RELATIVE",
+ positionFeedback: "ACTUAL",
+ maxFeedOverride: 1.5,
+ maxSpindleOverride: 1.2,
+ minSpindleOverride: 0.5,
+ increments: ["1.000 mm", "0.100 mm", "0.010 mm", "0.001 mm", "1.2345 in"],
+ },
+ rs274ngc: {
+ startupCode: "G17 G21 G40 G43H0 G54 G64P0.005 G80 G90 G94 G97 M5 M9",
+ subroutinePath: "./macros",
+ halPinVars: false,
+ parameterFile: "sim.var",
+ },
+ python: {
+ pathPrepend: "./python",
+ topLevel: "./python/toplevel.py",
+ logLevel: 0,
+ },
+ coordinates: ["X", "Y", "Z", "A", "B"],
+ joints: ["joint.0", "joint.1", "joint.2", "joint.3", "joint.4"],
+ kinematics: "trivkins coordinates=xyzab",
+ kinematicsModuleId: "gmoccapy-xyzab-trivkins-reference",
+ kinematicsParameters: {
+ sparm: "coordinates=xyzab",
+ defaultSwitchkinsType: 0,
+ tcpCapable: false,
+ rtcpProof: false,
+ switchkinsTypes: [
+ { value: 0, label: "XYZAB trivkins", mdiCommand: null, webKinsType: "identity" },
+ ],
+ },
+ remaps: [
+ {
+ code: "M6",
+ modalGroup: 6,
+ prolog: "change_prolog",
+ ngc: "change_g43",
+ epilog: "change_epilog",
+ path: "linuxcnc/configs/sim/gmoccapy/macros/change_g43.ngc",
+ boundary: "linuxcnc_interpreter_remap_reference",
+ },
+ {
+ code: "M61",
+ modalGroup: 6,
+ prolog: "settool_prolog",
+ ngc: "settool_g43",
+ epilog: "settool_epilog",
+ path: "linuxcnc/configs/sim/gmoccapy/macros/settool_g43.ngc",
+ boundary: "linuxcnc_interpreter_remap_reference",
+ },
+ ],
+ hal: {
+ halui: true,
+ halFiles: [
+ "core_sim_XYZAB.hal",
+ "spindle_sim.hal",
+ "simulated_home.hal",
+ ],
+ postguiHalFiles: ["gmoccapy_postgui.hal"],
+ postguiRequiresHalcompReady: true,
+ halcmd: {
+ loadrt: [
+ "trivkins coordinates=xyzab",
+ "motmod num_joints=5 unlock_joints_mask=16",
+ "ddt names=ddt_x,ddt_xv,ddt_y,ddt_yv,ddt_z,ddt_zv",
+ "hypot names=vel_xy,vel_xyz",
+ ],
+ loadusr: ["milltask", "halui", "gmoccapy"],
+ feedbackNets: [
+ { signal: "Xpos", source: "joint.0.motor-pos-cmd", target: "joint.0.motor-pos-fb" },
+ { signal: "Ypos", source: "joint.1.motor-pos-cmd", target: "joint.1.motor-pos-fb" },
+ { signal: "Zpos", source: "joint.2.motor-pos-cmd", target: "joint.2.motor-pos-fb" },
+ { signal: "Apos", source: "joint.3.motor-pos-cmd", target: "joint.3.motor-pos-fb" },
+ { signal: "Bpos", source: "joint.4.motor-pos-cmd", target: "joint.4.motor-pos-fb" },
+ ],
+ offsetNets: [
+ { signal: "tooloffset-x", source: "motion.tooloffset.x", target: "gmoccapy.tooloffset-x" },
+ { signal: "tooloffset-z", source: "motion.tooloffset.z", target: "gmoccapy.tooloffset-z" },
+ ],
+ simulationLoops: [
+ { signal: "estop-loop", source: "iocontrol.0.user-enable-out", target: "iocontrol.0.emc-enable-in" },
+ { signal: "tool-prep-loop", source: "iocontrol.0.tool-prepare", target: "iocontrol.0.tool-prepared" },
+ { signal: "tool-change-loop", source: "iocontrol.0.tool-change", target: "iocontrol.0.tool-changed" },
+ ],
+ spindleFeedback: [
+ { signal: "spindle-speed-cmd", source: "spindle.0.speed-out", target: "limit_speed.in" },
+ { signal: "spindle-speed-limited", source: "limit_speed.out", target: "sim_encoder_0.speed" },
+ { signal: "spindle-at-speed", source: "near_speed.out", target: "spindle.0.at-speed" },
+ { signal: "spindle-abs", source: "abs_spindle_feedback.out", target: "gmoccapy.spindle_feedback_bar" },
+ ],
+ },
+ },
+ nativeRuntime: {
+ linuxcncsvr: true,
+ task: "milltask",
+ emcmot: "motmod",
+ halui: "halui",
+ display: "gmoccapy -ini",
+ displayCycleTimeMs: 100,
+ taskCycleTimeSeconds: 0.001,
+ basePeriodNs: 100000,
+ servoPeriodNs: 1000000,
+ nmlChannels: ["emcCommand", "emcStatus", "emcError"],
+ },
+ homing: {
+ noForceHoming: false,
+ noForceHomingIniValue: 0,
+ homeSequenceRequired: true,
+ simulatedHomeSwitches: [
+ { axis: "X", position: 1 },
+ { axis: "Y", position: 0.5 },
+ { axis: "Z", position: 2 },
+ ],
+ },
+ halui: {
+ mdiCommands: [],
+ },
+ traj: {
+ coordinates: "XYZAB",
+ linearUnits: "mm",
+ angularUnits: "degree",
+ defaultLinearVelocity: 9,
+ maxLinearVelocity: 234,
+ defaultLinearAcceleration: 200,
+ maxLinearAcceleration: 200,
+ noForceHoming: false,
+ },
+ axisLimits: {
+ X: { min: 0, max: 600, maxVelocity: 166, maxAcceleration: 200 },
+ Y: { min: 0, max: 400, maxVelocity: 166, maxAcceleration: 200 },
+ Z: { min: -400, max: 4, maxVelocity: 166, maxAcceleration: 200 },
+ A: { min: -36000, max: 36000, maxVelocity: 90, maxAcceleration: 1200 },
+ B: { min: -36000, max: 36000, maxVelocity: 90, maxAcceleration: 1200 },
+ },
+ jointConfig: [
+ { id: 0, axis: "X", type: "LINEAR", home: 0, homeOffset: 0, homeSequence: 1, min: 0, max: 600, maxVelocity: 166, maxAcceleration: 200 },
+ { id: 1, axis: "Y", type: "LINEAR", home: 0, homeOffset: 0, homeSequence: 1, min: 0, max: 400, maxVelocity: 166, maxAcceleration: 200 },
+ { id: 2, axis: "Z", type: "LINEAR", home: 0, homeOffset: 1, homeSequence: 0, min: -400, max: 4, maxVelocity: 166, maxAcceleration: 200 },
+ { id: 3, axis: "A", type: "ANGULAR", home: 0, homeOffset: 0, homeSequence: 1, min: -36000, max: 36000, maxVelocity: 90, maxAcceleration: 1200 },
+ { id: 4, axis: "B", type: "ANGULAR", home: 0, homeOffset: 45, homeSequence: 1, min: -36000, max: 36000, maxVelocity: 90, maxAcceleration: 1200 },
+ ],
+ halPins: [
+ "gmoccapy.h-button.button-0",
+ "gmoccapy.v-button.button-0",
+ "gmoccapy.jog.axis.jog-x-plus",
+ "gmoccapy.jog.axis.jog-b-minus",
+ "gmoccapy.feed.feed-override.counts",
+ "gmoccapy.spindle.spindle-override.counts",
+ "gmoccapy.rapid.rapid-override.direct-value",
+ "gmoccapy.tooloffset-x",
+ "gmoccapy.tooloffset-z",
+ "gmoccapy.toolchange-change",
+ "gmoccapy.toolchange-changed",
+ "gmoccapy.program.length",
+ "gmoccapy.program.current-line",
+ "gmoccapy.program.progress",
+ "gmoccapy.spindle_feedback_bar",
+ "gmoccapy.spindle_at_speed_led",
+ "gmoccapy.error",
+ ],
+ offsets: {
+ toolOffsetX: 0,
+ toolOffsetZ: 0,
+ },
+ sourceReferences: sourceReferenceObjects.map((reference) => reference.path),
+ sourceReferenceObjects,
+ panelSchema: gmoccapyXyzabPanelSchema,
+ toolTable: {
+ toolCount: 17,
+ tools: [
+ { tool: 1, pocket: 101, zOffset: -12.7129, diameter: 1, comment: "3/4 roughing" },
+ { tool: 2, pocket: 2, zOffset: -0.01, diameter: 2, comment: "3/4 2 flute" },
+ { tool: 3, pocket: 3, zOffset: 33.3877, diameter: 3, comment: "2-1/4 face mill" },
+ { tool: 4, pocket: 4, zOffset: -10.1737, diameter: 4, comment: "3/8 4fl carbide" },
+ { tool: 5, pocket: 5, zOffset: -1.15, diameter: 5, comment: "long ER40 collet chuck" },
+ { tool: 6, pocket: 6, zOffset: -5.1252, diameter: 7, comment: "tool 6" },
+ { tool: 7, pocket: 7, zOffset: -1.07, diameter: 7, comment: "big albrecht drill chuck .25 drill" },
+ { tool: 8, pocket: 8, zOffset: -1.16, diameter: 8, comment: "V engraving tool" },
+ { tool: 9, pocket: 9, zOffset: -1.14, diameter: 9, comment: "90 deg 3/8 mill-drill" },
+ { tool: 10, pocket: 10, zOffset: -1.13, diameter: 10, comment: "other big jacobs chuck" },
+ { tool: 11, pocket: 11, zOffset: -0.09, diameter: 11, comment: "1/8 2fl end mill" },
+ { tool: 12, pocket: 12, zOffset: -1.12, diameter: 12, comment: "1/2 2fl long em" },
+ { tool: 13, pocket: 13, zOffset: -0.02, diameter: 13, comment: "Tool 13" },
+ { tool: 14, pocket: 14, zOffset: -0.04, diameter: 14, comment: "Tool 14" },
+ { tool: 15, pocket: 15, zOffset: -0.1, diameter: 15, comment: "little albrecht chuck spot drill" },
+ { tool: 16, pocket: 16, zOffset: -0.03, diameter: 16, comment: "little jacobs chuck" },
+ { tool: 18, pocket: 1, zOffset: -1.18, diameter: 18, comment: "big jacobs chuck" },
+ ],
+ },
+ macros: [
+ "i_am_lost",
+ "halo_world",
+ "jog_around",
+ "increment xinc yinc",
+ "go_to_position X-pos Y-pos Z-pos",
+ ],
+ samplePrograms: [
+ "linuxcnc/nc_files/3D_Chips.ngc",
+ ],
+ semanticBoundary: "gmoccapy_xyzab_trivkins_reference_only_not_rtcp_proof",
+ linuxCncKinematicsReady: false,
+ tcpCapable: false,
+ rtcpProof: false,
+ promotionAllowed: false,
+};
diff --git a/web-rtcp-5axis-sim-plan/app/src/profiles/index.js b/web-rtcp-5axis-sim-plan/app/src/profiles/index.js
index 2e98e18..565cba3 100644
--- a/web-rtcp-5axis-sim-plan/app/src/profiles/index.js
+++ b/web-rtcp-5axis-sim-plan/app/src/profiles/index.js
@@ -1,7 +1,8 @@
import { xyzacTrtProfile } from "./xyzac-trt.js";
import { xyzbcTrtProfile } from "./xyzbc-trt.js";
+import { gmoccapyXyzabProfile } from "./gmoccapy-xyzab.js";
-export const fiveAxisProfiles = [xyzacTrtProfile, xyzbcTrtProfile];
+export const fiveAxisProfiles = [xyzacTrtProfile, xyzbcTrtProfile, gmoccapyXyzabProfile];
export function getFiveAxisProfile(profileId = "xyzac-trt") {
const profile = fiveAxisProfiles.find((entry) => entry.id === profileId);
diff --git a/web-rtcp-5axis-sim-plan/app/src/profiles/source-reference-map.js b/web-rtcp-5axis-sim-plan/app/src/profiles/source-reference-map.js
index bfceda3..7b89e09 100644
--- a/web-rtcp-5axis-sim-plan/app/src/profiles/source-reference-map.js
+++ b/web-rtcp-5axis-sim-plan/app/src/profiles/source-reference-map.js
@@ -127,6 +127,86 @@ export const linuxCncSourceReferenceMap = [
boundary: "linuxcnc_program_reference",
usage: "representative XYZBC switchkins demonstration program",
},
+ {
+ id: "gmoccapy-xyzab-ini",
+ profileId: "gmoccapy-xyzab",
+ kind: "ini",
+ path: "linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini",
+ boundary: "linuxcnc_gmoccapy_config_reference",
+ usage: "gmoccapy XYZAB DISPLAY/TASK/TRAJ/KINS/HAL reference; trivkins only",
+ },
+ {
+ id: "gmoccapy-xyzab-core-hal",
+ profileId: "gmoccapy-xyzab",
+ kind: "halfile",
+ path: "linuxcnc/configs/sim/gmoccapy/core_sim_XYZAB.hal",
+ boundary: "native_hal_reference",
+ usage: "trivkins/motmod loading, XYZAB command-to-feedback loopback, estop/tool/coolant nets",
+ },
+ {
+ id: "gmoccapy-xyzab-spindle-hal",
+ profileId: "gmoccapy-xyzab",
+ kind: "halfile",
+ path: "linuxcnc/configs/sim/gmoccapy/spindle_sim.hal",
+ boundary: "native_hal_reference",
+ usage: "spindle speed limiting, encoder simulation, speed-in and at-speed feedback",
+ },
+ {
+ id: "gmoccapy-xyzab-home-hal",
+ profileId: "gmoccapy-xyzab",
+ kind: "halfile",
+ path: "linuxcnc/configs/sim/gmoccapy/simulated_home.hal",
+ boundary: "native_hal_reference",
+ usage: "simulated X/Y/Z home switch positions",
+ },
+ {
+ id: "gmoccapy-postgui-hal",
+ profileId: "gmoccapy-xyzab",
+ kind: "postgui_hal",
+ path: "linuxcnc/configs/sim/gmoccapy/gmoccapy_postgui.hal",
+ boundary: "postgui_hal_reference",
+ usage: "gmoccapy.* pins connected after halcomp.ready(); spindle feedback and tooloffset display",
+ },
+ {
+ id: "gmoccapy-trivkins-source",
+ profileId: "gmoccapy-xyzab",
+ kind: "kinematics_source",
+ path: "linuxcnc/src/emc/kinematics/trivkins.c",
+ boundary: "linuxcnc_source_reference_only_not_rtcp_proof",
+ usage: "trivial XYZAB joint/axis mapping source reference",
+ },
+ {
+ id: "gmoccapy-python-gui",
+ profileId: "gmoccapy-xyzab",
+ kind: "python_gui",
+ path: "linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py",
+ boundary: "native_gui_reference_not_browser_runtime",
+ usage: "native command/stat/error_channel and gmoccapy HAL component reference",
+ },
+ {
+ id: "gmoccapy-glade",
+ profileId: "gmoccapy-xyzab",
+ kind: "glade_ui",
+ path: "linuxcnc/share/gmoccapy/gmoccapy.glade",
+ boundary: "ui_reference_not_browser_runtime",
+ usage: "button/control layout and gladevcp action reference",
+ },
+ {
+ id: "gmoccapy-button-icon-manifest",
+ profileId: "gmoccapy-xyzab",
+ kind: "icon_manifest",
+ path: "web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json",
+ boundary: "button_icon_asset_reference_only",
+ usage: "project-local manifest generated from working3 button_icon_inventory.csv",
+ },
+ {
+ id: "gmoccapy-xyzab-reference-doc",
+ profileId: "gmoccapy-xyzab",
+ kind: "documentation",
+ path: "web-rtcp-5axis-sim-plan/docs/gmoccapy-xyzab-reference.md",
+ boundary: "documentation_reference",
+ usage: "summarizes native startup, NML/HAL/postgui, gate, and icon asset boundaries",
+ },
];
export function getSourceReferencesForProfile(profileId) {
diff --git a/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js b/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js
new file mode 100644
index 0000000..a183d76
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js
@@ -0,0 +1,169 @@
+export const gmoccapyCommunicationModel = {
+ apiName: "web-rtcp-5axis-gmoccapy-communication-model",
+ profileId: "gmoccapy-xyzab",
+ nativeConfigPath: "linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini",
+ cycleTimeMs: 100,
+ semanticBoundary: "native_gmoccapy_nml_hal_reference_web_store_runtime_mapping",
+ nativePaths: {
+ command: {
+ label: "NML command",
+ path: ["gmoccapy", "linuxcnc.command()", "NML emcCommand", "milltask"],
+ notes: "GUI commands are written to the NML command buffer and consumed by milltask.",
+ },
+ status: {
+ label: "NML status/error",
+ path: ["milltask", "NML emcStatus/emcError", "linuxcnc.stat()", "linuxcnc.error_channel()", "gmoccapy"],
+ notes: "gmoccapy polls status and error channels at DISPLAY CYCLE_TIME.",
+ },
+ hal: {
+ label: "HAL shared memory",
+ path: ["gmoccapy.* pins", "HAL shared memory", "halui/iocontrol/motmod/spindle_sim"],
+ notes: "postgui HAL connects gmoccapy pins after halcomp.ready().",
+ },
+ remap: {
+ label: "Interpreter REMAP",
+ path: ["milltask", "RS274NGC", "Python remap prolog/ngc/epilog"],
+ notes: "M6/M61 remap executes on the task/interpreter side, not inside the GUI event loop.",
+ },
+ },
+ webPath: {
+ label: "Web simulation",
+ path: ["button", "store.dispatch", "linuxcnc-task-policy", "runtime/status snapshot", "UI/Three.js"],
+ notes: "The browser does not attach to native NML buffers or HAL shared memory.",
+ },
+ startupStages: [
+ { order: 1, id: "parse-ini", nativeStep: "linuxcnc script parses gmoccapy_XYZAB.ini", webEquivalent: "profile/source map selection" },
+ { order: 2, id: "linuxcncsvr", nativeStep: "linuxcncsvr -ini ", webEquivalent: "not connected; reference only" },
+ { order: 3, id: "realtime-hal", nativeStep: "start realtime/RTAPI/HAL", webEquivalent: "task/HAL runtime simulation boundary where available" },
+ { order: 4, id: "milltask", nativeStep: "halcmd loadusr -Wn inihal milltask -ini ", webEquivalent: "store task policy and optional task/HAL runtime" },
+ { order: 5, id: "halui", nativeStep: "halcmd loadusr -Wn halui halui -ini ", webEquivalent: "HALUI pins listed as reference diagnostics" },
+ { order: 6, id: "halfiles", nativeStep: "run core_sim_XYZAB.hal, spindle_sim.hal, simulated_home.hal", webEquivalent: "HAL model diagnostics" },
+ { order: 7, id: "hal-start", nativeStep: "halcmd start", webEquivalent: "not a browser realtime thread" },
+ { order: 8, id: "gmoccapy", nativeStep: "gmoccapy -ini ", webEquivalent: "gmoccapy Web shell" },
+ { order: 9, id: "postgui", nativeStep: "halcomp.ready(), then gmoccapy_postgui.hal", webEquivalent: "postgui model shown after gmoccapy pins exist" },
+ ],
+ actionMappings: [
+ {
+ actionId: "estop",
+ webAction: "ESTOP",
+ nativeCommand: "command.state(STATE_ESTOP)",
+ nativeStatus: "task_state STATE_ESTOP",
+ gate: "always available as abortive safety action",
+ },
+ {
+ actionId: "estop-reset",
+ webAction: "RESET",
+ nativeCommand: "command.state(STATE_ESTOP_RESET)",
+ nativeStatus: "task_state STATE_ESTOP_RESET",
+ gate: "allowed from estop",
+ },
+ {
+ actionId: "power-on",
+ webAction: "TOGGLE_POWER",
+ nativeCommand: "command.state(STATE_ON)",
+ nativeStatus: "task_state STATE_ON",
+ gate: "blocked while STATE_ESTOP",
+ },
+ {
+ actionId: "mode-manual",
+ webAction: "SET_MODE manual",
+ nativeCommand: "command.mode(MODE_MANUAL)",
+ nativeStatus: "task_mode MODE_MANUAL",
+ gate: "blocked when AUTO interpreter is not idle",
+ },
+ {
+ actionId: "mode-mdi",
+ webAction: "SET_MODE mdi",
+ nativeCommand: "command.mode(MODE_MDI)",
+ nativeStatus: "task_mode MODE_MDI",
+ gate: "machine on and homed when NO_FORCE_HOMING=0",
+ },
+ {
+ actionId: "mode-auto",
+ webAction: "SET_MODE auto",
+ nativeCommand: "command.mode(MODE_AUTO)",
+ nativeStatus: "task_mode MODE_AUTO",
+ gate: "machine on and homed when NO_FORCE_HOMING=0",
+ },
+ {
+ actionId: "jog",
+ webAction: "JOG",
+ nativeCommand: "command.jog(JOG_CONTINUOUS/JOG_INCREMENT/JOG_STOP)",
+ nativeStatus: "motion/joint feedback",
+ gate: "machine on, manual mode, not running",
+ },
+ {
+ actionId: "home",
+ webAction: "HOME",
+ nativeCommand: "command.home(-1) or command.home(joint)",
+ nativeStatus: "all-homed",
+ gate: "machine on, manual mode, no conflicting homing command",
+ },
+ {
+ actionId: "mdi",
+ webAction: "RUN_MDI",
+ nativeCommand: "command.mdi(command)",
+ nativeStatus: "interp reading/idle",
+ gate: "machine on, MDI mode, homed when NO_FORCE_HOMING=0",
+ },
+ {
+ actionId: "auto-run",
+ webAction: "RUN",
+ nativeCommand: "command.auto(AUTO_RUN, start_line)",
+ nativeStatus: "interp reading/waiting/idle",
+ gate: "INI loaded, program loaded, machine on, homed, AUTO, interpreter idle, runtime ready",
+ },
+ {
+ actionId: "auto-pause",
+ webAction: "PAUSE",
+ nativeCommand: "command.auto(AUTO_PAUSE)",
+ nativeStatus: "interp paused",
+ gate: "AUTO or MDI with machine on",
+ },
+ {
+ actionId: "auto-resume",
+ webAction: "RESUME",
+ nativeCommand: "command.auto(AUTO_RESUME)",
+ nativeStatus: "interp reading",
+ gate: "interpreter paused",
+ },
+ {
+ actionId: "abort",
+ webAction: "STOP/ABORT",
+ nativeCommand: "command.abort()",
+ nativeStatus: "interp idle after abort",
+ gate: "available across most states",
+ },
+ {
+ actionId: "spindle",
+ webAction: "spindle controls",
+ nativeCommand: "command.spindle(direction, rpm)",
+ nativeStatus: "spindle.0.forward/reverse/speed-out/speed-in",
+ gate: "blocked in estop/off and restricted during interpreter reading/waiting",
+ },
+ {
+ actionId: "coolant",
+ webAction: "TOGGLE_COOLANT",
+ nativeCommand: "command.flood()/command.mist()",
+ nativeStatus: "iocontrol.0.coolant-flood/mist",
+ gate: "blocked in estop/off",
+ },
+ ],
+};
+
+export function createGmoccapyCommunicationSummary(model = gmoccapyCommunicationModel) {
+ return {
+ apiName: `${model.apiName}-summary`,
+ profileId: model.profileId,
+ nativeConfigPath: model.nativeConfigPath,
+ nativePathCount: Object.keys(model.nativePaths).length,
+ startupStageCount: model.startupStages.length,
+ actionCount: model.actionMappings.length,
+ postguiAfterHalcompReady: model.startupStages.findIndex((stage) => stage.id === "postgui") >
+ model.startupStages.findIndex((stage) => stage.id === "gmoccapy"),
+ hasNativeNmlCommandPath: model.nativePaths.command.path.includes("NML emcCommand"),
+ hasNativeHalPath: model.nativePaths.hal.path.includes("HAL shared memory"),
+ hasWebStorePath: model.webPath.path.includes("store.dispatch"),
+ semanticBoundary: model.semanticBoundary,
+ };
+}
diff --git a/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js b/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js
new file mode 100644
index 0000000..7136eef
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js
@@ -0,0 +1,280 @@
+export const gmoccapyHalModel = {
+ apiName: "web-rtcp-5axis-gmoccapy-hal-model",
+ profileId: "gmoccapy-xyzab",
+ nativeConfigPath: "linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini",
+ halFiles: [
+ {
+ path: "linuxcnc/configs/sim/gmoccapy/core_sim_XYZAB.hal",
+ stage: "HALFILE",
+ role: "trivkins and motmod simulation loopback",
+ semanticBoundary: "native_hal_reference",
+ },
+ {
+ path: "linuxcnc/configs/sim/gmoccapy/spindle_sim.hal",
+ stage: "HALFILE",
+ role: "spindle speed encoder and at-speed simulation",
+ semanticBoundary: "native_hal_reference",
+ },
+ {
+ path: "linuxcnc/configs/sim/gmoccapy/simulated_home.hal",
+ stage: "HALFILE",
+ role: "simulated X/Y/Z home switches",
+ semanticBoundary: "native_hal_reference",
+ },
+ {
+ path: "linuxcnc/configs/sim/gmoccapy/gmoccapy_postgui.hal",
+ stage: "POSTGUI_HALFILE",
+ role: "connections that require gmoccapy.* pins",
+ requiresHalcompReady: true,
+ semanticBoundary: "native_postgui_hal_reference",
+ },
+ ],
+ nativePins: [
+ {
+ group: "hard-buttons",
+ pins: [
+ "gmoccapy.h-button.button-0",
+ "gmoccapy.h-button.button-1",
+ "gmoccapy.h-button.button-2",
+ "gmoccapy.h-button.button-3",
+ "gmoccapy.h-button.button-4",
+ "gmoccapy.h-button.button-5",
+ "gmoccapy.h-button.button-6",
+ "gmoccapy.h-button.button-7",
+ "gmoccapy.h-button.button-8",
+ "gmoccapy.h-button.button-9",
+ "gmoccapy.v-button.button-0",
+ "gmoccapy.v-button.button-1",
+ "gmoccapy.v-button.button-2",
+ "gmoccapy.v-button.button-3",
+ "gmoccapy.v-button.button-4",
+ "gmoccapy.v-button.button-5",
+ "gmoccapy.v-button.button-6",
+ ],
+ webSource: "current visible Web button set",
+ semanticBoundary: "native_hardware_button_pin_reference",
+ },
+ {
+ group: "jog",
+ pins: [
+ "gmoccapy.jog.axis.jog-x-plus",
+ "gmoccapy.jog.axis.jog-x-minus",
+ "gmoccapy.jog.axis.jog-y-plus",
+ "gmoccapy.jog.axis.jog-y-minus",
+ "gmoccapy.jog.axis.jog-z-plus",
+ "gmoccapy.jog.axis.jog-z-minus",
+ "gmoccapy.jog.axis.jog-a-plus",
+ "gmoccapy.jog.axis.jog-a-minus",
+ "gmoccapy.jog.axis.jog-b-plus",
+ "gmoccapy.jog.axis.jog-b-minus",
+ "gmoccapy.jog.jog-inc-0",
+ "gmoccapy.jog.jog-increment",
+ ],
+ webSource: "JOG dispatch and machine.jogIncrement",
+ semanticBoundary: "native_jog_pin_reference_web_task_policy_gate",
+ },
+ {
+ group: "override",
+ pins: [
+ "gmoccapy.feed.feed-override.counts",
+ "gmoccapy.feed.feed-override.direct-value",
+ "gmoccapy.spindle.spindle-override.counts",
+ "gmoccapy.spindle.spindle-override.direct-value",
+ "gmoccapy.jog.jog-velocity.counts",
+ "gmoccapy.jog.jog-velocity.direct-value",
+ "gmoccapy.rapid.rapid-override.counts",
+ "gmoccapy.rapid.rapid-override.direct-value",
+ ],
+ webSource: "feed/spindle override state",
+ semanticBoundary: "native_override_pin_reference_web_state_mapping",
+ },
+ {
+ group: "tool",
+ pins: [
+ "gmoccapy.tooloffset-x",
+ "gmoccapy.tooloffset-z",
+ "gmoccapy.tool-diameter",
+ "gmoccapy.toolchange-change",
+ "gmoccapy.toolchange-changed",
+ "gmoccapy.toolchange-number",
+ ],
+ webSource: "toolPreview and tool-change diagnostics",
+ semanticBoundary: "native_tool_pin_reference_simulated_tool_loop",
+ },
+ {
+ group: "program",
+ pins: [
+ "gmoccapy.program.length",
+ "gmoccapy.program.current-line",
+ "gmoccapy.program.progress",
+ ],
+ webSource: "programLines, activeLine, programRuntimeFeedback",
+ semanticBoundary: "native_program_pin_reference_web_execution_status",
+ },
+ {
+ group: "message",
+ pins: [
+ "gmoccapy.error",
+ "gmoccapy.delete-message",
+ "gmoccapy.warning-confirm",
+ ],
+ webSource: "operatorMessage and browser diagnostics",
+ semanticBoundary: "native_message_pin_reference_web_operator_message",
+ },
+ {
+ group: "spindle-postgui",
+ pins: [
+ "gmoccapy.spindle_feedback_bar",
+ "gmoccapy.spindle_at_speed_led",
+ ],
+ webSource: "spindle rpm/status panel",
+ semanticBoundary: "postgui_spindle_feedback_reference",
+ },
+ ],
+ coreNets: [
+ {
+ signal: "Xpos",
+ source: "joint.0.motor-pos-cmd",
+ target: "joint.0.motor-pos-fb",
+ role: "X command-to-feedback simulation loop",
+ },
+ {
+ signal: "Ypos",
+ source: "joint.1.motor-pos-cmd",
+ target: "joint.1.motor-pos-fb",
+ role: "Y command-to-feedback simulation loop",
+ },
+ {
+ signal: "Zpos",
+ source: "joint.2.motor-pos-cmd",
+ target: "joint.2.motor-pos-fb",
+ role: "Z command-to-feedback simulation loop",
+ },
+ {
+ signal: "Apos",
+ source: "joint.3.motor-pos-cmd",
+ target: "joint.3.motor-pos-fb",
+ role: "A command-to-feedback simulation loop",
+ },
+ {
+ signal: "Bpos",
+ source: "joint.4.motor-pos-cmd",
+ target: "joint.4.motor-pos-fb",
+ role: "B command-to-feedback simulation loop",
+ },
+ {
+ signal: "estop-loop",
+ source: "iocontrol.0.user-enable-out",
+ target: "iocontrol.0.emc-enable-in",
+ role: "simulated estop enable loop",
+ },
+ {
+ signal: "tool-prep-loop",
+ source: "iocontrol.0.tool-prepare",
+ target: "iocontrol.0.tool-prepared",
+ role: "simulated tool prepare completion",
+ },
+ {
+ signal: "tool-change-loop",
+ source: "iocontrol.0.tool-change",
+ target: "iocontrol.0.tool-changed",
+ role: "simulated automatic tool-change completion",
+ },
+ {
+ signal: "flood",
+ source: "iocontrol.0.coolant-flood",
+ target: "gmoccapy coolant display via status",
+ role: "coolant flood state",
+ },
+ {
+ signal: "mist",
+ source: "iocontrol.0.coolant-mist",
+ target: "gmoccapy coolant display via status",
+ role: "coolant mist state",
+ },
+ ],
+ spindleNets: [
+ {
+ signal: "spindle-speed-cmd",
+ source: "spindle.0.speed-out",
+ target: "limit_speed.in",
+ role: "commanded spindle speed",
+ },
+ {
+ signal: "spindle-speed-limited",
+ source: "limit_speed.out",
+ target: "sim_encoder_0.speed",
+ role: "simulated spindle inertia",
+ },
+ {
+ signal: "spindle-pos",
+ source: "encoder_0.position",
+ target: "spindle.0.revs",
+ role: "encoder feedback position",
+ },
+ {
+ signal: "spindle-at-speed",
+ source: "near_speed.out",
+ target: "spindle.0.at-speed",
+ role: "at-speed comparator result",
+ },
+ ],
+ postguiNets: [
+ {
+ signal: "spindle-abs",
+ source: "abs_spindle_feedback.out",
+ target: "gmoccapy.spindle_feedback_bar",
+ requiresHalcompReady: true,
+ role: "spindle feedback bar",
+ },
+ {
+ signal: "spindle-at-speed",
+ source: "spindle.0.at-speed",
+ target: "gmoccapy.spindle_at_speed_led",
+ requiresHalcompReady: true,
+ role: "spindle at-speed LED",
+ },
+ {
+ signal: "tooloffset-x",
+ source: "motion.tooloffset.x",
+ target: "gmoccapy.tooloffset-x",
+ requiresHalcompReady: true,
+ role: "tool X offset display",
+ },
+ {
+ signal: "tooloffset-z",
+ source: "motion.tooloffset.z",
+ target: "gmoccapy.tooloffset-z",
+ requiresHalcompReady: true,
+ role: "tool Z offset display",
+ },
+ {
+ signal: "tool-change-loop",
+ source: "iocontrol.0.tool-change",
+ target: "iocontrol.0.tool-changed",
+ requiresHalcompReady: true,
+ simulationLoop: true,
+ role: "automatic simulated tool-change completion",
+ },
+ ],
+ semanticBoundary: "gmoccapy_hal_pin_postgui_reference_web_diagnostic_only",
+};
+
+export function createGmoccapyHalSummary(model = gmoccapyHalModel) {
+ const pinCount = model.nativePins.reduce((total, group) => total + group.pins.length, 0);
+ const postguiFile = model.halFiles.find((file) => file.stage === "POSTGUI_HALFILE");
+ return {
+ apiName: `${model.apiName}-summary`,
+ profileId: model.profileId,
+ halFileCount: model.halFiles.length,
+ nativePinGroupCount: model.nativePins.length,
+ nativePinCount: pinCount,
+ coreNetCount: model.coreNets.length,
+ spindleNetCount: model.spindleNets.length,
+ postguiNetCount: model.postguiNets.length,
+ postguiRequiresHalcompReady: Boolean(postguiFile?.requiresHalcompReady) &&
+ model.postguiNets.every((net) => net.requiresHalcompReady),
+ hasToolChangeSimulationLoop: model.postguiNets.some((net) => net.simulationLoop),
+ semanticBoundary: model.semanticBoundary,
+ };
+}
diff --git a/web-rtcp-5axis-sim-plan/app/src/runtime/rtcp-frame.js b/web-rtcp-5axis-sim-plan/app/src/runtime/rtcp-frame.js
index 83c1189..653651b 100644
--- a/web-rtcp-5axis-sim-plan/app/src/runtime/rtcp-frame.js
+++ b/web-rtcp-5axis-sim-plan/app/src/runtime/rtcp-frame.js
@@ -54,7 +54,7 @@ export function buildRtcpFrame({
rtcpEnabled,
rtcpState: rtcpEnabled ? "on" : "off",
axisPose: pose,
- jointPose: buildJointPose(pose),
+ jointPose: buildJointPose(pose, profile),
tcpPose,
toolAxisVector,
compensation,
@@ -140,18 +140,16 @@ function normalizeAxisPose(axisPose) {
};
}
-function buildJointPose(pose) {
- return [
- { joint: 0, axis: "X", value: pose.x },
- { joint: 1, axis: "Y", value: pose.y },
- { joint: 2, axis: "Z", value: pose.z },
- { joint: 3, axis: "A", value: pose.a },
- { joint: 4, axis: "C", value: pose.c },
- ];
+function buildJointPose(pose, profile = xyzacTrtProfile) {
+ return axesForProfile(profile).map((axis, joint) => ({
+ joint,
+ axis,
+ value: pose[axis.toLowerCase()] ?? 0,
+ }));
}
function buildJointPoseFromLinuxCncJoints(joints, fallbackPose, profile = xyzacTrtProfile) {
- const axes = profile?.traj?.coordinates === "XYZBC" ? ["X", "Y", "Z", "B", "C"] : ["X", "Y", "Z", "A", "C"];
+ const axes = axesForProfile(profile);
const fallbackValues = axes.map((axis) => fallbackPose[axis.toLowerCase()] ?? 0);
return axes.map((axis, joint) => ({
joint,
@@ -160,6 +158,13 @@ function buildJointPoseFromLinuxCncJoints(joints, fallbackPose, profile = xyzacT
}));
}
+function axesForProfile(profile = xyzacTrtProfile) {
+ const coordinates = profile?.coordinates?.length
+ ? profile.coordinates
+ : String(profile?.traj?.coordinates || "XYZAC").split("");
+ return coordinates.map((axis) => String(axis).toUpperCase()).filter(Boolean);
+}
+
function normalizeLinuxCncPose(pose = {}) {
return {
x: Number(pose.x ?? pose.tran?.x ?? 0),
diff --git a/web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js b/web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js
index 617020d..03d40d8 100644
--- a/web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js
+++ b/web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js
@@ -56,24 +56,55 @@ export function createLinuxCncTaskPolicyStatus(state) {
const interpState = normalizeLinuxCncInterpState(state.machine, state.runState);
const allHomed = Boolean(state.machine.allHomed);
const noForceHoming = Boolean(state.machine.noForceHoming);
+ const machineFileStaging = state.machineFileStaging || {};
+ const profileId = state.profile?.id || state.machineProfile || null;
+ const taskHalReadiness = state.taskHalRuntimeReadiness || {};
+ const taskHalRuntimeReady = Boolean(
+ state.taskHalRuntime?.loaded &&
+ taskHalReadiness.taskRuntimeReady === true &&
+ taskHalReadiness.motionRuntimeReady === true &&
+ taskHalReadiness.halRuntimeReady === true,
+ );
+ const iniLoaded = state.iniConfigReadiness?.loaded === true && state.iniConfigReadiness?.ready === true;
+ const machineFileStaged = machineFileStaging.status === "staged" && Boolean(machineFileStaging.save?.files?.length);
+ const machineFileOpened = profileId === "gmoccapy-xyzab"
+ ? Boolean(machineFileStaging.selectedGcodeSourceRel)
+ : Boolean(machineFileStaging.selectedGcodeSourceRel || state.activeProgram);
+ const interpIdle = interpState === "idle";
+ const running = taskMode === "auto" &&
+ (state.runState === "running" || interpState === "reading" || interpState === "waiting");
+ const referenceOnly = state.profile?.tcpCapable === false || state.profile?.promotionAllowed === false;
return {
...LINUXCNC_TASK_POLICY,
taskState,
taskMode,
interpState,
+ runState: state.runState || "idle",
allHomed,
noForceHoming,
+ iniLoaded,
+ machineFileStaged,
+ machineFileOpened,
+ taskHalRuntimeReady,
+ interpIdle,
+ referenceOnly,
+ profileId,
powerOn: taskState === "on",
estopActive: taskState === "estop",
canMove: taskState === "on",
- canJog: taskState === "on" && taskMode === "manual",
- canHome: taskState === "on" && taskMode === "manual",
+ canJog: taskState === "on" && taskMode === "manual" && !running,
+ canHome: taskState === "on" && taskMode === "manual" && !running,
canRunAuto: taskState === "on" && taskMode === "auto" && (allHomed || noForceHoming),
+ canRunAutoStrict: taskState === "on" && taskMode === "auto" && (allHomed || noForceHoming) &&
+ interpIdle && iniLoaded && machineFileStaged && machineFileOpened && taskHalRuntimeReady,
canExecuteMdi: taskState === "on" && taskMode === "mdi" && (allHomed || noForceHoming),
canPause: taskState === "on" && (taskMode === "auto" || taskMode === "mdi"),
canResume: taskState === "on" && (taskMode === "auto" || taskMode === "mdi") && interpState === "paused",
canAbort: true,
+ canSpindle: taskState === "on",
+ canCoolant: taskState === "on",
+ canOverride: taskState === "on",
canLeaveAuto: taskMode !== "auto" || interpState === "idle",
};
}
@@ -94,15 +125,26 @@ export function gateLinuxCncTaskAction(state, action) {
case "JOG":
if (status.taskState !== "on") return block(status, "jog blocked: machine must be on");
if (status.taskMode !== "manual") return block(status, "jog blocked: switch to manual mode first");
+ if (status.taskMode === "auto" &&
+ (status.runState === "running" || status.interpState === "reading" || status.interpState === "waiting")) {
+ return block(status, "jog blocked: interpreter must be idle");
+ }
return allow(status);
case "HOME":
if (status.taskState !== "on") return block(status, "home blocked: machine must be on");
if (status.taskMode !== "manual") return block(status, "home blocked: switch to manual mode first");
+ if (status.taskMode === "auto" &&
+ (status.runState === "running" || status.interpState === "reading" || status.interpState === "waiting")) {
+ return block(status, "home blocked: interpreter must be idle");
+ }
return allow(status);
case "RUN_MDI":
if (status.taskState !== "on") return block(status, "MDI blocked: machine must be on");
if (status.taskMode !== "mdi") return block(status, "MDI blocked: switch to MDI mode first");
if (!status.allHomed && !status.noForceHoming) return block(status, "MDI blocked: home machine first");
+ if (status.interpState === "reading" || status.interpState === "waiting") {
+ return block(status, "MDI blocked: interpreter must be idle");
+ }
return allow(status);
case "RUN":
case "STEP":
@@ -111,6 +153,10 @@ export function gateLinuxCncTaskAction(state, action) {
if (status.taskMode !== "auto") return block(status, `${type.toLowerCase()} blocked: switch to auto mode first`);
if (!status.allHomed && !status.noForceHoming) return block(status, `${type.toLowerCase()} blocked: home machine first`);
if (type === "RUN" && status.interpState === "paused") return block(status, "run blocked: resume paused program first");
+ if (type === "RUN" && status.profileId === "gmoccapy-xyzab") {
+ const strictGate = gateGmoccapyXyzabRun(status);
+ if (!strictGate.allowed) return strictGate;
+ }
return allow(status);
case "PAUSE":
if (status.taskState !== "on") return block(status, "pause blocked: machine must be on");
@@ -128,11 +174,42 @@ export function gateLinuxCncTaskAction(state, action) {
case "STOP":
case "ABORT":
return allow(status);
+ case "SET_SPINDLE_DIRECTION":
+ case "SPINDLE":
+ return gateSpindle(status, typeof action === "object" ? action.direction : undefined);
+ case "TOGGLE_COOLANT":
+ case "COOLANT":
+ if (status.taskState !== "on") return block(status, "coolant blocked: machine must be on");
+ return allow(status);
+ case "ADJUST_OVERRIDE":
+ case "ADJUST_SPINDLE_OVERRIDE":
+ case "OVERRIDE":
+ if (status.taskState !== "on") return block(status, "override blocked: machine must be on");
+ return allow(status);
default:
return allow(status);
}
}
+function gateGmoccapyXyzabRun(status) {
+ if (!status.iniLoaded) return block(status, "run blocked: LinuxCNC INI not loaded");
+ if (!status.machineFileStaged) return block(status, "run blocked: LinuxCNC machine files not staged");
+ if (!status.machineFileOpened) return block(status, "run blocked: no machine-file G-code opened for task/HAL session");
+ if (!status.interpIdle) return block(status, "run blocked: interpreter must be idle");
+ if (!status.taskHalRuntimeReady) return block(status, "run blocked: task/HAL runtime not ready");
+ return allow(status);
+}
+
+function gateSpindle(status, direction = "stop") {
+ if (status.taskState !== "on") return block(status, "spindle blocked: machine must be on");
+ const requested = String(direction || "stop").toLowerCase();
+ if ((requested === "forward" || requested === "reverse") &&
+ (status.interpState === "reading" || status.interpState === "waiting")) {
+ return block(status, "spindle blocked: interpreter is running");
+ }
+ return allow(status);
+}
+
function gateMode(status, requestedMode) {
const targetMode = normalizeLinuxCncTaskMode(requestedMode);
if (!LINUXCNC_TASK_MODES.has(targetMode)) {
diff --git a/web-rtcp-5axis-sim-plan/app/src/state/store.js b/web-rtcp-5axis-sim-plan/app/src/state/store.js
index 4ac057b..6b62ded 100644
--- a/web-rtcp-5axis-sim-plan/app/src/state/store.js
+++ b/web-rtcp-5axis-sim-plan/app/src/state/store.js
@@ -224,6 +224,7 @@ const initialState = {
rpm: 1600,
override: 100,
enabled: true,
+ direction: "forward",
},
coolant: {
flood: true,
@@ -985,7 +986,7 @@ export function createSimulationStore(seed = {}) {
runState: turningOff ? "powered-off" : "idle",
feed: turningOff ? { ...state.feed, currentVelocity: 0 } : state.feed,
coolant: turningOff ? { ...state.coolant, flood: false, mist: false } : state.coolant,
- spindle: turningOff ? { ...state.spindle, enabled: false } : state.spindle,
+ spindle: turningOff ? { ...state.spindle, enabled: false, direction: "stop" } : state.spindle,
operatorMessage: turningOff ? "task/HAL machine power off" : "task/HAL machine power on",
});
runTaskHalCommandSequence([
@@ -1006,7 +1007,7 @@ export function createSimulationStore(seed = {}) {
runState: turningOff ? "powered-off" : "idle",
feed: turningOff ? { ...state.feed, currentVelocity: 0 } : state.feed,
coolant: turningOff ? { ...state.coolant, flood: false, mist: false } : state.coolant,
- spindle: turningOff ? { ...state.spindle, enabled: false } : state.spindle,
+ spindle: turningOff ? { ...state.spindle, enabled: false, direction: "stop" } : state.spindle,
operatorMessage: turningOff ? "machine power off" : "machine power on",
});
}
@@ -1035,6 +1036,7 @@ export function createSimulationStore(seed = {}) {
spindle: {
...state.spindle,
enabled: false,
+ direction: "stop",
},
operatorMessage: "emergency stop active",
});
@@ -1060,6 +1062,7 @@ export function createSimulationStore(seed = {}) {
spindle: {
...state.spindle,
enabled: false,
+ direction: "stop",
},
operatorMessage: "estop reset; machine off",
});
@@ -1285,6 +1288,24 @@ export function createSimulationStore(seed = {}) {
});
}
break;
+ case "SET_SPINDLE_DIRECTION":
+ {
+ const gate = gateLinuxCncTaskAction(state, action);
+ if (!gate.allowed) {
+ setState({ operatorMessage: gate.operatorMessage });
+ break;
+ }
+ const direction = normalizeSpindleDirection(action.direction);
+ setState({
+ spindle: {
+ ...state.spindle,
+ enabled: direction !== "stop",
+ direction,
+ },
+ operatorMessage: direction === "stop" ? "spindle stopped" : `spindle ${direction}`,
+ });
+ }
+ break;
case "PAUSE":
{
const gate = gateLinuxCncTaskAction(state, action);
@@ -1486,9 +1507,17 @@ export function createSimulationStore(seed = {}) {
});
break;
case "SET_RTCP":
+ if (action.enabled && !profileSupportsTcp(state.profile)) {
+ setState({
+ kinsType: "identity",
+ rtcpState: "off",
+ operatorMessage: `RTCP blocked: ${state.profile.id} is ${state.profile.kinematics} reference only`,
+ });
+ break;
+ }
setState({
kinsType: action.enabled
- ? (state.profile.kinematicsParameters.switchkinsTypes.find((type) => type.value === 1)?.webKinsType || "tcp-xyzac")
+ ? tcpKinsTypeForProfile(state.profile)
: "identity",
rtcpState: action.enabled ? "on" : "off",
});
@@ -1526,6 +1555,14 @@ export function createSimulationStore(seed = {}) {
});
break;
case "SET_KINS_TYPE":
+ if (String(action.kinsType || "").startsWith("tcp-") && !profileSupportsTcp(state.profile)) {
+ setState({
+ kinsType: "identity",
+ rtcpState: "off",
+ operatorMessage: `kinematics ${action.kinsType} blocked: ${state.profile.id} is not TCP capable`,
+ });
+ break;
+ }
setState({
kinsType: action.kinsType,
rtcpState: action.kinsType.startsWith("tcp-") ? "on" : "off",
@@ -1533,6 +1570,12 @@ export function createSimulationStore(seed = {}) {
});
break;
case "ADJUST_OVERRIDE":
+ {
+ const gate = gateLinuxCncTaskAction(state, action);
+ if (!gate.allowed) {
+ setState({ operatorMessage: gate.operatorMessage });
+ break;
+ }
setState({
feed: {
...state.feed,
@@ -1544,8 +1587,15 @@ export function createSimulationStore(seed = {}) {
},
operatorMessage: `${action.target} override adjusted`,
});
+ }
break;
case "ADJUST_SPINDLE_OVERRIDE":
+ {
+ const gate = gateLinuxCncTaskAction(state, action);
+ if (!gate.allowed) {
+ setState({ operatorMessage: gate.operatorMessage });
+ break;
+ }
setState({
spindle: {
...state.spindle,
@@ -1553,8 +1603,15 @@ export function createSimulationStore(seed = {}) {
},
operatorMessage: "spindle override adjusted",
});
+ }
break;
case "TOGGLE_COOLANT":
+ {
+ const gate = gateLinuxCncTaskAction(state, action);
+ if (!gate.allowed) {
+ setState({ operatorMessage: gate.operatorMessage });
+ break;
+ }
setState({
coolant: {
...state.coolant,
@@ -1562,6 +1619,7 @@ export function createSimulationStore(seed = {}) {
},
operatorMessage: `${action.kind} coolant toggled`,
});
+ }
break;
case "RELOAD_PROGRAM":
setState({
@@ -1875,11 +1933,13 @@ export function createSimulationStore(seed = {}) {
allHomed: true,
},
});
- const tcpKinsType = state.profile.kinematicsParameters.switchkinsTypes.find((type) => type.value === 1)?.webKinsType || "tcp-xyzac";
- dispatch({ type: "SET_KINS_TYPE", kinsType: tcpKinsType });
+ const tcpKinsType = tcpKinsTypeForProfile(state.profile);
+ if (tcpKinsType) {
+ dispatch({ type: "SET_KINS_TYPE", kinsType: tcpKinsType });
+ }
return state;
}
- const tcpKinsType = state.profile.kinematicsParameters.switchkinsTypes.find((type) => type.value === 1)?.webKinsType || "tcp-xyzac";
+ const tcpKinsType = tcpKinsTypeForProfile(state.profile);
setState({
machine: {
...state.machine,
@@ -1893,9 +1953,11 @@ export function createSimulationStore(seed = {}) {
taskPaused: false,
},
runState: "idle",
- kinsType: tcpKinsType,
- rtcpState: "on",
- operatorMessage: "RUN ready: power on, homed, auto mode",
+ kinsType: tcpKinsType || "identity",
+ rtcpState: tcpKinsType ? "on" : "off",
+ operatorMessage: tcpKinsType
+ ? "RUN ready: power on, homed, auto mode"
+ : "RUN ready: power on, homed, auto mode; TCP unavailable for this reference profile",
});
return state;
};
@@ -2737,7 +2799,17 @@ function createIniConfigReadiness(iniConfig) {
function normalizeKinsTypeForProfile(kinsType, profile) {
if (kinsType !== "tcp-xyzac" && kinsType !== "tcp-xyzbc") return kinsType;
- return profile.kinematicsParameters.switchkinsTypes.find((type) => type.value === 1)?.webKinsType || kinsType;
+ return tcpKinsTypeForProfile(profile) || "identity";
+}
+
+function profileSupportsTcp(profile) {
+ return Boolean(profile?.tcpCapable !== false && tcpKinsTypeForProfile(profile));
+}
+
+function tcpKinsTypeForProfile(profile) {
+ return profile?.kinematicsParameters?.switchkinsTypes
+ ?.find((type) => type.value === 1 && String(type.webKinsType || "").startsWith("tcp-"))
+ ?.webKinsType || null;
}
function clampAxisPoseToProfile(axisPose, profile = defaultProfile) {
@@ -2821,6 +2893,7 @@ function executeMdiCommand(state, rawCommand) {
...state.spindle,
rpm: parsed.spindleRpm ?? state.spindle.rpm,
enabled: parsed.spindleEnabled ?? state.spindle.enabled,
+ direction: parsed.spindleDirection ?? state.spindle.direction,
}
: state.spindle,
coolant: parsed.coolantPatch
@@ -2850,6 +2923,7 @@ function parseMdiCommand(command) {
feedRate: null,
spindleRpm: null,
spindleEnabled: null,
+ spindleDirection: null,
coolantPatch: null,
distanceMode: null,
motionCode: null,
@@ -2888,8 +2962,10 @@ function parseMdiCommand(command) {
function applyMdiMCode(parsed, value) {
if (value === 3 || value === 4) {
parsed.spindleEnabled = true;
+ parsed.spindleDirection = value === 4 ? "reverse" : "forward";
} else if (value === 5) {
parsed.spindleEnabled = false;
+ parsed.spindleDirection = "stop";
} else if (value === 7) {
parsed.coolantPatch = { ...(parsed.coolantPatch || {}), mist: true };
} else if (value === 8) {
@@ -2905,9 +2981,14 @@ function applyMdiMCode(parsed, value) {
}
}
+function normalizeSpindleDirection(direction) {
+ const value = String(direction || "stop").toLowerCase();
+ return value === "forward" || value === "reverse" ? value : "stop";
+}
+
function resolveMdiKinsType(state, kinsType) {
if (kinsType !== "tcp") return kinsType;
- return state.profile.kinematicsParameters.switchkinsTypes.find((type) => type.value === 1)?.webKinsType || "tcp-xyzac";
+ return tcpKinsTypeForProfile(state.profile);
}
function hasMdiAxisWords(parsed) {
diff --git a/web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css b/web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
index 7af2f0a..902ac1c 100644
--- a/web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
+++ b/web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
@@ -49,6 +49,45 @@ button:active {
transform: translateY(1px);
}
+button[data-icon-name] {
+ display: grid;
+ grid-template-rows: minmax(18px, 1fr) auto;
+ place-items: center;
+ gap: 2px;
+ min-width: 0;
+ overflow: hidden;
+}
+
+button[data-icon-name] img,
+.gmoccapy-icon-fallback {
+ display: block;
+ width: min(26px, 78%);
+ height: min(26px, 78%);
+ object-fit: contain;
+ pointer-events: none;
+}
+
+button[data-icon-name] span {
+ max-width: 100%;
+ min-width: 0;
+ overflow: hidden;
+ font-size: inherit;
+ line-height: 1;
+ text-align: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.gmoccapy-icon-fallback {
+ display: grid;
+ place-items: center;
+ border: 1px solid #938d84;
+ background: #f5f2ea;
+ color: #38332e;
+ font-size: 9px;
+ font-weight: 700;
+}
+
.profile-select-label {
display: grid;
gap: 1px;
@@ -282,6 +321,11 @@ button:active {
font-weight: 700;
}
+.preview-toolbar button[data-icon-name] img {
+ width: min(24px, 80%);
+ height: min(24px, 80%);
+}
+
.dro-panel {
grid-area: dro;
display: grid;
@@ -601,6 +645,11 @@ button:active {
line-height: 1.1;
}
+.sidebar-button[data-icon-name] img {
+ width: min(34px, 82%);
+ height: min(34px, 82%);
+}
+
.sidebar-button.estop {
color: #c30000;
}
@@ -809,6 +858,27 @@ button:active {
border: 1px solid #898989;
}
+.spindle-direction {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 3px;
+ min-width: 0;
+}
+
+.spindle-direction button,
+.cooling button {
+ min-width: 0;
+ min-height: 38px;
+ padding: 2px;
+ font-size: 10px;
+}
+
+.spindle-direction button[data-active="true"],
+.cooling button[data-active="true"] {
+ border-color: #278b37;
+ background: #39ef37;
+}
+
.spindle-range i {
display: block;
height: 100%;
@@ -834,6 +904,11 @@ button:active {
white-space: normal;
}
+.bottom-controls button[data-icon-name] img {
+ width: min(24px, 82%);
+ height: min(24px, 82%);
+}
+
.program-file-input {
display: none;
}
@@ -1065,4 +1140,18 @@ button:active {
min-height: 18px;
padding: 1px 2px;
}
+
+ button[data-icon-name] {
+ grid-template-rows: 1fr;
+ }
+
+ button[data-icon-name] img,
+ .gmoccapy-icon-fallback {
+ width: 12px;
+ height: 12px;
+ }
+
+ button[data-icon-name] span {
+ display: none;
+ }
}
diff --git a/web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json b/web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json
new file mode 100644
index 0000000..150a647
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json
@@ -0,0 +1,4051 @@
+{
+ "schema": "web-rtcp-5axis-gmoccapy-button-icons-v1",
+ "generatedFrom": {
+ "csv": "work/working3/gmoccapy_button_icons/button_icon_inventory.csv",
+ "iconDirectory": "work/working3/gmoccapy_button_icons/files"
+ },
+ "semanticBoundary": "gmoccapy_button_icon_asset_reference_only_not_control_semantics",
+ "rowCount": 211,
+ "copiedIconCount": 112,
+ "entries": [
+ {
+ "category": "右侧主状态栏",
+ "source": "运行时状态",
+ "buttonId": "rbt_auto",
+ "label": "Auto 已选中",
+ "tooltip": "",
+ "imageId": "img_auto_on",
+ "imageRole": "Auto active 图标",
+ "stateOrVariant": "active",
+ "iconName": "mode_auto_active",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/mode_auto_active.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_auto_on__mode_auto_active__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_auto_on__mode_auto_active__48.png",
+ "signals": [],
+ "notes": "on_rbt_auto_toggled"
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "Glade 静态",
+ "buttonId": "rbt_auto",
+ "label": "",
+ "tooltip": "enter auto mode to run programs",
+ "imageId": "img_auto",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "mode_auto_inactive",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/mode_auto_inactive.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_auto__mode_auto_inactive__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_auto__mode_auto_inactive__48.png",
+ "signals": [
+ "pressed:on_rbt_auto_pressed",
+ "toggled:on_rbt_auto_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "运行时状态",
+ "buttonId": "rbt_manual",
+ "label": "手动已选中",
+ "tooltip": "",
+ "imageId": "img_manual_on",
+ "imageRole": "手动模式 active 图标",
+ "stateOrVariant": "active",
+ "iconName": "mode_manual_active",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/mode_manual_active.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_manual_on__mode_manual_active__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_manual_on__mode_manual_active__48.png",
+ "signals": [],
+ "notes": "on_rbt_manual_toggled"
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "Glade 静态",
+ "buttonId": "rbt_manual",
+ "label": "",
+ "tooltip": "enter manual mode to jog axis by hand or touch off / [F3]",
+ "imageId": "img_manual",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "mode_manual_inactive",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/mode_manual_inactive.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_manual__mode_manual_inactive__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_manual__mode_manual_inactive__48.png",
+ "signals": [
+ "pressed:on_rbt_manual_pressed",
+ "toggled:on_rbt_manual_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "运行时状态",
+ "buttonId": "rbt_mdi",
+ "label": "MDI 已选中",
+ "tooltip": "",
+ "imageId": "img_mdi_on",
+ "imageRole": "MDI active 图标",
+ "stateOrVariant": "active",
+ "iconName": "mode_mdi_active",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/mode_mdi_active.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_mdi_on__mode_mdi_active__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_mdi_on__mode_mdi_active__48.png",
+ "signals": [],
+ "notes": "on_rbt_mdi_toggled"
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "Glade 静态",
+ "buttonId": "rbt_mdi",
+ "label": "",
+ "tooltip": "enter MDI mode to launch G-code commands / [F5]",
+ "imageId": "img_mdi",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "mode_mdi_inactive",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/mode_mdi_inactive.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_mdi__mode_mdi_inactive__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_mdi__mode_mdi_inactive__48.png",
+ "signals": [
+ "pressed:on_rbt_mdi_pressed",
+ "toggled:on_rbt_mdi_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "运行时状态",
+ "buttonId": "tbtn_estop",
+ "label": "急停复位/非急停",
+ "tooltip": "",
+ "imageId": "img_emergency_off",
+ "imageRole": "急停复位态图标",
+ "stateOrVariant": "STATE_ESTOP_RESET",
+ "iconName": "main_switch_off",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/main_switch_off.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_emergency_off__main_switch_off__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_emergency_off__main_switch_off__48.png",
+ "signals": [],
+ "notes": "on_hal_status_state_estop_reset"
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_estop",
+ "label": "",
+ "tooltip": "Estop the machine / [F1]",
+ "imageId": "img_emergency",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "main_switch_on",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/main_switch_on.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_emergency__main_switch_on__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_emergency__main_switch_on__48.png",
+ "signals": [
+ "toggled:on_tbtn_estop_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "运行时状态",
+ "buttonId": "tbtn_on",
+ "label": "上电",
+ "tooltip": "",
+ "imageId": "img_machine_on",
+ "imageRole": "机器 ON 图标",
+ "stateOrVariant": "STATE_ON",
+ "iconName": "power_on",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/power_on.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_machine_on__power_on__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_machine_on__power_on__48.png",
+ "signals": [],
+ "notes": "on_hal_status_state_on"
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_on",
+ "label": "",
+ "tooltip": "Turn the machine on/off / [F2]",
+ "imageId": "img_machine_off",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "power_off",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/power_off.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_machine_off__power_off__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_machine_off__power_off__48.png",
+ "signals": [
+ "toggled:on_tbtn_on_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "运行时状态",
+ "buttonId": "tbtn_setup",
+ "label": "设置页已进入",
+ "tooltip": "",
+ "imageId": "img_settings_on",
+ "imageRole": "设置页 active 图标",
+ "stateOrVariant": "active/unlocked",
+ "iconName": "mode_settings_active",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/mode_settings_active.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_settings_on__mode_settings_active__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_settings_on__mode_settings_active__48.png",
+ "signals": [],
+ "notes": "on_tbtn_setup_toggled"
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_setup",
+ "label": "",
+ "tooltip": "Enter the settings page, the default code is \"123\"",
+ "imageId": "img_settings",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "mode_settings_inactive",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/mode_settings_inactive.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_settings__mode_settings_inactive__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_settings__mode_settings_inactive__48.png",
+ "signals": [
+ "toggled:on_tbtn_setup_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "运行时状态",
+ "buttonId": "tbtn_user_tabs",
+ "label": "用户页已显示",
+ "tooltip": "",
+ "imageId": "img_user_tabs_on",
+ "imageRole": "用户页 active 图标",
+ "stateOrVariant": "active",
+ "iconName": "mode_user_tabs_active",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/mode_user_tabs_active.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_user_tabs_on__mode_user_tabs_active__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_user_tabs_on__mode_user_tabs_active__48.png",
+ "signals": [],
+ "notes": "on_tbtn_user_tabs_toggled"
+ },
+ {
+ "category": "右侧主状态栏",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_user_tabs",
+ "label": "",
+ "tooltip": "show user tabs",
+ "imageId": "img_user_tabs",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "mode_user_tabs_inactive",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/mode_user_tabs_inactive.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_user_tabs__mode_user_tabs_inactive__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_user_tabs__mode_user_tabs_inactive__48.png",
+ "signals": [
+ "toggled:on_tbtn_user_tabs_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "底部主按钮栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_exit",
+ "label": "",
+ "tooltip": "Close gmoccapy / leave the program",
+ "imageId": "img_close",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "logout",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/logout.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_close__logout__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_close__logout__48.png",
+ "signals": [
+ "clicked:on_btn_exit_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "底部主按钮栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_homing",
+ "label": "",
+ "tooltip": "open homing button list",
+ "imageId": "img_ref_menu",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "ref_all",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/ref_all.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_ref_menu__ref_all__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_ref_menu__ref_all__48.png",
+ "signals": [
+ "clicked:on_btn_homing_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "底部主按钮栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_tool",
+ "label": "",
+ "tooltip": "Open the tooleditor page",
+ "imageId": "img_tools",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "hsk_mill_tool",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/hsk_mill_tool.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_tools__hsk_mill_tool__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_tools__hsk_mill_tool__48.png",
+ "signals": [
+ "clicked:on_btn_tool_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "底部主按钮栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_touch",
+ "label": "",
+ "tooltip": "open touch off button list",
+ "imageId": "img_touch_off",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "touch_off",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/touch_off.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_touch_off__touch_off__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_touch_off__touch_off__48.png",
+ "signals": [
+ "clicked:on_btn_touch_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "底部主按钮栏",
+ "source": "运行时状态",
+ "buttonId": "tbtn_fullsize_preview0",
+ "label": "退出大预览",
+ "tooltip": "",
+ "imageId": "img_fullsize_preview0_close",
+ "imageRole": "大预览关闭图标",
+ "stateOrVariant": "fullscreen active",
+ "iconName": "fullscreen_close",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/fullscreen_close.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_fullsize_preview0_close__fullscreen_close__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_fullsize_preview0_close__fullscreen_close__48.png",
+ "signals": [],
+ "notes": "on_tbtn_fullsize_preview_toggled"
+ },
+ {
+ "category": "底部主按钮栏",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_fullsize_preview0",
+ "label": "",
+ "tooltip": "make the preview as large as possible",
+ "imageId": "img_fullsize_preview0_open",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "fullscreen_open",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/fullscreen_open.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_fullsize_preview0_open__fullscreen_open__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_fullsize_preview0_open__fullscreen_open__48.png",
+ "signals": [
+ "toggled:on_tbtn_fullsize_preview_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "底部主按钮栏",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_switch_mode",
+ "label": "World / Mode",
+ "tooltip": "Switch motion mode between Joint and World mode / F12 or $ key does the same",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_tbtn_switch_mode_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_edit",
+ "label": "",
+ "tooltip": "Edit the loaded program",
+ "imageId": "img_editor",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "edit_code",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/edit_code.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_editor__edit_code__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_editor__edit_code__32.png",
+ "signals": [
+ "clicked:on_btn_edit_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_from_line",
+ "label": "",
+ "tooltip": "run the program from a certain line, attention, that is dangerous, because the previous lines will not checked!",
+ "imageId": "img_run_from",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "run_from_line",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/run_from_line.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_run_from__run_from_line__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_run_from__run_from_line__48.png",
+ "signals": [
+ "clicked:on_btn_from_line_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_load",
+ "label": "",
+ "tooltip": "Load a new program",
+ "imageId": "img_open",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "open_file",
+ "requestedSize": 32,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/open_file.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_open__open_file__32.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_open__open_file__32.svg",
+ "signals": [
+ "clicked:on_btn_load_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_reload",
+ "label": "",
+ "tooltip": "Reload file",
+ "imageId": "img_reload1",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "refresh",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/refresh.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_reload1__refresh__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_reload1__refresh__32.png",
+ "signals": [],
+ "notes": ""
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_run",
+ "label": "",
+ "tooltip": "Run the loaded program",
+ "imageId": "img_run",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "play",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/play.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_run__play__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_run__play__32.png",
+ "signals": [
+ "clicked:on_btn_run_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_step",
+ "label": "",
+ "tooltip": "Run the loaded program step by step",
+ "imageId": "img_step",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "step",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/step.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_step__step__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_step__step__32.png",
+ "signals": [],
+ "notes": ""
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_stop",
+ "label": "",
+ "tooltip": "Stop the running program",
+ "imageId": "img_stop",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "stop",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/stop.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_stop__stop__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_stop__stop__32.png",
+ "signals": [
+ "clicked:on_btn_stop_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "运行时状态",
+ "buttonId": "tbtn_fullsize_preview1",
+ "label": "退出大预览",
+ "tooltip": "",
+ "imageId": "img_fullsize_preview1_close",
+ "imageRole": "大预览关闭图标",
+ "stateOrVariant": "fullscreen active",
+ "iconName": "fullscreen_close",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/fullscreen_close.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_fullsize_preview1_close__fullscreen_close__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_fullsize_preview1_close__fullscreen_close__48.png",
+ "signals": [],
+ "notes": "on_tbtn_fullsize_preview_toggled"
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_fullsize_preview1",
+ "label": "",
+ "tooltip": "Show full screen preview",
+ "imageId": "img_fullsize_preview1_open",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "fullscreen_open",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/fullscreen_open.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_fullsize_preview1_open__fullscreen_open__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_fullsize_preview1_open__fullscreen_open__48.png",
+ "signals": [
+ "toggled:on_tbtn_fullsize_preview_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "运行时状态",
+ "buttonId": "tbtn_optional_blocks",
+ "label": "跳过可选段",
+ "tooltip": "",
+ "imageId": "img_skip_optional_active",
+ "imageRole": "可选段跳过 active 图标",
+ "stateOrVariant": "block delete active",
+ "iconName": "skip_optional_active",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/skip_optional_active.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_skip_optional_active__skip_optional_active__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_skip_optional_active__skip_optional_active__32.png",
+ "signals": [],
+ "notes": "on_tbtn_optional_blocks_toggled"
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_optional_blocks",
+ "label": "",
+ "tooltip": "Machine or not the optional blocks of the program. If the button is pressed, the optional blocks will not be machined. The button will indicate this by a yellow background.",
+ "imageId": "img_skip_optional_inactive",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "skip_optional_inactive",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/skip_optional_inactive.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_skip_optional_inactive__skip_optional_inactive__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_skip_optional_inactive__skip_optional_inactive__32.png",
+ "signals": [
+ "toggled:on_tbtn_optional_blocks_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "运行时状态",
+ "buttonId": "tbtn_pause",
+ "label": "暂停中",
+ "tooltip": "",
+ "imageId": "img_pause_active",
+ "imageRole": "暂停 active 图标",
+ "stateOrVariant": "paused",
+ "iconName": "pause_active",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/pause_active.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_pause_active__pause_active__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_pause_active__pause_active__32.png",
+ "signals": [],
+ "notes": "on_tbtn_pause_toggled"
+ },
+ {
+ "category": "Auto 程序运行栏",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_pause",
+ "label": "",
+ "tooltip": "Pause the running program",
+ "imageId": "img_pause",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "pause",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/pause.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_pause__pause__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_pause__pause__32.png",
+ "signals": [
+ "toggled:on_tbtn_pause_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "MDI/宏按钮栏",
+ "source": "Python 动态",
+ "buttonId": "calculator",
+ "label": "计算器",
+ "tooltip": "Press to display the calculator",
+ "imageId": "img_macro_menu_calculator",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "calculator_open",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/calculator_open.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_macro_menu_calculator__calculator_open__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_macro_menu_calculator__calculator_open__32.png",
+ "signals": [],
+ "notes": "_make_macro_button"
+ },
+ {
+ "category": "MDI/宏按钮栏",
+ "source": "运行时状态",
+ "buttonId": "keyboard",
+ "label": "隐藏虚拟键盘",
+ "tooltip": "",
+ "imageId": "img_macro_menu_keyboard_hide",
+ "imageRole": "宏栏键盘隐藏图标",
+ "stateOrVariant": "keyboard shown",
+ "iconName": "keyboard_hide",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/keyboard_hide.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_macro_menu_keyboard_hide__keyboard_hide__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_macro_menu_keyboard_hide__keyboard_hide__32.png",
+ "signals": [],
+ "notes": "on_ntb_info_switch_page"
+ },
+ {
+ "category": "MDI/宏按钮栏",
+ "source": "运行时状态",
+ "buttonId": "keyboard",
+ "label": "中止运行宏/程序",
+ "tooltip": "",
+ "imageId": "img_macro_menu_stop",
+ "imageRole": "宏栏停止图标",
+ "stateOrVariant": "program/macro running or no keyboard",
+ "iconName": "stop",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/stop.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_macro_menu_stop__stop__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_macro_menu_stop__stop__32.png",
+ "signals": [],
+ "notes": "on_hal_status_interp_run/_no_virt_keyboard"
+ },
+ {
+ "category": "MDI/宏按钮栏",
+ "source": "Python 动态",
+ "buttonId": "keyboard",
+ "label": "虚拟键盘",
+ "tooltip": "Press to display the virtual keyboard",
+ "imageId": "img_macro_menu_keyboard",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "keyboard",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/keyboard.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_macro_menu_keyboard__keyboard__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_macro_menu_keyboard__keyboard__32.png",
+ "signals": [],
+ "notes": "_make_macro_button"
+ },
+ {
+ "category": "MDI/宏按钮栏",
+ "source": "Python 动态宏",
+ "buttonId": "macro_0",
+ "label": "i_am_lost",
+ "tooltip": "Press to run macro i_am_lost",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "macro-custom-file",
+ "sourcePath": "linuxcnc/configs/sim/gmoccapy/macros/images/i_am_lost.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/macro_0__macro_image.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/macro_0__macro_image.png",
+ "signals": [],
+ "notes": "宏文件 IMAGE 自定义图片: /home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/macros/images/i_am_lost.png"
+ },
+ {
+ "category": "MDI/宏按钮栏",
+ "source": "Python 动态宏",
+ "buttonId": "macro_1",
+ "label": "halo_world",
+ "tooltip": "Press to run macro halo_world",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "宏按钮使用文字,无图标"
+ },
+ {
+ "category": "MDI/宏按钮栏",
+ "source": "Python 动态宏",
+ "buttonId": "macro_2",
+ "label": "jog_around",
+ "tooltip": "Press to run macro jog_around",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "宏按钮使用文字,无图标"
+ },
+ {
+ "category": "MDI/宏按钮栏",
+ "source": "Python 动态宏",
+ "buttonId": "macro_3",
+ "label": "increment",
+ "tooltip": "Press to run macro increment xinc yinc",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "宏按钮使用文字,无图标"
+ },
+ {
+ "category": "MDI/宏按钮栏",
+ "source": "Python 动态宏",
+ "buttonId": "macro_4",
+ "label": "go_to_position",
+ "tooltip": "Press to run macro go_to_position X-pos Y-pos Z-pos",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "macro-custom-file",
+ "sourcePath": "linuxcnc/configs/sim/gmoccapy/macros/images/goto_x_y_z.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/macro_4__macro_image.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/macro_4__macro_image.png",
+ "signals": [],
+ "notes": "宏文件 IMAGE 自定义图片: /home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/macros/images/goto_x_y_z.png"
+ },
+ {
+ "category": "MDI/宏按钮栏",
+ "source": "Python 动态",
+ "buttonId": "next_button",
+ "label": "下一组宏按钮",
+ "tooltip": "Press to display next macro button",
+ "imageId": "img_macro_paginate_next",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "go-next",
+ "requestedSize": null,
+ "sourceType": "gtk-icon-name-not-copied",
+ "sourcePath": "GTK/system icon theme",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "当前宏数 5,小于 7,默认隐藏; 未在 gmoccapy classic 主题中固定解析,运行时由 GTK/system icon theme 决定"
+ },
+ {
+ "category": "MDI/宏按钮栏",
+ "source": "Python 动态",
+ "buttonId": "previous_button",
+ "label": "上一组宏按钮",
+ "tooltip": "Press to display previous macro button",
+ "imageId": "img_macro_paginate_prev",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "go-previous",
+ "requestedSize": null,
+ "sourceType": "gtk-icon-name-not-copied",
+ "sourcePath": "GTK/system icon theme",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "当前宏数 5,小于 7,默认隐藏; 未在 gmoccapy classic 主题中固定解析,运行时由 GTK/system icon theme 决定"
+ },
+ {
+ "category": "回零按钮栏",
+ "source": "Python 动态",
+ "buttonId": "home_axis_a",
+ "label": "回零 A 轴",
+ "tooltip": "Press to home axis A",
+ "imageId": "img_ref_a",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "ref_a",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/ref_a.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_ref_a__ref_a__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_ref_a__ref_a__48.png",
+ "signals": [],
+ "notes": "当前 XYZAB 生成"
+ },
+ {
+ "category": "回零按钮栏",
+ "source": "Python 动态",
+ "buttonId": "home_axis_b",
+ "label": "回零 B 轴",
+ "tooltip": "Press to home axis B",
+ "imageId": "img_ref_b",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "ref_b",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/ref_b.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_ref_b__ref_b__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_ref_b__ref_b__48.png",
+ "signals": [],
+ "notes": "当前 XYZAB 生成"
+ },
+ {
+ "category": "回零按钮栏",
+ "source": "Python 动态",
+ "buttonId": "home_axis_x",
+ "label": "回零 X 轴",
+ "tooltip": "Press to home axis X",
+ "imageId": "img_ref_x",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "ref_x",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/ref_x.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_ref_x__ref_x__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_ref_x__ref_x__48.png",
+ "signals": [],
+ "notes": "当前 XYZAB 生成"
+ },
+ {
+ "category": "回零按钮栏",
+ "source": "Python 动态",
+ "buttonId": "home_axis_y",
+ "label": "回零 Y 轴",
+ "tooltip": "Press to home axis Y",
+ "imageId": "img_ref_y",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "ref_y",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/ref_y.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_ref_y__ref_y__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_ref_y__ref_y__48.png",
+ "signals": [],
+ "notes": "当前 XYZAB 生成"
+ },
+ {
+ "category": "回零按钮栏",
+ "source": "Python 动态",
+ "buttonId": "home_axis_z",
+ "label": "回零 Z 轴",
+ "tooltip": "Press to home axis Z",
+ "imageId": "img_ref_z",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "ref_z",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/ref_z.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_ref_z__ref_z__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_ref_z__ref_z__48.png",
+ "signals": [],
+ "notes": "当前 XYZAB 生成"
+ },
+ {
+ "category": "回零按钮栏",
+ "source": "Python 动态",
+ "buttonId": "home_back",
+ "label": "返回主按钮栏",
+ "tooltip": "Press to return to main button list",
+ "imageId": "img_ref_menu_close",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "back_to_app",
+ "requestedSize": 48,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/back_to_app.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_ref_menu_close__back_to_app__48.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_ref_menu_close__back_to_app__48.svg",
+ "signals": [],
+ "notes": "_make_ref_axis_button"
+ },
+ {
+ "category": "回零按钮栏",
+ "source": "Python 动态",
+ "buttonId": "next_button",
+ "label": "下一组回零按钮",
+ "tooltip": "Press to display next homing button",
+ "imageId": "img_ref_paginate_next",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "chevron_right",
+ "requestedSize": 32,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/chevron_right.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_ref_paginate_next__chevron_right__32.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_ref_paginate_next__chevron_right__32.svg",
+ "signals": [],
+ "notes": "仅轴数大于 7 时显示"
+ },
+ {
+ "category": "回零按钮栏",
+ "source": "Python 动态",
+ "buttonId": "previous_button",
+ "label": "上一组回零按钮",
+ "tooltip": "Press to display previous homing button",
+ "imageId": "img_ref_paginate_prev",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "chevron_left",
+ "requestedSize": 32,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/chevron_left.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_ref_paginate_prev__chevron_left__32.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_ref_paginate_prev__chevron_left__32.svg",
+ "signals": [],
+ "notes": "仅轴数大于 7 时显示"
+ },
+ {
+ "category": "回零按钮栏",
+ "source": "Python 动态",
+ "buttonId": "ref_all",
+ "label": "回零全部轴",
+ "tooltip": "Press to home all axes",
+ "imageId": "img_ref_all",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "ref_all",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/ref_all.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_ref_all__ref_all__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_ref_all__ref_all__48.png",
+ "signals": [],
+ "notes": "_make_ref_axis_button"
+ },
+ {
+ "category": "回零按钮栏",
+ "source": "Python 动态",
+ "buttonId": "unref_all",
+ "label": "取消全部回零",
+ "tooltip": "Press to unhome all axes",
+ "imageId": "img_unref_all",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "unref_all",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/unref_all.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_unref_all__unref_all__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_unref_all__unref_all__48.png",
+ "signals": [],
+ "notes": "_make_ref_axis_button"
+ },
+ {
+ "category": "对刀/坐标设定按钮栏",
+ "source": "Python 动态",
+ "buttonId": "block_height",
+ "label": "Block Height",
+ "tooltip": "Press to enter new value for block height",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "仅 [TOOLSENSOR] 有效时出现;本配置默认不出现"
+ },
+ {
+ "category": "对刀/坐标设定按钮栏",
+ "source": "Python 动态",
+ "buttonId": "next_button",
+ "label": "下一组 Touch Off 按钮",
+ "tooltip": "Press to display next homing button",
+ "imageId": "img_touch_paginate_next",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "chevron_right",
+ "requestedSize": 32,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/chevron_right.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_touch_paginate_next__chevron_right__32.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_touch_paginate_next__chevron_right__32.svg",
+ "signals": [],
+ "notes": "仅按钮过多时显示"
+ },
+ {
+ "category": "对刀/坐标设定按钮栏",
+ "source": "Python 动态",
+ "buttonId": "previous_button",
+ "label": "上一组 Touch Off 按钮",
+ "tooltip": "Press to display previous homing button",
+ "imageId": "img_touch_paginate_prev",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "chevron_left",
+ "requestedSize": 32,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/chevron_left.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_touch_paginate_prev__chevron_left__32.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_touch_paginate_prev__chevron_left__32.svg",
+ "signals": [],
+ "notes": "仅轴数大于 8 时显示"
+ },
+ {
+ "category": "对刀/坐标设定按钮栏",
+ "source": "Python 动态",
+ "buttonId": "touch_a",
+ "label": "A 轴 Touch Off",
+ "tooltip": "Press to set touch off value for axis A",
+ "imageId": "img_touch_a",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "touch_a",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/touch_a.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_touch_a__touch_a__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_touch_a__touch_a__48.png",
+ "signals": [],
+ "notes": "当前 XYZAB 生成"
+ },
+ {
+ "category": "对刀/坐标设定按钮栏",
+ "source": "Python 动态",
+ "buttonId": "touch_b",
+ "label": "B 轴 Touch Off",
+ "tooltip": "Press to set touch off value for axis B",
+ "imageId": "img_touch_b",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "touch_b",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/touch_b.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_touch_b__touch_b__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_touch_b__touch_b__48.png",
+ "signals": [],
+ "notes": "当前 XYZAB 生成"
+ },
+ {
+ "category": "对刀/坐标设定按钮栏",
+ "source": "Python 动态",
+ "buttonId": "touch_back",
+ "label": "返回主按钮栏",
+ "tooltip": "Press to return to main button list",
+ "imageId": "img_touch_menu_close",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "back_to_app",
+ "requestedSize": 48,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/back_to_app.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_touch_menu_close__back_to_app__48.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_touch_menu_close__back_to_app__48.svg",
+ "signals": [],
+ "notes": "_make_touch_button"
+ },
+ {
+ "category": "对刀/坐标设定按钮栏",
+ "source": "Python 动态",
+ "buttonId": "touch_x",
+ "label": "X 轴 Touch Off",
+ "tooltip": "Press to set touch off value for axis X",
+ "imageId": "img_touch_x",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "touch_x",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/touch_x.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_touch_x__touch_x__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_touch_x__touch_x__48.png",
+ "signals": [],
+ "notes": "当前 XYZAB 生成"
+ },
+ {
+ "category": "对刀/坐标设定按钮栏",
+ "source": "Python 动态",
+ "buttonId": "touch_y",
+ "label": "Y 轴 Touch Off",
+ "tooltip": "Press to set touch off value for axis Y",
+ "imageId": "img_touch_y",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "touch_y",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/touch_y.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_touch_y__touch_y__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_touch_y__touch_y__48.png",
+ "signals": [],
+ "notes": "当前 XYZAB 生成"
+ },
+ {
+ "category": "对刀/坐标设定按钮栏",
+ "source": "Python 动态",
+ "buttonId": "touch_z",
+ "label": "Z 轴 Touch Off",
+ "tooltip": "Press to set touch off value for axis Z",
+ "imageId": "img_touch_z",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "touch_z",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/touch_z.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_touch_z__touch_z__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_touch_z__touch_z__48.png",
+ "signals": [],
+ "notes": "当前 XYZAB 生成"
+ },
+ {
+ "category": "手动/Jog 面板",
+ "source": "Glade 静态",
+ "buttonId": "chk_ignore_limits",
+ "label": "Ignore limits",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_ignore_limits_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "手动/Jog 面板",
+ "source": "运行时状态",
+ "buttonId": "tbtn_turtle_jog",
+ "label": "慢速 Jog",
+ "tooltip": "",
+ "imageId": "img_turtle_jog",
+ "imageRole": "慢速 Jog 图标",
+ "stateOrVariant": "active",
+ "iconName": "jog_speed_slow",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/jog_speed_slow.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_turtle_jog__jog_speed_slow__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_turtle_jog__jog_speed_slow__32.png",
+ "signals": [],
+ "notes": "on_tbtn_turtle_jog_toggled"
+ },
+ {
+ "category": "手动/Jog 面板",
+ "source": "运行时状态",
+ "buttonId": "tbtn_turtle_jog",
+ "label": "快速 Jog",
+ "tooltip": "",
+ "imageId": "img_rabbit_jog",
+ "imageRole": "快速 Jog 图标",
+ "stateOrVariant": "inactive",
+ "iconName": "jog_speed_fast",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/jog_speed_fast.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_rabbit_jog__jog_speed_fast__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_rabbit_jog__jog_speed_fast__32.png",
+ "signals": [],
+ "notes": "on_tbtn_turtle_jog_toggled"
+ },
+ {
+ "category": "手动/Jog 面板",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_turtle_jog",
+ "label": "",
+ "tooltip": "",
+ "imageId": "img_rabbit_jog",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "jog_speed_fast",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/jog_speed_fast.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_rabbit_jog__jog_speed_fast__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_rabbit_jog__jog_speed_fast__32.png",
+ "signals": [
+ "toggled:on_tbtn_turtle_jog_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Jog 增量选择栏",
+ "source": "Python 动态",
+ "buttonId": "rbt_0",
+ "label": "Continuous",
+ "tooltip": "Continuous jog",
+ "imageId": "img_continuous",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "jog_continuous",
+ "requestedSize": 24,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/24x24/actions/jog_continuous.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_continuous__jog_continuous__24.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_continuous__jog_continuous__24.png",
+ "signals": [],
+ "notes": "连续 Jog 唯一带图标"
+ },
+ {
+ "category": "Jog 增量选择栏",
+ "source": "Python 动态",
+ "buttonId": "rbt_1",
+ "label": "1.000 mm",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "增量按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 增量选择栏",
+ "source": "Python 动态",
+ "buttonId": "rbt_2",
+ "label": "0.100 mm",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "增量按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 增量选择栏",
+ "source": "Python 动态",
+ "buttonId": "rbt_3",
+ "label": "0.010 mm",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "增量按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 增量选择栏",
+ "source": "Python 动态",
+ "buttonId": "rbt_4",
+ "label": "0.001 mm",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "增量按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 增量选择栏",
+ "source": "Python 动态",
+ "buttonId": "rbt_5",
+ "label": "1.2345 in",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "增量按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 轴按钮",
+ "source": "Python 动态",
+ "buttonId": "a+",
+ "label": "A+",
+ "tooltip": "Press to jog axis A",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "轴 Jog 按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 轴按钮",
+ "source": "Python 动态",
+ "buttonId": "a-",
+ "label": "A-",
+ "tooltip": "Press to jog axis A",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "轴 Jog 按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 轴按钮",
+ "source": "Python 动态",
+ "buttonId": "b+",
+ "label": "B+",
+ "tooltip": "Press to jog axis B",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "轴 Jog 按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 轴按钮",
+ "source": "Python 动态",
+ "buttonId": "b-",
+ "label": "B-",
+ "tooltip": "Press to jog axis B",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "轴 Jog 按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 轴按钮",
+ "source": "Python 动态",
+ "buttonId": "x+",
+ "label": "X+",
+ "tooltip": "Press to jog axis X",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "轴 Jog 按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 轴按钮",
+ "source": "Python 动态",
+ "buttonId": "x-",
+ "label": "X-",
+ "tooltip": "Press to jog axis X",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "轴 Jog 按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 轴按钮",
+ "source": "Python 动态",
+ "buttonId": "y+",
+ "label": "Y+",
+ "tooltip": "Press to jog axis Y",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "轴 Jog 按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 轴按钮",
+ "source": "Python 动态",
+ "buttonId": "y-",
+ "label": "Y-",
+ "tooltip": "Press to jog axis Y",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "轴 Jog 按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 轴按钮",
+ "source": "Python 动态",
+ "buttonId": "z+",
+ "label": "Z+",
+ "tooltip": "Press to jog axis Z",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "轴 Jog 按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 轴按钮",
+ "source": "Python 动态",
+ "buttonId": "z-",
+ "label": "Z-",
+ "tooltip": "Press to jog axis Z",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "轴 Jog 按钮使用文字,无图标"
+ },
+ {
+ "category": "Jog 关节按钮",
+ "source": "Python 动态",
+ "buttonId": "0+",
+ "label": "0+",
+ "tooltip": "Press to jog joint 0",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "trivial kinematics 时不创建;非平凡运动学才出现"
+ },
+ {
+ "category": "Jog 关节按钮",
+ "source": "Python 动态",
+ "buttonId": "0-",
+ "label": "0-",
+ "tooltip": "Press to jog joint 0",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "trivial kinematics 时不创建;非平凡运动学才出现"
+ },
+ {
+ "category": "Jog 关节按钮",
+ "source": "Python 动态",
+ "buttonId": "1+",
+ "label": "1+",
+ "tooltip": "Press to jog joint 1",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "trivial kinematics 时不创建;非平凡运动学才出现"
+ },
+ {
+ "category": "Jog 关节按钮",
+ "source": "Python 动态",
+ "buttonId": "1-",
+ "label": "1-",
+ "tooltip": "Press to jog joint 1",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "trivial kinematics 时不创建;非平凡运动学才出现"
+ },
+ {
+ "category": "Jog 关节按钮",
+ "source": "Python 动态",
+ "buttonId": "2+",
+ "label": "2+",
+ "tooltip": "Press to jog joint 2",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "trivial kinematics 时不创建;非平凡运动学才出现"
+ },
+ {
+ "category": "Jog 关节按钮",
+ "source": "Python 动态",
+ "buttonId": "2-",
+ "label": "2-",
+ "tooltip": "Press to jog joint 2",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "trivial kinematics 时不创建;非平凡运动学才出现"
+ },
+ {
+ "category": "Jog 关节按钮",
+ "source": "Python 动态",
+ "buttonId": "3+",
+ "label": "3+",
+ "tooltip": "Press to jog joint 3",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "trivial kinematics 时不创建;非平凡运动学才出现"
+ },
+ {
+ "category": "Jog 关节按钮",
+ "source": "Python 动态",
+ "buttonId": "3-",
+ "label": "3-",
+ "tooltip": "Press to jog joint 3",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "trivial kinematics 时不创建;非平凡运动学才出现"
+ },
+ {
+ "category": "Jog 关节按钮",
+ "source": "Python 动态",
+ "buttonId": "4+",
+ "label": "4+",
+ "tooltip": "Press to jog joint 4",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "trivial kinematics 时不创建;非平凡运动学才出现"
+ },
+ {
+ "category": "Jog 关节按钮",
+ "source": "Python 动态",
+ "buttonId": "4-",
+ "label": "4-",
+ "tooltip": "Press to jog joint 4",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "trivial kinematics 时不创建;非平凡运动学才出现"
+ },
+ {
+ "category": "冷却/主轴控制",
+ "source": "运行时状态",
+ "buttonId": "rbt_forward",
+ "label": "主轴正转中",
+ "tooltip": "",
+ "imageId": "img_spindle_forward_on",
+ "imageRole": "正转 active 图标",
+ "stateOrVariant": "active",
+ "iconName": "spindle_right_on",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/spindle_right_on.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_spindle_forward_on__spindle_right_on__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_spindle_forward_on__spindle_right_on__48.png",
+ "signals": [],
+ "notes": "on_rbt_forward_released/clicked"
+ },
+ {
+ "category": "冷却/主轴控制",
+ "source": "Glade 静态",
+ "buttonId": "rbt_forward",
+ "label": "",
+ "tooltip": "",
+ "imageId": "img_spindle_forward",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "spindle_right",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/spindle_right.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_spindle_forward__spindle_right__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_spindle_forward__spindle_right__48.png",
+ "signals": [
+ "clicked:on_rbt_forward_clicked",
+ "released:on_rbt_forward_released"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "冷却/主轴控制",
+ "source": "运行时状态",
+ "buttonId": "rbt_reverse",
+ "label": "主轴反转中",
+ "tooltip": "",
+ "imageId": "img_spindle_reverse_on",
+ "imageRole": "反转 active 图标",
+ "stateOrVariant": "active",
+ "iconName": "spindle_left_on",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/spindle_left_on.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_spindle_reverse_on__spindle_left_on__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_spindle_reverse_on__spindle_left_on__48.png",
+ "signals": [],
+ "notes": "on_rbt_reverse_released/clicked"
+ },
+ {
+ "category": "冷却/主轴控制",
+ "source": "Glade 静态",
+ "buttonId": "rbt_reverse",
+ "label": "",
+ "tooltip": "",
+ "imageId": "img_spindle_reverse",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "spindle_left",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/spindle_left.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_spindle_reverse__spindle_left__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_spindle_reverse__spindle_left__48.png",
+ "signals": [
+ "clicked:on_rbt_reverse_clicked",
+ "released:on_rbt_reverse_released"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "冷却/主轴控制",
+ "source": "运行时状态",
+ "buttonId": "rbt_stop",
+ "label": "主轴停止已选中",
+ "tooltip": "",
+ "imageId": "img_spindle_stop_on",
+ "imageRole": "停止 active 图标",
+ "stateOrVariant": "active",
+ "iconName": "spindle_stop_on",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/spindle_stop_on.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_spindle_stop_on__spindle_stop_on__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_spindle_stop_on__spindle_stop_on__48.png",
+ "signals": [],
+ "notes": "on_rbt_stop_clicked"
+ },
+ {
+ "category": "冷却/主轴控制",
+ "source": "运行时状态",
+ "buttonId": "rbt_stop",
+ "label": "主轴停止未选中",
+ "tooltip": "",
+ "imageId": "img_spindle_stop",
+ "imageRole": "停止 inactive 图标",
+ "stateOrVariant": "inactive",
+ "iconName": "spindle_stop",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/spindle_stop.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_spindle_stop__spindle_stop__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_spindle_stop__spindle_stop__48.png",
+ "signals": [],
+ "notes": "on_rbt_stop_clicked: widget inactive 时切回停止普通图标"
+ },
+ {
+ "category": "冷却/主轴控制",
+ "source": "Glade 静态",
+ "buttonId": "rbt_stop",
+ "label": "",
+ "tooltip": "",
+ "imageId": "img_spindle_stop_on",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "spindle_stop_on",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/spindle_stop_on.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_spindle_stop_on__spindle_stop_on__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_spindle_stop_on__spindle_stop_on__48.png",
+ "signals": [
+ "clicked:on_rbt_stop_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "冷却/主轴控制",
+ "source": "运行时状态",
+ "buttonId": "tbtn_flood",
+ "label": "冷却液开",
+ "tooltip": "",
+ "imageId": "img_coolant_on",
+ "imageRole": "Flood active 图标",
+ "stateOrVariant": "active",
+ "iconName": "coolant_flood_active",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/coolant_flood_active.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_coolant_on__coolant_flood_active__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_coolant_on__coolant_flood_active__48.png",
+ "signals": [],
+ "notes": "on_hal_status_flood_changed"
+ },
+ {
+ "category": "冷却/主轴控制",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_flood",
+ "label": "",
+ "tooltip": "",
+ "imageId": "img_coolant_off",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "coolant_flood_inactive",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/coolant_flood_inactive.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_coolant_off__coolant_flood_inactive__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_coolant_off__coolant_flood_inactive__48.png",
+ "signals": [
+ "toggled:on_tbtn_flood_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "冷却/主轴控制",
+ "source": "运行时状态",
+ "buttonId": "tbtn_mist",
+ "label": "雾冷开",
+ "tooltip": "",
+ "imageId": "img_mist_on",
+ "imageRole": "Mist active 图标",
+ "stateOrVariant": "active",
+ "iconName": "coolant_mist_active",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/coolant_mist_active.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_mist_on__coolant_mist_active__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_mist_on__coolant_mist_active__48.png",
+ "signals": [],
+ "notes": "on_hal_status_mist_changed"
+ },
+ {
+ "category": "冷却/主轴控制",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_mist",
+ "label": "",
+ "tooltip": "",
+ "imageId": "img_mist_off",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "coolant_mist_inactive",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/coolant_mist_inactive.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_mist_off__coolant_mist_inactive__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_mist_off__coolant_mist_inactive__48.png",
+ "signals": [
+ "toggled:on_tbtn_mist_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "预览/视图",
+ "source": "Glade 静态",
+ "buttonId": "btn_delete_view",
+ "label": "",
+ "tooltip": "clear plot",
+ "imageId": "img_tool_clear",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "clear",
+ "requestedSize": 24,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/clear.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_tool_clear__clear__24.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_tool_clear__clear__24.png",
+ "signals": [
+ "clicked:on_btn_delete_view_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "预览/视图",
+ "source": "Glade 静态",
+ "buttonId": "btn_zoom_in",
+ "label": "",
+ "tooltip": "Zoom in",
+ "imageId": "img_zoom_in",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "zoom_in",
+ "requestedSize": 24,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/zoom_in.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_zoom_in__zoom_in__24.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_zoom_in__zoom_in__24.png",
+ "signals": [
+ "clicked:on_btn_zoom_in_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "预览/视图",
+ "source": "Glade 静态",
+ "buttonId": "btn_zoom_out",
+ "label": "",
+ "tooltip": "Zoom out",
+ "imageId": "img_zoom_out",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "zoom_out",
+ "requestedSize": 24,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/zoom_out.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_zoom_out__zoom_out__24.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_zoom_out__zoom_out__24.png",
+ "signals": [
+ "clicked:on_btn_zoom_out_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "预览/视图",
+ "source": "Glade 静态",
+ "buttonId": "rbt_view_p",
+ "label": "",
+ "tooltip": "view perspective",
+ "imageId": "img_view_p",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "tool_axis_p",
+ "requestedSize": 24,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/24x24/actions/tool_axis_p.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_view_p__tool_axis_p__24.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_view_p__tool_axis_p__24.png",
+ "signals": [
+ "toggled:on_rbt_view_p_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "预览/视图",
+ "source": "Glade 静态",
+ "buttonId": "rbt_view_x",
+ "label": "",
+ "tooltip": "view along the X axis from positive to negative",
+ "imageId": "img_view_x",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "tool_axis_x",
+ "requestedSize": 24,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/24x24/actions/tool_axis_x.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_view_x__tool_axis_x__24.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_view_x__tool_axis_x__24.png",
+ "signals": [
+ "toggled:on_rbt_view_x_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "预览/视图",
+ "source": "Glade 静态",
+ "buttonId": "rbt_view_y",
+ "label": "",
+ "tooltip": "view along the Y axis from positive to negative",
+ "imageId": "img_view_y",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "tool_axis_y",
+ "requestedSize": 24,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/24x24/actions/tool_axis_y.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_view_y__tool_axis_y__24.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_view_y__tool_axis_y__24.png",
+ "signals": [
+ "toggled:on_rbt_view_y_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "预览/视图",
+ "source": "Glade 静态",
+ "buttonId": "rbt_view_y2",
+ "label": "",
+ "tooltip": "view along the Y axis from positive to negative as viewn for a back tool lathe",
+ "imageId": "img_view_y2",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "tool_axis_y_inv",
+ "requestedSize": 24,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/24x24/actions/tool_axis_y_inv.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_view_y2__tool_axis_y_inv__24.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_view_y2__tool_axis_y_inv__24.png",
+ "signals": [
+ "toggled:on_rbt_view_y2_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "预览/视图",
+ "source": "Glade 静态",
+ "buttonId": "rbt_view_z",
+ "label": "",
+ "tooltip": "view along the Z axis from positive to negative",
+ "imageId": "img_view_z",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "tool_axis_z",
+ "requestedSize": 24,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/24x24/actions/tool_axis_z.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_view_z__tool_axis_z__24.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_view_z__tool_axis_z__24.png",
+ "signals": [
+ "toggled:on_rbt_view_z_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "预览/视图",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_view_dimension",
+ "label": "",
+ "tooltip": "Show or hide dimensions",
+ "imageId": "img_dimensions",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "dimensions",
+ "requestedSize": 24,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/dimensions.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_dimensions__dimensions__24.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_dimensions__dimensions__24.png",
+ "signals": [
+ "toggled:on_tbtn_view_dimension_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "预览/视图",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_view_tool_path",
+ "label": "",
+ "tooltip": "Show or hide tool path",
+ "imageId": "img_tool_path",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "toolpath",
+ "requestedSize": 24,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/24x24/actions/toolpath.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_tool_path__toolpath__24.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_tool_path__toolpath__24.png",
+ "signals": [
+ "toggled:on_tbtn_view_tool_path_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "编辑器搜索/编辑",
+ "source": "Glade 静态",
+ "buttonId": "btn_comment",
+ "label": "",
+ "tooltip": "",
+ "imageId": "img_edit_comment",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "comment",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/comment.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_edit_comment__comment__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_edit_comment__comment__32.png",
+ "signals": [
+ "clicked:on_btn_toggle_comment_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "编辑器搜索/编辑",
+ "source": "Glade 静态",
+ "buttonId": "btn_redo",
+ "label": "",
+ "tooltip": "Redo",
+ "imageId": "img_edit-redo",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "edit_redo",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/edit_redo.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_edit-redo__edit_redo__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_edit-redo__edit_redo__32.png",
+ "signals": [
+ "clicked:on_btn_redo_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "编辑器搜索/编辑",
+ "source": "Glade 静态",
+ "buttonId": "btn_replace",
+ "label": "Replace",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_replace_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "编辑器搜索/编辑",
+ "source": "Glade 静态",
+ "buttonId": "btn_search_back",
+ "label": "Search / back",
+ "tooltip": "",
+ "imageId": "img_up",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "chevron_up",
+ "requestedSize": 24,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/chevron_up.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_up__chevron_up__24.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_up__chevron_up__24.svg",
+ "signals": [
+ "clicked:on_btn_search_back_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "编辑器搜索/编辑",
+ "source": "Glade 静态",
+ "buttonId": "btn_search_forward",
+ "label": "Search / fwd",
+ "tooltip": "",
+ "imageId": "img_down",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "chevron_down",
+ "requestedSize": 24,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/chevron_down.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_down__chevron_down__24.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_down__chevron_down__24.svg",
+ "signals": [
+ "clicked:on_btn_search_forward_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "编辑器搜索/编辑",
+ "source": "Glade 静态",
+ "buttonId": "btn_undo",
+ "label": "",
+ "tooltip": "Undo",
+ "imageId": "img_edit-undo",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "edit_undo",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/edit_undo.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_edit-undo__edit_undo__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_edit-undo__edit_undo__32.png",
+ "signals": [
+ "clicked:on_btn_undo_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "编辑器搜索/编辑",
+ "source": "Glade 静态",
+ "buttonId": "chk_ignore_case",
+ "label": "Ignore Case",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": ""
+ },
+ {
+ "category": "编辑器搜索/编辑",
+ "source": "Glade 静态",
+ "buttonId": "chk_replace_all",
+ "label": "Replace All",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": ""
+ },
+ {
+ "category": "编辑页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_back_edit",
+ "label": "",
+ "tooltip": "Go back to main button list",
+ "imageId": "img_edit_menu_close",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "back_to_app",
+ "requestedSize": 48,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/back_to_app.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_edit_menu_close__back_to_app__48.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_edit_menu_close__back_to_app__48.svg",
+ "signals": [
+ "clicked:on_btn_back_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "编辑页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_calc",
+ "label": "",
+ "tooltip": "Show calculator",
+ "imageId": "img_edit_menu_calculator",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "calculator_open",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/calculator_open.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_edit_menu_calculator__calculator_open__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_edit_menu_calculator__calculator_open__32.png",
+ "signals": [
+ "clicked:on_btn_show_calc_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "编辑页底部栏",
+ "source": "运行时状态",
+ "buttonId": "btn_keyb",
+ "label": "隐藏虚拟键盘",
+ "tooltip": "",
+ "imageId": "img_edit_menu_keyboard_hide",
+ "imageRole": "编辑页键盘隐藏图标",
+ "stateOrVariant": "keyboard shown",
+ "iconName": "keyboard_hide",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/keyboard_hide.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_edit_menu_keyboard_hide__keyboard_hide__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_edit_menu_keyboard_hide__keyboard_hide__32.png",
+ "signals": [],
+ "notes": "on_ntb_info_switch_page"
+ },
+ {
+ "category": "编辑页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_keyb",
+ "label": "",
+ "tooltip": "Show or hide the virtual keyboard",
+ "imageId": "img_edit_menu_keyboard",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "keyboard",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/keyboard.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_edit_menu_keyboard__keyboard__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_edit_menu_keyboard__keyboard__32.png",
+ "signals": [
+ "clicked:on_btn_show_kbd_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "编辑页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_new",
+ "label": "",
+ "tooltip": "clear the edit field and make a new file",
+ "imageId": "img_edit_menu_new",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "new_document",
+ "requestedSize": 32,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/new_document.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_edit_menu_new__new_document__32.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_edit_menu_new__new_document__32.svg",
+ "signals": [
+ "clicked:on_btn_new_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "编辑页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_reload_edit",
+ "label": "",
+ "tooltip": "Reload file",
+ "imageId": "img_edit_menu_reload",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "refresh",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/refresh.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_edit_menu_reload__refresh__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_edit_menu_reload__refresh__32.png",
+ "signals": [],
+ "notes": ""
+ },
+ {
+ "category": "编辑页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_save",
+ "label": "",
+ "tooltip": "save the file using the original name",
+ "imageId": "img_edit_menu_save",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "save",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/save.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_edit_menu_save__save__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_edit_menu_save__save__32.png",
+ "signals": [],
+ "notes": ""
+ },
+ {
+ "category": "编辑页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_save_as",
+ "label": "",
+ "tooltip": "save the file with a new name",
+ "imageId": "img_edit_menu_save_as",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "save_as",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/save_as.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_edit_menu_save_as__save_as__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_edit_menu_save_as__save_as__32.png",
+ "signals": [],
+ "notes": ""
+ },
+ {
+ "category": "编辑页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_split_view",
+ "label": "",
+ "tooltip": "Show preview as split view",
+ "imageId": "img_split_view",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "split_view",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/split_view.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_split_view__split_view__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_split_view__split_view__32.png",
+ "signals": [
+ "toggled:on_tbtn_split_view_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "刀具页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_back_tool",
+ "label": "",
+ "tooltip": "Go back to main button list",
+ "imageId": "img_back_tool",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "back_to_app",
+ "requestedSize": 48,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/back_to_app.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_back_tool__back_to_app__48.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_back_tool__back_to_app__48.svg",
+ "signals": [
+ "clicked:on_btn_back_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "刀具页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_change_tool",
+ "label": "",
+ "tooltip": "change tool to the selected one",
+ "imageId": "img_toolchange",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "mill_tool_change",
+ "requestedSize": 48,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/mill_tool_change.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_toolchange__mill_tool_change__48.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_toolchange__mill_tool_change__48.png",
+ "signals": [
+ "clicked:on_btn_selected_tool_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "刀具页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_index_tool",
+ "label": "",
+ "tooltip": "change tool with the command M61 Q?, no machine move will be done",
+ "imageId": "img_index_tool",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "mill_tool_set_num",
+ "requestedSize": 48,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/mill_tool_set_num.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_index_tool__mill_tool_set_num__48.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_index_tool__mill_tool_set_num__48.svg",
+ "signals": [
+ "clicked:on_btn_selected_tool_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "刀具页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_select_tool_by_no",
+ "label": "",
+ "tooltip": "Select a tool by number",
+ "imageId": "img_tool_by_no",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "mill_tool_change_num",
+ "requestedSize": 48,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/mill_tool_change_num.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_tool_by_no__mill_tool_change_num__48.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_tool_by_no__mill_tool_change_num__48.svg",
+ "signals": [
+ "clicked:on_btn_select_tool_by_no_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "刀具页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_tool_touchoff_x",
+ "label": "",
+ "tooltip": "touch off the tool and set the value to the tool table",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_tool_touchoff_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "刀具页底部栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_tool_touchoff_z",
+ "label": "",
+ "tooltip": "touch off the tool and set the value to the tool table",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_tool_touchoff_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "文件选择栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_back_file_load",
+ "label": "",
+ "tooltip": "Close without returning a file path",
+ "imageId": "img_back_file_load",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "back_to_app",
+ "requestedSize": 48,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/back_to_app.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_back_file_load__back_to_app__48.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_back_file_load__back_to_app__48.svg",
+ "signals": [
+ "clicked:on_btn_back_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "文件选择栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_dir_up",
+ "label": "",
+ "tooltip": "Move to parent directory",
+ "imageId": "img_dir_up",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "chevron_up",
+ "requestedSize": 32,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/chevron_up.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_dir_up__chevron_up__32.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_dir_up__chevron_up__32.svg",
+ "signals": [
+ "clicked:on_btn_dir_up_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "文件选择栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_home",
+ "label": "",
+ "tooltip": "Move to your home directory",
+ "imageId": "img_home",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "home_folder",
+ "requestedSize": 32,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/home_folder.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_home__home_folder__32.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_home__home_folder__32.svg",
+ "signals": [
+ "clicked:on_btn_home_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "文件选择栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_jump_to",
+ "label": "",
+ "tooltip": "Jump to user defined directory",
+ "imageId": "img_jump_to",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "user_defined_folder",
+ "requestedSize": 32,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/user_defined_folder.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_jump_to__user_defined_folder__32.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_jump_to__user_defined_folder__32.svg",
+ "signals": [
+ "clicked:on_btn_jump_to_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "文件选择栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_reload_dir",
+ "label": "",
+ "tooltip": "Refresh directory",
+ "imageId": "img_refresh_dir",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "refresh",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/refresh.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_refresh_dir__refresh__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_refresh_dir__refresh__32.png",
+ "signals": [
+ "clicked:on_btn_refresh_dir_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "文件选择栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_sel_next",
+ "label": "",
+ "tooltip": "Select the next file",
+ "imageId": "img_sel_next",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "chevron_right",
+ "requestedSize": 32,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/chevron_right.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_sel_next__chevron_right__32.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_sel_next__chevron_right__32.svg",
+ "signals": [
+ "clicked:on_btn_sel_next_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "文件选择栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_sel_prev",
+ "label": "",
+ "tooltip": "Select the previous file",
+ "imageId": "img_sel_prev",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "chevron_left",
+ "requestedSize": 32,
+ "sourceType": "theme-svg",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/scalable/actions/chevron_left.svg",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_sel_prev__chevron_left__32.svg",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_sel_prev__chevron_left__32.svg",
+ "signals": [
+ "clicked:on_btn_sel_prev_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "文件选择栏",
+ "source": "Glade 静态",
+ "buttonId": "btn_select",
+ "label": "",
+ "tooltip": "select the highlighted file and return the path",
+ "imageId": "img_select",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "select_file",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/select_file.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_select__select_file__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_select__select_file__32.png",
+ "signals": [
+ "clicked:on_btn_select_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "文件选择栏",
+ "source": "Glade 静态",
+ "buttonId": "tbtn_sort",
+ "label": "Sort by / date",
+ "tooltip": "Sort files by date, newest first",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_tbtn_sort_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "内嵌 ToolEdit 控件",
+ "source": "内嵌控件",
+ "buttonId": "tooldedit.add",
+ "label": "新增刀具",
+ "tooltip": "Add new tool",
+ "imageId": "img_tool_add",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "add",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/add.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_tool_add__add__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_tool_add__add__32.png",
+ "signals": [],
+ "notes": "Python 改造 ToolEdit"
+ },
+ {
+ "category": "内嵌 ToolEdit 控件",
+ "source": "内嵌控件",
+ "buttonId": "tooldedit.apply",
+ "label": "保存刀具表",
+ "tooltip": "Save tool table to file",
+ "imageId": "img_tool_save",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "save",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/save.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_tool_save__save__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_tool_save__save__32.png",
+ "signals": [],
+ "notes": "Python 改造 ToolEdit"
+ },
+ {
+ "category": "内嵌 ToolEdit 控件",
+ "source": "内嵌控件",
+ "buttonId": "tooldedit.calculator",
+ "label": "刀具表计算器",
+ "tooltip": "Use calculator to edit numeric values",
+ "imageId": "img_tool_calculator",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "calculator_open",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/calculator_open.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_tool_calculator__calculator_open__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_tool_calculator__calculator_open__32.png",
+ "signals": [],
+ "notes": "Python 改造 ToolEdit"
+ },
+ {
+ "category": "内嵌 ToolEdit 控件",
+ "source": "内嵌控件",
+ "buttonId": "tooldedit.delete",
+ "label": "删除选中刀具",
+ "tooltip": "Delete selected tool",
+ "imageId": "img_tool_delete",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "delete",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/delete.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_tool_delete__delete__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_tool_delete__delete__32.png",
+ "signals": [],
+ "notes": "Python 改造 ToolEdit"
+ },
+ {
+ "category": "内嵌 ToolEdit 控件",
+ "source": "内嵌控件",
+ "buttonId": "tooldedit.reload",
+ "label": "重载刀具表",
+ "tooltip": "Reload tool table from file",
+ "imageId": "img_tool_reload",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "refresh",
+ "requestedSize": 32,
+ "sourceType": "theme-nearest-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/48x48/actions/refresh.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_tool_reload__refresh__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_tool_reload__refresh__32.png",
+ "signals": [],
+ "notes": "Python 改造 ToolEdit"
+ },
+ {
+ "category": "内嵌 OffsetPage 控件",
+ "source": "内嵌控件",
+ "buttonId": "offsetpage.calculator",
+ "label": "坐标偏置计算器",
+ "tooltip": "Use calculator to edit numeric values",
+ "imageId": "img_offset_calculator",
+ "imageRole": "动态按钮图标",
+ "stateOrVariant": "默认",
+ "iconName": "calculator_open",
+ "requestedSize": 32,
+ "sourceType": "theme-exact-png",
+ "sourcePath": "linuxcnc/share/gmoccapy/icons/classic/32x32/actions/calculator_open.png",
+ "copiedFile": "work/working3/gmoccapy_button_icons/files/img_offset_calculator__calculator_open__32.png",
+ "projectAsset": "app/src/assets/gmoccapy-icons/img_offset_calculator__calculator_open__32.png",
+ "signals": [],
+ "notes": "Python 改造 OffsetPage"
+ },
+ {
+ "category": "内嵌 OffsetPage 控件",
+ "source": "内嵌控件",
+ "buttonId": "offsetpage.edit_offsets",
+ "label": "Edit Offsets",
+ "tooltip": "Edit offsets in OffsetPage",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "内嵌控件文字按钮,无图标"
+ },
+ {
+ "category": "内嵌 OffsetPage 控件",
+ "source": "内嵌控件",
+ "buttonId": "offsetpage.set_selected",
+ "label": "Set selected",
+ "tooltip": "Set selected coordinate system active",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "内嵌控件文字按钮,无图标"
+ },
+ {
+ "category": "内嵌 OffsetPage 控件",
+ "source": "内嵌控件",
+ "buttonId": "offsetpage.zero_g92",
+ "label": "Zero G92",
+ "tooltip": "Set G92 offsets to zero",
+ "imageId": "",
+ "imageRole": "无图标",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": "内嵌控件文字按钮,无图标"
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "abs_colorbutton",
+ "label": "",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "color-set:on_abs_colorbutton_color_set"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "audio_alert_chooser",
+ "label": "",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "file-set:on_change_sound"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "audio_error_chooser",
+ "label": "",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "file-set:on_change_sound"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_en_audio",
+ "label": "Enable sound",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_en_audio_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_font_monospace",
+ "label": "Monospace",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_font_monospace_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_font_regular",
+ "label": "Regular/Medium",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_font_regular_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_hide_cursor",
+ "label": "Hide cursor",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_hide_cursor_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_hide_tooltips",
+ "label": "Hide tooltips",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_hide_tooltips_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_kbd_set_height",
+ "label": "Height",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_kb_set_height_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_kbd_set_width",
+ "label": "Width",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_kb_set_width_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_reload_tool",
+ "label": "Reload Tool on Start",
+ "tooltip": "If checked, the tool in spindle / will be saved on each change / and the last tool will be reloaded / at start of the GUI. Also it's / length offset will be reloaded.",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_reload_tool_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_show_dro",
+ "label": "Show DRO",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_show_dro_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_show_dtg",
+ "label": "Show DTG",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_show_dtg_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_show_offsets",
+ "label": "Show offsets",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_show_offsets_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_toggle_readout",
+ "label": "Toggle DRO mode by / clicking on the DRO",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_toggle_readout_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_turtle_jog",
+ "label": "Hide turtle Jog Button",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_turtle_jog_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_use_frames",
+ "label": "Use frames",
+ "tooltip": "If checked, the messages / will be in a frame.",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_use_frames_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_use_kb_on_edit",
+ "label": "Show keyboard on EDIT",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_use_kb_on_edit_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_use_kb_on_file_selection",
+ "label": "Show keyboard on load file",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_use_kb_on_file_selection_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_use_kb_on_mdi",
+ "label": "Show keyboard on MDI",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_use_kb_on_mdi_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_use_kb_on_offset",
+ "label": "Show keyboard on offset",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_use_kb_on_offset_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_use_kb_on_tooledit",
+ "label": "Show keyboard on tooledit",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_use_kb_on_tooledit_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_use_kb_shortcuts",
+ "label": "Use keyboard shortcuts",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_use_kb_shortcuts_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "chk_use_tool_measurement",
+ "label": "Use auto tool measurement",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chk_use_tool_measurement_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "dtg_colorbutton",
+ "label": "",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "color-set:on_dtg_colorbutton_color_set"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "file_to_load_chooser",
+ "label": "",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "file-set:on_file_to_load_chooser_file_set"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "fontbutton_gcodeview",
+ "label": "",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "font-set:on_fontbutton_gcodeview_font_set"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "fontbutton_popup",
+ "label": "",
+ "tooltip": "The font to use",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "font-set:on_fontbutton_popup_font_set"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "homed_colorbtn",
+ "label": "",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "color-set:on_homed_colorbtn_color_set"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "jump_to_dir_chooser",
+ "label": "",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "file-set:on_jump_to_dir_chooser_file_set"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "rbt_hal_unlock",
+ "label": "Use hal pin to unlock",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_rbt_unlock_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "rbt_no_unlock",
+ "label": "Do not use unlock code",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_rbt_unlock_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "rbt_use_unlock",
+ "label": "Use unlock code",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_rbt_unlock_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "rbtn_fullscreen",
+ "label": "Start as fullscreen",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_rbtn_fullscreen_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "rbtn_maximized",
+ "label": "Start maximized",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_rbtn_maximized_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "rbtn_no_run_from_line",
+ "label": "Do not use run from line",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_rbtn_run_from_line_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "rbtn_run_from_line",
+ "label": "Use run from line",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_rbtn_run_from_line_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "rbtn_show_offsets",
+ "label": "show offsets",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "rbtn_show_preview",
+ "label": "show preview",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_rbtn_show_preview_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "rbtn_window",
+ "label": "Start as window",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_rbtn_window_toggled"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "rel_colorbutton",
+ "label": "",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "color-set:on_rel_colorbutton_color_set"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "设置页控件",
+ "source": "Glade 静态",
+ "buttonId": "unhomed_colorbtn",
+ "label": "",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "color-set:on_unhomed_colorbtn_color_set"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_calibration",
+ "label": "Calibration",
+ "tooltip": "launch calibration",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_calibration_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_classicladder",
+ "label": "Cl.-ladder",
+ "tooltip": "Open classicladder",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_classicladder_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_delete",
+ "label": "",
+ "tooltip": "delete MDI history",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_delete_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_feed_100",
+ "label": "100%",
+ "tooltip": "reset feed override to 100 %",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_feed_100_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_hal_meter",
+ "label": "Hal Meter",
+ "tooltip": "launch hal meter",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_hal_meter_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_hal_scope",
+ "label": "Hal-Scope",
+ "tooltip": "launch hal scope",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_hal_scope_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_launch_test_message",
+ "label": "Launch test message",
+ "tooltip": "Push here to launch a test message / to test your settings.",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "pressed:on_btn_launch_test_message_pressed"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_none",
+ "label": "none",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_none_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_show_hal",
+ "label": "Halshow",
+ "tooltip": "opens the show hal tool",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_show_hal_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_spindle_100",
+ "label": "100%",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_spindle_100_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_status",
+ "label": "Status",
+ "tooltip": "launch linuxcnc status",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_status_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "btn_use_current",
+ "label": "current / file",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "clicked:on_btn_use_current_clicked"
+ ],
+ "notes": ""
+ },
+ {
+ "category": "Glade 静态按钮",
+ "source": "Glade 静态",
+ "buttonId": "chkbtn_hide_titlebar",
+ "label": "Hide title bar",
+ "tooltip": "",
+ "imageId": "",
+ "imageRole": "静态绑定",
+ "stateOrVariant": "默认",
+ "iconName": "",
+ "requestedSize": null,
+ "sourceType": "none",
+ "sourcePath": "",
+ "copiedFile": null,
+ "projectAsset": null,
+ "signals": [
+ "toggled:on_chkbtn_hide_titlebar_toggled"
+ ],
+ "notes": ""
+ }
+ ]
+}
diff --git a/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-icon-registry.js b/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-icon-registry.js
new file mode 100644
index 0000000..a6ab697
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-icon-registry.js
@@ -0,0 +1,146 @@
+const ICON_BASE = new URL("../assets/gmoccapy-icons/", import.meta.url).href;
+
+const ICONS = {
+ tbtn_estop: {
+ active: icon("main_switch_on", "img_emergency__main_switch_on__48.png"),
+ inactive: icon("main_switch_off", "img_emergency_off__main_switch_off__48.png"),
+ default: icon("main_switch_on", "img_emergency__main_switch_on__48.png"),
+ },
+ tbtn_on: {
+ active: icon("power_on", "img_machine_on__power_on__48.png"),
+ inactive: icon("power_off", "img_machine_off__power_off__48.png"),
+ default: icon("power_off", "img_machine_off__power_off__48.png"),
+ },
+ rbt_manual: {
+ active: icon("mode_manual_active", "img_manual_on__mode_manual_active__48.png"),
+ inactive: icon("mode_manual_inactive", "img_manual__mode_manual_inactive__48.png"),
+ default: icon("mode_manual_inactive", "img_manual__mode_manual_inactive__48.png"),
+ },
+ rbt_mdi: {
+ active: icon("mode_mdi_active", "img_mdi_on__mode_mdi_active__48.png"),
+ inactive: icon("mode_mdi_inactive", "img_mdi__mode_mdi_inactive__48.png"),
+ default: icon("mode_mdi_inactive", "img_mdi__mode_mdi_inactive__48.png"),
+ },
+ rbt_auto: {
+ active: icon("mode_auto_active", "img_auto_on__mode_auto_active__48.png"),
+ inactive: icon("mode_auto_inactive", "img_auto__mode_auto_inactive__48.png"),
+ default: icon("mode_auto_inactive", "img_auto__mode_auto_inactive__48.png"),
+ },
+ tbtn_setup: {
+ active: icon("mode_settings_active", "img_settings_on__mode_settings_active__48.png"),
+ inactive: icon("mode_settings_inactive", "img_settings__mode_settings_inactive__48.png"),
+ default: icon("mode_settings_inactive", "img_settings__mode_settings_inactive__48.png"),
+ },
+ tbtn_user_tabs: {
+ active: icon("mode_user_tabs_active", "img_user_tabs_on__mode_user_tabs_active__48.png"),
+ inactive: icon("mode_user_tabs_inactive", "img_user_tabs__mode_user_tabs_inactive__48.png"),
+ default: icon("mode_user_tabs_inactive", "img_user_tabs__mode_user_tabs_inactive__48.png"),
+ },
+ btn_load: { default: icon("open_file", "img_open__open_file__32.svg") },
+ btn_reload: { default: icon("refresh", "img_reload1__refresh__32.png") },
+ btn_run: { default: icon("play", "img_run__play__32.png") },
+ btn_stop: { default: icon("stop", "img_stop__stop__32.png") },
+ tbtn_pause: {
+ active: icon("pause_active", "img_pause_active__pause_active__32.png"),
+ inactive: icon("pause", "img_pause__pause__32.png"),
+ default: icon("pause", "img_pause__pause__32.png"),
+ },
+ btn_step: { default: icon("step", "img_step__step__32.png") },
+ btn_homing: { default: icon("ref_all", "img_ref_menu__ref_all__48.png") },
+ btn_tool: { default: icon("hsk_mill_tool", "img_tools__hsk_mill_tool__48.png") },
+ btn_touch: { default: icon("touch_off", "img_touch_off__touch_off__48.png") },
+ ref_all: { default: icon("ref_all", "img_ref_all__ref_all__48.png") },
+ btn_fullscreen: {
+ active: icon("fullscreen_close", "img_fullsize_preview0_close__fullscreen_close__48.png"),
+ inactive: icon("fullscreen_open", "img_fullsize_preview0_open__fullscreen_open__48.png"),
+ default: icon("fullscreen_open", "img_fullsize_preview0_open__fullscreen_open__48.png"),
+ },
+ rbt_forward: {
+ active: icon("spindle_right_on", "img_spindle_forward_on__spindle_right_on__48.png"),
+ inactive: icon("spindle_right", "img_spindle_forward__spindle_right__48.png"),
+ default: icon("spindle_right", "img_spindle_forward__spindle_right__48.png"),
+ },
+ rbt_reverse: {
+ active: icon("spindle_left_on", "img_spindle_reverse_on__spindle_left_on__48.png"),
+ inactive: icon("spindle_left", "img_spindle_reverse__spindle_left__48.png"),
+ default: icon("spindle_left", "img_spindle_reverse__spindle_left__48.png"),
+ },
+ rbt_stop: {
+ active: icon("spindle_stop_on", "img_spindle_stop_on__spindle_stop_on__48.png"),
+ inactive: icon("spindle_stop", "img_spindle_stop__spindle_stop__48.png"),
+ default: icon("spindle_stop_on", "img_spindle_stop_on__spindle_stop_on__48.png"),
+ },
+ tbtn_flood: {
+ active: icon("coolant_flood_active", "img_coolant_on__coolant_flood_active__48.png"),
+ inactive: icon("coolant_flood_inactive", "img_coolant_off__coolant_flood_inactive__48.png"),
+ default: icon("coolant_flood_inactive", "img_coolant_off__coolant_flood_inactive__48.png"),
+ },
+ tbtn_mist: {
+ active: icon("coolant_mist_active", "img_mist_on__coolant_mist_active__48.png"),
+ inactive: icon("coolant_mist_inactive", "img_mist_off__coolant_mist_inactive__48.png"),
+ default: icon("coolant_mist_inactive", "img_mist_off__coolant_mist_inactive__48.png"),
+ },
+ btn_zoom_in: { default: icon("zoom_in", "img_zoom_in__zoom_in__24.png") },
+ btn_zoom_out: { default: icon("zoom_out", "img_zoom_out__zoom_out__24.png") },
+ rbt_view_x: { default: icon("tool_axis_x", "img_view_x__tool_axis_x__24.png") },
+ rbt_view_y: { default: icon("tool_axis_y", "img_view_y__tool_axis_y__24.png") },
+ rbt_view_z: { default: icon("tool_axis_z", "img_view_z__tool_axis_z__24.png") },
+ rbt_view_p: { default: icon("tool_axis_p", "img_view_p__tool_axis_p__24.png") },
+ tbtn_view_tool_path: { default: icon("toolpath", "img_tool_path__toolpath__24.png") },
+ tbtn_view_dimension: { default: icon("dimensions", "img_dimensions__dimensions__24.png") },
+ btn_exit: { default: icon("logout", "img_close__logout__48.png") },
+ btn_save_session: { default: icon("save", "img_tool_save__save__32.png") },
+};
+
+export const GMOCAPY_ICON_REGISTRY_BOUNDARY = "gmoccapy_icon_registry_from_working3_manifest_core_ui_subset";
+
+export function getGmoccapyIcon(buttonId, variant = "default") {
+ const variants = ICONS[buttonId] || null;
+ const fallback = {
+ buttonId,
+ variant,
+ iconName: "text_fallback",
+ path: null,
+ fallback: true,
+ semanticBoundary: GMOCAPY_ICON_REGISTRY_BOUNDARY,
+ };
+ if (!variants) return fallback;
+ const normalizedVariant = normalizeVariant(variant);
+ const resolved = variants[normalizedVariant] || variants.default || variants.inactive || variants.active || null;
+ if (!resolved) return fallback;
+ return {
+ buttonId,
+ variant: normalizedVariant,
+ ...resolved,
+ fallback: false,
+ semanticBoundary: GMOCAPY_ICON_REGISTRY_BOUNDARY,
+ };
+}
+
+export function listGmoccapyIconRegistryEntries() {
+ return Object.entries(ICONS).flatMap(([buttonId, variants]) => (
+ Object.entries(variants).map(([variant, entry]) => ({
+ buttonId,
+ variant,
+ ...entry,
+ semanticBoundary: GMOCAPY_ICON_REGISTRY_BOUNDARY,
+ }))
+ ));
+}
+
+function icon(iconName, filename) {
+ return {
+ iconName,
+ filename,
+ path: `${ICON_BASE}${filename}`,
+ };
+}
+
+function normalizeVariant(variant) {
+ if (variant === true) return "active";
+ if (variant === false) return "inactive";
+ const value = String(variant || "default").toLowerCase();
+ if (["active", "on", "selected", "state_on", "paused", "fullscreen active"].includes(value)) return "active";
+ if (["inactive", "off", "default", "state_estop_reset"].includes(value)) return value === "default" ? "default" : "inactive";
+ return value;
+}
diff --git a/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js b/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
index c26bf3f..6fd3eed 100644
--- a/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
+++ b/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
@@ -1,5 +1,8 @@
import { renderFiveAxisScene } from "../visualization/five-axis-scene.js";
import { gateLinuxCncTaskAction } from "../state/linuxcnc-task-policy.js";
+import { gmoccapyCommunicationModel, createGmoccapyCommunicationSummary } from "../runtime/gmoccapy-communication-model.js";
+import { gmoccapyHalModel, createGmoccapyHalSummary } from "../runtime/gmoccapy-hal-model.js";
+import { getGmoccapyIcon } from "./gmoccapy-icon-registry.js";
const REGIONS = [
"titlebar",
@@ -104,11 +107,11 @@ function renderPreview(element, state, dispatch) {
-
-
-
-
-
+ ${iconButtonHtml({ buttonId: "rbt_view_x", action: "view-x", label: "X", title: "View X", active: state.preview.selectedView === "x" })}
+ ${iconButtonHtml({ buttonId: "rbt_view_y", action: "view-y", label: "Y", title: "View Y", active: state.preview.selectedView === "y" })}
+ ${iconButtonHtml({ buttonId: "rbt_view_z", action: "view-z", label: "Z", title: "View Z", active: state.preview.selectedView === "z" })}
+ ${iconButtonHtml({ buttonId: "rbt_view_p", action: "reset-view", label: "Fit", title: "Fit perspective", active: state.preview.selectedView === "iso" })}
+ ${iconButtonHtml({ buttonId: "tbtn_view_tool_path", action: "clear-preview", label: "Clear", title: "Clear preview path" })}
`;
@@ -145,12 +148,11 @@ function renderPreview(element, state, dispatch) {
const toolbar = element.querySelector("[data-preview-points]");
if (toolbar) {
toolbar.dataset.previewPoints = String(state.preview.pathPoints);
- for (const view of ["x", "y", "z"]) {
- toolbar.querySelector(`[data-action="view-${view}"]`)?.setAttribute(
- "data-active",
- state.preview.selectedView === view ? "true" : "false",
- );
- }
+ updateIconButton(toolbar.querySelector('[data-action="view-x"]'), "rbt_view_x", state.preview.selectedView === "x");
+ updateIconButton(toolbar.querySelector('[data-action="view-y"]'), "rbt_view_y", state.preview.selectedView === "y");
+ updateIconButton(toolbar.querySelector('[data-action="view-z"]'), "rbt_view_z", state.preview.selectedView === "z");
+ updateIconButton(toolbar.querySelector('[data-action="reset-view"]'), "rbt_view_p", state.preview.selectedView === "iso");
+ updateIconButton(toolbar.querySelector('[data-action="clear-preview"]'), "tbtn_view_tool_path", state.preview.pathPoints > 0);
}
const canvas = element.querySelector("[data-five-axis-canvas]");
@@ -345,16 +347,18 @@ function mdiQuickCommands(state) {
}
function renderSidebar(element, state, dispatch) {
+ const tcpKinsType = tcpKinsTypeForProfile(state.profile);
+ const tcpDisabled = tcpKinsType ? "" : ` disabled title="${escapeHtml(`${state.profile.id} is ${state.profile.kinematics} reference only`)}"`;
element.innerHTML = `
-
-
-
-
-
-
-
+ ${iconButtonHtml({ buttonId: "tbtn_estop", action: "estop", label: "E-STOP", title: "Estop the machine / F1", className: "sidebar-button estop", active: state.machine.estopActive })}
+ ${iconButtonHtml({ buttonId: "tbtn_on", action: "power", label: "POWER", title: "Turn the machine on/off / F2", className: "sidebar-button power", active: state.machine.powerOn })}
+ ${iconButtonHtml({ buttonId: "tbtn_setup", action: "reset", label: "RESET", title: "Reset estop and return machine off", className: "sidebar-button", active: false })}
+ ${iconButtonHtml({ buttonId: "rbt_auto", action: "mode-auto", label: "AUTO", title: "Enter auto mode", className: "sidebar-button", active: state.machine.mode === "auto" })}
+ ${iconButtonHtml({ buttonId: "rbt_manual", action: "mode-manual", label: "MANUAL", title: "Enter manual mode", className: "sidebar-button", active: state.machine.mode === "manual" })}
+ ${iconButtonHtml({ buttonId: "tbtn_user_tabs", action: "mode-jog", label: "JOG", title: "Manual jog page", className: "sidebar-button", active: state.machine.mode === "manual" })}
+ ${iconButtonHtml({ buttonId: "rbt_mdi", action: "mode-mdi", label: "MDI", title: "Enter MDI mode", className: "sidebar-button", active: state.machine.mode === "mdi" })}
-
+
`;
@@ -369,7 +373,7 @@ function renderSidebar(element, state, dispatch) {
dispatch({ type: "SET_KINS_TYPE", kinsType: "identity" });
});
element.querySelector('[data-action="kins-tcp"]').addEventListener("click", () => {
- const tcpKinsType = state.profile.kinematicsParameters.switchkinsTypes.find((type) => type.value === 1)?.webKinsType || "tcp-xyzac";
+ if (!tcpKinsType) return;
dispatch({ type: "SET_KINS_TYPE", kinsType: tcpKinsType });
});
}
@@ -377,11 +381,13 @@ function renderSidebar(element, state, dispatch) {
function renderInfoTabs(element, state) {
const frame = state.rtcpFrame;
const taskPolicy = state.linuxCncTaskPolicy;
+ const commSummary = createGmoccapyCommunicationSummary();
+ const halSummary = createGmoccapyHalSummary();
element.innerHTML = `
- Size:
- ${state.fileSizeBytes} bytes
@@ -431,6 +437,18 @@ function renderInfoTabs(element, state) {
- Source map:
- ${state.linuxCncBoundaryAdapter.sourceSummary.referenceCount} refs / ${state.linuxCncBoundaryAdapter.sourceSummary.sourceRequiredCount} source files
- Profile summary:
- ${state.linuxCncBoundaryAdapter.profileSummary.coordinates} / ${state.linuxCncBoundaryAdapter.profileSummary.jointCount} joints / ${state.linuxCncBoundaryAdapter.profileSummary.toolCount} tools
- Boundary ready:
- ${state.linuxCncBoundaryReadiness.ready ? "ready" : "blocked"} (${state.linuxCncBoundaryReadiness.missing.join(", ")})
+ - gmoccapy comms:
- ${gmoccapyCommunicationModel.semanticBoundary}
+ - NML command:
- ${formatGmoccapyNativePath("command")}
+ - NML status:
- ${formatGmoccapyNativePath("status")}
+ - Web runtime:
- ${gmoccapyCommunicationModel.webPath.path.join(" -> ")}
+ - postgui order:
- ${commSummary.postguiAfterHalcompReady ? "halcomp.ready before POSTGUI_HALFILE" : "pending"}
+ - native RUN:
- ${formatGmoccapyActionMapping("auto-run")}
+ - HAL model:
- ${gmoccapyHalModel.semanticBoundary}
+ - HAL files:
- ${halSummary.halFileCount} files / POSTGUI ${halSummary.postguiRequiresHalcompReady ? "after halcomp.ready" : "pending"}
+ - HAL pins:
- ${halSummary.nativePinGroupCount} groups / ${halSummary.nativePinCount} pins
+ - postgui nets:
- ${gmoccapyHalModel.postguiNets.map((net) => net.signal).join(" / ")}
+ - Tool loop:
- ${halSummary.hasToolChangeSimulationLoop ? "simulated tool-change loop" : "pending"}
+ - Operator:
- ${escapeHtml(state.operatorMessage)}
`;
}
@@ -603,15 +621,25 @@ function overrideControl(label, target, value, prefix = "") {
}
function renderSpindleCoolant(element, state, dispatch) {
+ const forwardGate = gateLinuxCncTaskAction(state, { type: "SPINDLE", direction: "forward" });
+ const reverseGate = gateLinuxCncTaskAction(state, { type: "SPINDLE", direction: "reverse" });
+ const stopGate = gateLinuxCncTaskAction(state, { type: "SPINDLE", direction: "stop" });
+ const floodGate = gateLinuxCncTaskAction(state, { type: "TOGGLE_COOLANT", kind: "flood" });
+ const mistGate = gateLinuxCncTaskAction(state, { type: "TOGGLE_COOLANT", kind: "mist" });
element.innerHTML = `
Cooling
-
-
+ ${iconButtonHtml({ buttonId: "tbtn_flood", action: "toggle-flood", label: "Flood", title: floodGate.operatorMessage || "Flood coolant", active: state.coolant.flood, className: state.coolant.flood ? "active" : "", gate: floodGate })}
+ ${iconButtonHtml({ buttonId: "tbtn_mist", action: "toggle-mist", label: "Mist", title: mistGate.operatorMessage || "Mist coolant", active: state.coolant.mist, className: state.coolant.mist ? "active" : "", gate: mistGate })}
Spindle
${state.spindle.rpm} rpm
+
+ ${iconButtonHtml({ buttonId: "rbt_forward", action: "spindle-forward", label: "FWD", title: forwardGate.operatorMessage || "Spindle forward", active: state.spindle.direction === "forward", gate: forwardGate })}
+ ${iconButtonHtml({ buttonId: "rbt_stop", action: "spindle-stop", label: "STOP", title: stopGate.operatorMessage || "Spindle stop", active: !state.spindle.enabled || state.spindle.direction === "stop", gate: stopGate })}
+ ${iconButtonHtml({ buttonId: "rbt_reverse", action: "spindle-reverse", label: "REV", title: reverseGate.operatorMessage || "Spindle reverse", active: state.spindle.direction === "reverse", gate: reverseGate })}
+
${state.spindle.override} %
@@ -633,6 +661,15 @@ function renderSpindleCoolant(element, state, dispatch) {
element.querySelector('[data-action="spindle-override-up"]').addEventListener("click", () => {
dispatch({ type: "ADJUST_SPINDLE_OVERRIDE", delta: 10 });
});
+ element.querySelector('[data-action="spindle-forward"]').addEventListener("click", () => {
+ dispatch({ type: "SET_SPINDLE_DIRECTION", direction: "forward" });
+ });
+ element.querySelector('[data-action="spindle-reverse"]').addEventListener("click", () => {
+ dispatch({ type: "SET_SPINDLE_DIRECTION", direction: "reverse" });
+ });
+ element.querySelector('[data-action="spindle-stop"]').addEventListener("click", () => {
+ dispatch({ type: "SET_SPINDLE_DIRECTION", direction: "stop" });
+ });
}
function renderBottomControls(element, state, dispatch) {
@@ -669,7 +706,15 @@ function renderBottomControls(element, state, dispatch) {
const blockedAttrs = gate.allowed
? ` data-command-ready="true" aria-disabled="false"`
: ` data-command-ready="false" aria-disabled="${promptable ? "true" : "false"}"`;
- return `
`;
+ return iconButtonHtml({
+ buttonId: bottomIconButtonId(action),
+ action,
+ label,
+ title: gate.allowed ? label : gate.operatorMessage || "blocked",
+ active: bottomControlActive(state, action),
+ disabled: Boolean(disabled),
+ attrs: blockedAttrs,
+ });
})
.join("")}
`;
@@ -714,6 +759,99 @@ function bottomControlGate(state, action) {
return gateLinuxCncTaskAction(state, actionMap[action] || { type: "UI_CONTROL" });
}
+function bottomIconButtonId(action) {
+ return {
+ OPEN: "btn_load",
+ RELOAD: "btn_reload",
+ RUN_READY: "ref_all",
+ RUN: "btn_run",
+ STOP: "btn_stop",
+ PAUSE: "tbtn_pause",
+ RESUME: "btn_run",
+ STEP: "btn_step",
+ HOME: "btn_homing",
+ JOG_X_NEG: "rbt_view_x",
+ JOG_X_POS: "rbt_view_x",
+ JOG_Y_NEG: "rbt_view_y",
+ JOG_Y_POS: "rbt_view_y",
+ MDI_RUN: "rbt_mdi",
+ SAVE_SESSION: "btn_save_session",
+ RESTORE_SESSION: "btn_reload",
+ AUDIT_FULL_BOUNDARY: "tbtn_setup",
+ FULL: "btn_fullscreen",
+ }[action] || "btn_load";
+}
+
+function bottomControlActive(state, action) {
+ if (action === "PAUSE") return state.runState === "paused";
+ if (action === "RUN" || action === "RESUME") return state.runState === "running";
+ if (action === "STEP") return state.runState === "stepping";
+ if (action === "HOME") return state.machine.allHomed;
+ if (action === "FULL") return state.preview.fullscreen;
+ return false;
+}
+
+function iconButtonHtml({
+ buttonId,
+ action,
+ label,
+ title = label,
+ active = false,
+ className = "",
+ gate = null,
+ disabled = false,
+ attrs = "",
+} = {}) {
+ const icon = getGmoccapyIcon(buttonId, active ? "active" : "inactive");
+ const disabledAttr = disabled || gate?.allowed === false ? " disabled" : "";
+ const readyAttr = gate
+ ? ` data-command-ready="${gate.allowed ? "true" : "false"}" aria-disabled="${gate.allowed ? "false" : "true"}"`
+ : "";
+ const labelText = escapeHtml(label || icon.iconName);
+ const titleAttr = title ? ` title="${escapeHtml(title)}"` : "";
+ const classAttr = className ? ` class="${escapeHtml(className)}"` : "";
+ const iconMarkup = icon.path
+ ? `
})
`
+ : `
${escapeHtml((label || "?").slice(0, 3))}`;
+ return `
+
+ `;
+}
+
+function updateIconButton(button, buttonId, active) {
+ if (!button) return;
+ const icon = getGmoccapyIcon(buttonId, active ? "active" : "inactive");
+ button.dataset.active = active ? "true" : "false";
+ button.dataset.iconName = icon.iconName;
+ button.dataset.iconVariant = icon.variant;
+ const image = button.querySelector("img");
+ if (image && icon.path) image.src = icon.path;
+}
+
+function formatGmoccapyNativePath(kind) {
+ const entry = gmoccapyCommunicationModel.nativePaths[kind];
+ if (!entry) return "pending";
+ return `${entry.label}: ${entry.path.join(" -> ")}`;
+}
+
+function formatGmoccapyActionMapping(actionId) {
+ const mapping = gmoccapyCommunicationModel.actionMappings.find((item) => item.actionId === actionId);
+ if (!mapping) return "pending";
+ return `${mapping.nativeCommand} / ${mapping.gate}`;
+}
+
function formatNumber(value, digits = 3) {
return Number(value).toFixed(digits);
}
@@ -724,6 +862,13 @@ function formatAxisLimitSummary(axisLimits) {
.join(" ");
}
+function tcpKinsTypeForProfile(profile) {
+ if (profile?.tcpCapable === false) return null;
+ return profile?.kinematicsParameters?.switchkinsTypes
+ ?.find((type) => type.value === 1 && String(type.webKinsType || "").startsWith("tcp-"))
+ ?.webKinsType || null;
+}
+
function escapeHtml(value) {
return String(value)
.replaceAll("&", "&")
diff --git a/web-rtcp-5axis-sim-plan/docs/gmoccapy-xyzab-reference.md b/web-rtcp-5axis-sim-plan/docs/gmoccapy-xyzab-reference.md
new file mode 100644
index 0000000..29c3a08
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/docs/gmoccapy-xyzab-reference.md
@@ -0,0 +1,174 @@
+# gmoccapy XYZAB Reference
+
+Date: 2026-06-26
+
+This document records how the LinuxCNC `gmoccapy_XYZAB.ini` simulation maps into
+the Web RTCP 5-axis simulator. It is a reference boundary, not a browser port of
+the native gmoccapy GTK runtime.
+
+## Source Inputs
+
+Primary input:
+
+```text
+work/working3/gmoccapy_XYZAB_execution_analysis.md
+linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini
+linuxcnc/configs/sim/gmoccapy/core_sim_XYZAB.hal
+linuxcnc/configs/sim/gmoccapy/spindle_sim.hal
+linuxcnc/configs/sim/gmoccapy/simulated_home.hal
+linuxcnc/configs/sim/gmoccapy/gmoccapy_postgui.hal
+work/working3/gmoccapy_button_icons/button_icon_inventory.csv
+work/working3/gmoccapy_button_icons/files/
+```
+
+Project assets generated from those inputs:
+
+```text
+app/src/ui-reference/gmoccapy-button-icons.json
+app/src/assets/gmoccapy-icons/
+app/src/profiles/gmoccapy-xyzab.js
+app/src/runtime/gmoccapy-communication-model.js
+app/src/runtime/gmoccapy-hal-model.js
+```
+
+## Native LinuxCNC Configuration
+
+`gmoccapy_XYZAB.ini` defines a simulated five-axis machine:
+
+- UI: `[DISPLAY] DISPLAY = gmoccapy`.
+- UI poll cycle: `[DISPLAY] CYCLE_TIME = 100` ms.
+- Task process: `[TASK] TASK = milltask`.
+- Motion module: `[EMCMOT] EMCMOT = motmod`.
+- Realtime periods: `BASE_PERIOD = 100000` ns, `SERVO_PERIOD = 1000000` ns.
+- Coordinates: `[TRAJ] COORDINATES = X Y Z A B`.
+- Kinematics: `[KINS] KINEMATICS = trivkins coordinates=xyzab`.
+- Joint count: `[KINS] JOINTS = 5`.
+- Homing policy: `[TRAJ] NO_FORCE_HOMING = 0`.
+- HALUI: `[HAL] HALUI = halui`.
+
+The native profile is a `trivkins` simulation. It is not a TCP/RTCP proof and
+must not be promoted as source-derived table-rotary-tilting kinematics.
+
+## Native Startup Order
+
+The native command is:
+
+```bash
+linuxcnc linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini
+```
+
+The LinuxCNC script performs these stages:
+
+1. Parse INI and export `CONFIG_DIR`, `INI_FILE_NAME`, and config-local `PATH`.
+2. Start `linuxcncsvr -ini
` for NML buffers.
+3. Start realtime, RTAPI, and HAL.
+4. Load task process with `halcmd loadusr -Wn inihal milltask -ini `.
+5. Load `halui` with `halcmd loadusr -Wn halui halui -ini `.
+6. Execute normal `[HAL] HALFILE` entries:
+ `core_sim_XYZAB.hal`, `spindle_sim.hal`, and `simulated_home.hal`.
+7. Run `halcmd start`.
+8. Start the display with `gmoccapy -ini `.
+9. After gmoccapy creates `gmoccapy.*` pins and calls `halcomp.ready()`, execute
+ `[HAL] POSTGUI_HALFILE = gmoccapy_postgui.hal`.
+
+The postgui file is order-sensitive because it references `gmoccapy.*` pins that
+do not exist during ordinary HALFILE loading.
+
+## Native Communication Model
+
+gmoccapy talks to LinuxCNC through local LinuxCNC mechanisms:
+
+- Command path: `gmoccapy -> linuxcnc.command() -> NML emcCommand -> milltask`.
+- Status path: `milltask -> NML emcStatus/emcError -> linuxcnc.stat()/error_channel()`.
+- HAL path: `gmoccapy.* pins <-> HAL shared memory`.
+- HALUI path: `halui` exposes LinuxCNC commands and status as HAL pins.
+- REMAP path: `[RS274NGC] REMAP` and `[PYTHON]` run inside the task/interpreter
+ side, not inside the GUI.
+
+The Web application does not connect to native NML buffers, HAL shared memory, or
+realtime threads. Web buttons dispatch through `store.dispatch`, task policy
+gates, source-derived runtimes where available, and UI status snapshots.
+
+## HAL Topology
+
+`core_sim_XYZAB.hal` loads `trivkins` and `motmod`, then loops commanded joint
+positions directly back to feedback:
+
+```text
+joint.0 X command -> joint.0 feedback
+joint.1 Y command -> joint.1 feedback
+joint.2 Z command -> joint.2 feedback
+joint.3 A command -> joint.3 feedback
+joint.4 B command -> joint.4 feedback
+```
+
+It also creates the simulation loops:
+
+- `iocontrol.0.user-enable-out -> iocontrol.0.emc-enable-in`.
+- `iocontrol.0.tool-prepare -> iocontrol.0.tool-prepared`.
+- `iocontrol.0.tool-change -> iocontrol.0.tool-changed`.
+- `spindle.0.forward`, `spindle.0.reverse`.
+- `iocontrol.0.coolant-flood`, `iocontrol.0.coolant-mist`.
+
+`spindle_sim.hal` simulates speed feedback:
+
+```text
+spindle.0.speed-out -> limit2 -> sim_encoder -> encoder -> spindle.0.speed-in
+near_speed.out -> spindle.0.at-speed
+```
+
+`simulated_home.hal` creates simulated X/Y/Z home switches at X=1, Y=0.5, Z=2.
+
+`gmoccapy_postgui.hal` maps native HAL state into GUI pins:
+
+- `gmoccapy.spindle_feedback_bar`.
+- `gmoccapy.spindle_at_speed_led`.
+- `gmoccapy.tooloffset-x`.
+- `gmoccapy.tooloffset-z`.
+
+It also re-establishes the simulated tool-change loop after unlinking conflicting
+pins.
+
+## G-code Gates
+
+Because `NO_FORCE_HOMING = 0`, MDI and Auto require the normal LinuxCNC sequence:
+
+```text
+reset estop
+machine on
+home all axes
+load a program or MDI command
+switch to the correct mode
+interpreter idle
+run MDI or AUTO_RUN
+```
+
+The native stack enforces this in three layers:
+
+1. gmoccapy widget sensitivity and page state.
+2. gladevcp actions and gmoccapy callback checks.
+3. LinuxCNC task, interpreter, motion, IO, and HAL validation.
+
+The Web project mirrors this as UI disabled reasons, `linuxcnc-task-policy`
+gates, and runtime preconditions. The `gmoccapy-xyzab` profile is allowed as a
+reference selection, but full LinuxCNC 5-axis source-derived RUN proof remains
+limited to profiles that have project runtime support.
+
+## Button Icon Boundary
+
+The icon inventory from `working3/gmoccapy_button_icons` was converted into a
+project-local manifest:
+
+```text
+app/src/ui-reference/gmoccapy-button-icons.json
+```
+
+The copied icons live in:
+
+```text
+app/src/assets/gmoccapy-icons/
+```
+
+These assets prove UI origin and visual mapping only. Button correctness still
+depends on the Web store, task policy, runtime state, and browser interaction
+evidence.
diff --git a/web-rtcp-5axis-sim-plan/docs/traceability-matrix.md b/web-rtcp-5axis-sim-plan/docs/traceability-matrix.md
index ff05fe0..7eb153e 100644
--- a/web-rtcp-5axis-sim-plan/docs/traceability-matrix.md
+++ b/web-rtcp-5axis-sim-plan/docs/traceability-matrix.md
@@ -39,6 +39,10 @@ CNC 语义必须来自 LinuxCNC source/WASM/source-derived boundary;
| `xyzac-trt` profile | `app/src/profiles/xyzac-trt.js` | `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini` | source/config reference | profile boundary node smoke |
| `xyzac-trt` source reference map | `app/src/profiles/source-reference-map.js` | `xyzac-trt.ini`, `xyzac-trt.xml`, `switchkins_postgui.hal`, `xyzac-trt_cmds.hal`, `xyzac-trt-kins.c`, `trtfuncs.c`, `switchkins.c` | profile/source map only, not runtime proof | profile boundary node smoke |
| `xyzbc-trt` profile | `app/src/profiles/xyzbc-trt.js` | `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini` | source/config reference + runtime module switch | profile node smoke + browser profile switch smoke |
+| `gmoccapy-xyzab` reference profile | `app/src/profiles/gmoccapy-xyzab.js` | `linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini`, `core_sim_XYZAB.hal`, `spindle_sim.hal`, `simulated_home.hal`, `gmoccapy_postgui.hal` | gmoccapy/trivkins reference profile only; not RTCP proof | gmoccapy XYZAB profile node smoke |
+| gmoccapy_XYZAB NML/HAL communication model | `app/src/runtime/gmoccapy-communication-model.js`, `docs/gmoccapy-xyzab-reference.md` | `gmoccapy.py`, `linuxcnc.command/stat/error_channel`, `milltask`, HALUI, POSTGUI_HALFILE startup order | native communication reference mapped to Web store/task policy; browser does not attach to NML/HAL shared memory | gmoccapy communication model node smoke |
+| gmoccapy_XYZAB HAL/postgui diagnostics model | `app/src/runtime/gmoccapy-hal-model.js`, `docs/gmoccapy-xyzab-reference.md` | `core_sim_XYZAB.hal`, `spindle_sim.hal`, `simulated_home.hal`, `gmoccapy_postgui.hal` | HAL pin/postgui reference and Web diagnostics only | gmoccapy HAL model node smoke |
+| gmoccapy button icon manifest | `app/src/ui-reference/gmoccapy-button-icons.json`, `app/src/assets/gmoccapy-icons/` | `work/working3/gmoccapy_button_icons/button_icon_inventory.csv`, copied classic theme and macro image files | UI asset provenance only; button semantics still require task policy/runtime evidence | gmoccapy icon manifest node smoke |
| SWITCHKINS panel | `app/src/panel-schema/xyzac-trt-pyvcp.js` | `xyzac-trt.xml`, `switchkins_postgui.hal` | UI/HAL binding reference | profile boundary node smoke + browser DOM smoke |
| M428/M429/M430 state | `app/src/profiles/xyzac-trt.js`, `app/src/panel-schema/xyzac-trt-pyvcp.js` | `remap_subs/428remap.ngc`, `429remap.ngc`, `430remap.ngc` | LinuxCNC remap/source reference only until runtime adapter is connected | profile boundary node smoke |
| LinuxCNC boundary adapter | `app/src/runtime/linuxcnc-boundary-adapter.js` | LinuxCNC interpreter/kinematics/TP WASM adapter point | kinematics + interpreter + TP timing runtime connected; task/motion/HAL simulation runtime connected separately | profile boundary node smoke + browser DOM smoke |
@@ -77,6 +81,11 @@ src/hal/user_comps/vismach/5axisgui.py
src/hal/user_comps/vismach/xyzac-trt-gui.py
src/hal/user_comps/vismach/xyzbc-trt-gui.py
configs/sim/gmoccapy/gmoccapy_XYZAC.ini
+configs/sim/gmoccapy/gmoccapy_XYZAB.ini
+configs/sim/gmoccapy/core_sim_XYZAB.hal
+configs/sim/gmoccapy/spindle_sim.hal
+configs/sim/gmoccapy/simulated_home.hal
+configs/sim/gmoccapy/gmoccapy_postgui.hal
configs/sim/qtvcp_screens/qtdragon/README
```
diff --git a/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md b/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
index 716cf72..47bd685 100644
--- a/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
+++ b/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md
@@ -3792,3 +3792,923 @@ Final log-sync step:
- `记录云仓库同步日志`
- The final action is to push that log commit to:
- `origin/master`
+
+---
+
+## GPT/Codex execution log - 2026-06-26 10:10:14 EDT
+
+User request:
+- 使用 `/home/meswork/cnc_wams/work/working3` 的资料,编写完善
+ `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan` 功能的文档。
+- 文档要求覆盖:
+ - 01 功能内容。
+ - 02 程序开发详细步骤。
+ - 03 推进台账:每轮做了什么、改了哪些文件、验证了什么、下一步是什么。
+ - 04 任务矩阵:所有任务编号、状态、验收标准,防止重复做。
+ - 05 验收证据:命令、页面、job_id、report_id、PDF、截图等证据。
+ - 06 决策记录:为什么这么做,后续变更时不反复争论同一个问题。
+- 文档创建在 `/home/meswork/cnc_wams/work/working4`。
+
+Workspace and rules:
+- Working directory: `/home/meswork/cnc_wams`.
+- AGENTS.md logging rule requires appending the full execution process log to:
+ `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`.
+- Filesystem access: unrestricted.
+- Approval policy: never.
+
+Discovery commands executed:
+- `find /home/meswork/cnc_wams/work/working3 -maxdepth 3 -type f | sort`
+ - Confirmed `working3` contains:
+ - `gmoccapy_XYZAB_execution_analysis.md`
+ - `gmoccapy_button_icons/README.md`
+ - `gmoccapy_button_icons/button_icon_inventory.csv`
+ - `gmoccapy_button_icons/button_icon_inventory.md`
+ - `gmoccapy_button_icons/files/` with copied PNG/SVG icon assets.
+- `find /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan -maxdepth 3 -type f | sort | sed 's#^#/##'`
+ - Confirmed target project contains README, docs, app sources, build readiness artifact, tests, working notes, run evidence, and `gptlog-process/gpdlog.md`.
+- `find /home/meswork/cnc_wams/work -maxdepth 2 -type d | sort`
+ - Confirmed `work/working4` directory exists.
+- `find /home/meswork/cnc_wams/work/working4 -maxdepth 2 -type f -print | sort`
+ - Confirmed `working4` was empty before writing documents.
+
+Source documents read:
+- `sed -n '1,240p' /home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md`
+- `sed -n '240,520p' /home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md`
+- `sed -n '520,900p' /home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md`
+ - Extracted LinuxCNC/gmoccapy_XYZAB startup order, NML command/status/error communication, HAL/postgui behavior, HALUI role, G-code run conditions, task/mode/interpreter/home interlocks, and button/control logic.
+- `sed -n '1,220p' /home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/README.md`
+ - Confirmed icon inventory structure and naming conventions.
+- `sed -n '1,220p' /home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.md`
+ - Reviewed icon inventory summary and representative rows.
+- `head -n 30 /home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.csv`
+ - Reviewed CSV field names and button/icon mapping examples.
+- `wc -l /home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md /home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.csv /home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.md`
+ - Confirmed line counts:
+ - `gmoccapy_XYZAB_execution_analysis.md`: 1287 lines.
+ - `button_icon_inventory.csv`: 212 lines.
+ - `button_icon_inventory.md`: 247 lines.
+
+Target project context read:
+- `sed -n '1,240p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/README.md`
+ - Confirmed project goal: Web RTCP five-axis simulation, not a real machine controller or native LinuxCNC replacement.
+- `sed -n '1,260p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/docs/technical-roadmap.md`
+ - Confirmed technology route: native HTML/CSS/ES modules, Three.js, LinuxCNC source-derived WASM boundaries, gmoccapy-style shell.
+- `sed -n '1,260p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/docs/traceability-matrix.md`
+ - Confirmed existing traceability records for gmoccapy UI, run controls, LinuxCNC kinematics/interpreter/task-HAL runtime, profiles, OPFS/session, full execution boundary, and native task/HAL readiness.
+- `sed -n '1,260p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/main.js`
+- `sed -n '260,620p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/main.js`
+ - Confirmed app attaches profile INI config, kinematics runtime, interpreter runtime, task/HAL runtime, machine-file staging, and default LinuxCNC program preview.
+- `find /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src -maxdepth 3 -type f | sort`
+ - Confirmed current module layout: profiles, runtime, state, UI, styles, visualization, vendor Three.js.
+- `sed -n '1,260p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/working_run/12-working-run-final-completion-record.md`
+ - Confirmed RUN chain, preconditions, status loop, unit conversion, browser evidence, and working_run remaining tasks were previously closed.
+- `sed -n '1,260p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/docs/native-task-hal-sync-implementation-steps.md`
+ - Reviewed existing native task/HAL synchronization plan and promotion boundaries.
+- `sed -n '1,220p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/package.json`
+ - Confirmed build, dev, browser smoke, and node smoke scripts.
+- `sed -n '1,260p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/profiles/index.js`
+- `sed -n '1,260p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/profiles/xyzac-trt.js`
+- `sed -n '1,260p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/profiles/xyzbc-trt.js`
+ - Confirmed current profile set is `xyzac-trt` and `xyzbc-trt`; no gmoccapy_XYZAB profile exists yet.
+- `rg -n "icon|button|gmoccapy|status-sidebar|bottom|run|pause|stop|home|tool|touch|coolant|spindle" /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
+- `sed -n '1,260p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
+ - Confirmed UI has text-based controls and existing regions for titlebar, preview, DRO, G-code, status sidebar, info tabs, override, spindle/coolant, bottom controls.
+- `sed -n '1,280p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/state/store.js`
+ - Confirmed store state includes machine state, run state, kinematics/interpreter/task-HAL runtime status, machine-file staging, DRO, feed, spindle, coolant, preview, tool preview.
+- `sed -n '1,240p' /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs`
+ - Confirmed existing run-precondition tests cover profile/INI/kinematics/machine-file readiness and task policy gates.
+
+Existing evidence paths checked:
+- `find /home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output -maxdepth 2 -type f | sort`
+ - Confirmed existing QA JSON/PDF/DOCX/screenshots including button control evidence, cloud run evidence, native task/HAL comparison, toolpath preview cases, run preconditions feedback, and LinuxCNC source run reports.
+- `find /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/build -maxdepth 3 -type f | sort`
+ - Confirmed `build/readiness/native-task-hal-readiness.json`.
+- `find /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/working_run/run-feedback-evidence -maxdepth 1 -type f | sort`
+ - Confirmed `run-feedback-local.json` and `run-feedback-local.png`.
+
+Files created with `apply_patch`:
+- `/home/meswork/cnc_wams/work/working4/README.md`
+ - Index for the generated working4 document set.
+- `/home/meswork/cnc_wams/work/working4/01-function-content.md`
+ - Functional scope, current project state, working3 value, proposed feature packages, non-goals, success definition.
+- `/home/meswork/cnc_wams/work/working4/02-program-development-steps.md`
+ - Development plan by stages: asset ingestion, gmoccapy_XYZAB profile, communication diagnostics, icon mapping, gates, HAL/postgui diagnostics, browser evidence.
+- `/home/meswork/cnc_wams/work/working4/03-progress-ledger.md`
+ - Progress ledger with initial W4-DOC-001 entry and future entry template.
+- `/home/meswork/cnc_wams/work/working4/04-task-matrix.md`
+ - Task matrix with IDs, statuses, files, and acceptance standards.
+- `/home/meswork/cnc_wams/work/working4/05-acceptance-evidence.md`
+ - Evidence ledger for current document output, reusable existing evidence, future required evidence, job/report table, and gaps.
+- `/home/meswork/cnc_wams/work/working4/06-decision-records.md`
+ - Decision records ADR-001 through ADR-008 covering source input policy, no GTK port, XYZAB/trivkins boundary, NML/HAL diagnostics, icons vs semantics, three-layer gate model, postgui ordering, and evidence-first promotion.
+
+Validation commands executed:
+- `find /home/meswork/cnc_wams/work/working4 -maxdepth 1 -type f -printf '%f\n' | sort`
+ - Confirmed files:
+ - `01-function-content.md`
+ - `02-program-development-steps.md`
+ - `03-progress-ledger.md`
+ - `04-task-matrix.md`
+ - `05-acceptance-evidence.md`
+ - `06-decision-records.md`
+ - `README.md`
+- `rg -n "功能内容|程序开发详细步骤|推进台账|任务矩阵|验收证据|决策记录|gmoccapy-xyzab|working3|working4" /home/meswork/cnc_wams/work/working4`
+ - Confirmed required sections and key terms appear across the document set.
+- `git diff --check -- /home/meswork/cnc_wams/work/working4`
+ - Result: success, no whitespace errors reported.
+- `wc -l /home/meswork/cnc_wams/work/working4/*.md`
+ - Confirmed total document size: 970 lines.
+ - Per file:
+ - `01-function-content.md`: 98 lines.
+ - `02-program-development-steps.md`: 316 lines.
+ - `03-progress-ledger.md`: 101 lines.
+ - `04-task-matrix.md`: 85 lines.
+ - `05-acceptance-evidence.md`: 155 lines.
+ - `06-decision-records.md`: 160 lines.
+ - `README.md`: 55 lines.
+- `date '+%Y-%m-%d %H:%M:%S %Z'`
+ - Result: `2026-06-26 10:10:14 EDT`.
+- `git status --short -- work/working4 web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`
+ - Before appending this log, showed `?? work/working4/`.
+- `git diff --stat -- work/working4`
+ - No stat output because the files were untracked at that moment.
+
+Result:
+- Completed the requested working4 documentation set.
+- Did not modify application source code in `web-rtcp-5axis-sim-plan/app/src`.
+- Did not run app build or smoke tests because this turn only generated planning/traceability documents and did not alter runtime code.
+- Appended this execution process log to `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md` per workspace rule.
+
+Next recommended implementation step:
+- Start with `W4-A01` and `W4-A02` from `work/working4/04-task-matrix.md`:
+ - Create `web-rtcp-5axis-sim-plan/docs/gmoccapy-xyzab-reference.md`.
+ - Convert the gmoccapy button CSV inventory into a Web-consumable JSON manifest.
+
+---
+
+## GPT/Codex execution log - W4-IMPL-001 gmoccapy XYZAB reference/profile/models
+
+Timestamp: 2026-06-26 10:43:51 EDT
+Workspace: `/home/meswork/cnc_wams`
+User request: `按“/home/meswork/cnc_wams/work/working4“开始工作`
+
+Process summary:
+- Read workspace instruction from user-provided AGENTS content: append full process log after execution to `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`.
+- Started from `/home/meswork/cnc_wams/work/working4` as requested.
+- Read `work/working4/README.md`, `01-function-content.md`, `02-program-development-steps.md`, `03-progress-ledger.md`, `04-task-matrix.md`, `05-acceptance-evidence.md`, and `06-decision-records.md`.
+- Identified first todo group as W4-A01 through W4-A04, then proceeded through reference profile/model/test work that could be verified independently.
+- Checked dirty worktree with `git status --short`; existing `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md` was already modified and `work/working4/` was untracked.
+
+Repository context inspected:
+- `rg --files web-rtcp-5axis-sim-plan/app/src`
+- `rg --files web-rtcp-5axis-sim-plan/tests`
+- `sed -n '1,260p' web-rtcp-5axis-sim-plan/app/src/profiles/index.js`
+- `sed -n '1,300p' web-rtcp-5axis-sim-plan/app/src/profiles/xyzac-trt.js`
+- `sed -n '1,300p' web-rtcp-5axis-sim-plan/app/src/profiles/xyzbc-trt.js`
+- `sed -n '1,360p' web-rtcp-5axis-sim-plan/app/src/profiles/source-reference-map.js`
+- `sed -n '1,260p' web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs`
+- `sed -n '1,300p' web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs`
+- `sed -n '1,340p' web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js`
+- `sed -n '1,380p' web-rtcp-5axis-sim-plan/app/src/state/store.js`
+- `sed -n '280,620p' web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
+- `sed -n '2200,2365p' web-rtcp-5axis-sim-plan/app/src/state/store.js`
+- `sed -n '1,260p' web-rtcp-5axis-sim-plan/app/src/runtime/linuxcnc-boundary-adapter.js`
+- `cat web-rtcp-5axis-sim-plan/app/package.json`
+- `sed -n '1,260p' web-rtcp-5axis-sim-plan/app/scripts/build-static.mjs`
+- `sed -n '1,240p' web-rtcp-5axis-sim-plan/app/src/runtime/rtcp-frame.js`
+
+working3/LinuxCNC inputs inspected:
+- `sed -n '1,260p' /home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md`
+- `sed -n '260,620p' /home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md`
+- `sed -n '620,980p' /home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md`
+- `sed -n '1,80p' /home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.csv`
+- `wc -l /home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.csv` returned 212 lines including header.
+- `find /home/meswork/cnc_wams/work/working3 -maxdepth 4 -type f | sort` confirmed CSV, Markdown inventory, and copied icon files.
+- `sed -n '1,260p' linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini`
+- `sed -n '1,260p' linuxcnc/configs/sim/gmoccapy/core_sim_XYZAB.hal`
+- `sed -n '1,220p' linuxcnc/configs/sim/gmoccapy/gmoccapy_postgui.hal`
+- `sed -n '1,180p' linuxcnc/configs/sim/gmoccapy/spindle_sim.hal`
+- `sed -n '1,160p' linuxcnc/configs/sim/gmoccapy/simulated_home.hal`
+- `sed -n '1,220p' linuxcnc/configs/sim/gmoccapy/tool.tbl`
+- Verified `linuxcnc/src/emc/kinematics/trivkins.c` exists.
+- Verified `wasm-port/vendor/linuxcnc/configs/sim/gmoccapy` does not exist, so `gmoccapy-xyzab` must remain reference-only and not use machine-file staging.
+
+Asset/manifest generation:
+- Ran a Node CSV conversion script that:
+ - Parsed `work/working3/gmoccapy_button_icons/button_icon_inventory.csv`.
+ - Wrote `web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json`.
+ - Copied icon files from `work/working3/gmoccapy_button_icons/files/` to `web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/`.
+- Output:
+ - `manifest_entries=211`
+ - `copied_icons=112`
+- Verified:
+ - `find web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons -maxdepth 1 -type f | wc -l` returned `112`.
+ - Manifest JSON parse check printed `211 112 rbt_auto app/src/assets/gmoccapy-icons/img_auto_on__mode_auto_active__48.png`.
+
+Files added:
+- `web-rtcp-5axis-sim-plan/docs/gmoccapy-xyzab-reference.md`
+- `web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json`
+- `web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/` with 112 copied PNG/SVG files
+- `web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js`
+- `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js`
+- `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js`
+- `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs`
+- `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs`
+- `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs`
+- `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs`
+
+Files modified:
+- `web-rtcp-5axis-sim-plan/app/package.json`
+ - Added new gmoccapy tests to `smoke:node`.
+- `web-rtcp-5axis-sim-plan/app/src/profiles/index.js`
+ - Registered `gmoccapyXyzabProfile`.
+- `web-rtcp-5axis-sim-plan/app/src/profiles/source-reference-map.js`
+ - Added gmoccapy_XYZAB INI/HAL/postgui/trivkins/gui/glade/icon/doc references.
+- `web-rtcp-5axis-sim-plan/app/src/runtime/rtcp-frame.js`
+ - Generalized fixture joint-pose axis mapping from profile coordinates so XYZAB maps joint 4 to B instead of C.
+- `web-rtcp-5axis-sim-plan/app/src/state/store.js`
+ - Added protection for `tcpCapable=false` profiles in `SET_RTCP` and `SET_KINS_TYPE`.
+ - Updated RUN_READY TCP behavior to only enable RTCP when the selected profile actually has a tcp switchkins type.
+ - Removed default `tcp-xyzac` fallback from MDI TCP resolution for non-TCP profiles.
+- `web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
+ - Disabled TCP sidebar button for profiles without a TCP kins type and added a reference-only title reason.
+- `web-rtcp-5axis-sim-plan/docs/traceability-matrix.md`
+ - Added gmoccapy_XYZAB profile, communication model, HAL model, and icon manifest traceability rows.
+- `web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs`
+ - Updated expected profile list to include `gmoccapy-xyzab` and added reference-only assertions.
+- `web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs`
+ - Updated available profile assertion for the third profile.
+- `work/working4/03-progress-ledger.md`
+ - Added W4-IMPL-001 ledger entry.
+- `work/working4/04-task-matrix.md`
+ - Marked W4-A01..A04, W4-P01..P04, W4-T01..T04, W4-T08, W4-V01..V05 done; left UI icon integration, gate expansion, QA report/screenshot tasks todo.
+- `work/working4/05-acceptance-evidence.md`
+ - Added W4-IMPL-001 evidence with commands, outputs, manifest counts, and remaining gaps.
+- `work/working4/06-decision-records.md`
+ - Added ADR-009: gmoccapy_XYZAB profile must block TCP/RTCP switching.
+
+Important implementation decisions:
+- `gmoccapy-xyzab` is selectable but remains `trivkins coordinates=xyzab`, `tcpCapable=false`, `rtcpProof=false`, `promotionAllowed=false`.
+- UI and store both prevent TCP/RTCP activation for `gmoccapy-xyzab`.
+- Existing source-derived runtime proof remains with `xyzac-trt` and `xyzbc-trt`.
+- Button manifest and copied icons are treated as UI asset provenance only, not control semantics proof.
+- Communication and HAL/postgui models are data/test additions only in this turn; they are not yet rendered in the info panel.
+
+Validation commands and results:
+- `node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs`
+ - `gmoccapy_xyzab_profile_smoke=ok`
+- `node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs`
+ - Initially failed because test expected non-existent CSV IDs `btn_view_x` etc.; inspected manifest and corrected test IDs to `rbt_view_x/y/z/p`, `tbtn_view_tool_path`, `tbtn_view_dimension`.
+ - Final result: `gmoccapy_icon_manifest_smoke=ok`
+- `node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs`
+ - `gmoccapy_communication_model_smoke=ok`
+- `node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs`
+ - `gmoccapy_hal_model_smoke=ok`
+- `node web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs`
+ - `profile_boundary_smoke=ok`
+- `node web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs`
+ - Initially failed because existing test expected `availableProfiles.length === 2`; updated assertion to expected IDs `xyzac-trt`, `xyzbc-trt`, `gmoccapy-xyzab`.
+ - Final result: `rtcp_store_smoke=ok`
+- `npm --prefix web-rtcp-5axis-sim-plan/app run build`
+ - `gmoccapy_static_build=ok`
+- `npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node`
+ - Passed all chained tests, including:
+ - `profile_boundary_smoke=ok`
+ - `gmoccapy_xyzab_profile_smoke=ok`
+ - `gmoccapy_icon_manifest_smoke=ok`
+ - `gmoccapy_communication_model_smoke=ok`
+ - `gmoccapy_hal_model_smoke=ok`
+ - `linear_unit_conversion_smoke=ok`
+- `bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh`
+ - `gmoccapy_shell_smoke=ok`
+- `bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh`
+ - `gmoccapy_dist_smoke=ok`
+- `git diff --check -- web-rtcp-5axis-sim-plan work/working4`
+ - Returned 0 with no whitespace errors.
+- Final asset check:
+ - `find web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons -maxdepth 1 -type f | wc -l` returned `112`.
+ - Manifest check printed `manifest 211 112`.
+
+Final git status summary:
+- Modified tracked files:
+ - `web-rtcp-5axis-sim-plan/app/package.json`
+ - `web-rtcp-5axis-sim-plan/app/src/profiles/index.js`
+ - `web-rtcp-5axis-sim-plan/app/src/profiles/source-reference-map.js`
+ - `web-rtcp-5axis-sim-plan/app/src/runtime/rtcp-frame.js`
+ - `web-rtcp-5axis-sim-plan/app/src/state/store.js`
+ - `web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
+ - `web-rtcp-5axis-sim-plan/docs/traceability-matrix.md`
+ - `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`
+ - `web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs`
+ - `web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs`
+- Untracked new files/directories:
+ - `web-rtcp-5axis-sim-plan/app/src/assets/`
+ - `web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js`
+ - `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js`
+ - `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js`
+ - `web-rtcp-5axis-sim-plan/app/src/ui-reference/`
+ - `web-rtcp-5axis-sim-plan/docs/gmoccapy-xyzab-reference.md`
+ - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs`
+ - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs`
+ - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs`
+ - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs`
+ - `work/working4/`
+
+Remaining work intentionally left todo:
+- W4-U01..U05: icon registry and actual UI icon integration, plus communication/HAL diagnostics rendering in info panel.
+- W4-G01..G05 and W4-T05: dedicated gmoccapy_XYZAB gate expansion/test.
+- W4-T06..T07: browser smoke extensions and QA JSON/PDF/screenshot report.
+
+Result: W4-A/W4-P/W4-T01..T04/W4-V implementation slice completed and verified.
+
+---
+
+## Codex execution log - W4-IMPL-002
+
+Timestamp: 2026-06-26 15:08:32 EDT
+Workspace: `/home/meswork/cnc_wams`
+User request: Continue the previous round and finish the work under `/home/meswork/cnc_wams/work/working4`.
+
+Process followed:
+1. Inspected `work/working4/README.md`, `03-progress-ledger.md`, `04-task-matrix.md`, `05-acceptance-evidence.md`, `01-function-content.md`, `02-program-development-steps.md`, and `06-decision-records.md`.
+2. Confirmed previous round had completed W4-A and W4-P items, with remaining todo items in UI/icon integration, communication/HAL diagnostics rendering, gmoccapy_XYZAB gates, browser smoke, and QA report generation.
+3. Inspected existing implementation files:
+ - `app/src/ui/gmoccapy-shell.js`
+ - `app/src/styles/gmoccapy.css`
+ - `app/src/state/store.js`
+ - `app/src/state/linuxcnc-task-policy.js`
+ - existing tests under `tests/node/` and `tests/browser/`
+ - existing QA capture scripts under `qa/web-rtcp-5axis-site-test/`
+4. Implemented a focused gmoccapy icon registry and wired it into current shell rendering without introducing a frontend framework or runtime dependency on `work/working3`.
+5. Extended the info panel to render gmoccapy communication and HAL/postgui diagnostics from the already-created models.
+6. Extended task policy for gmoccapy_XYZAB gate semantics while preserving existing `xyzac-trt`/`xyzbc-trt` runtime behavior.
+7. Added spindle direction state and gate-guarded spindle/coolant/override store actions.
+8. Added Node tests for icon registry and gmoccapy_XYZAB gate behavior.
+9. Updated browser smoke to assert core icon DOM attributes and gmoccapy NML/HAL diagnostics.
+10. Added a QA capture script that generates JSON/PDF/screenshots for gmoccapy_XYZAB function evidence.
+11. Updated `work/working4/03-progress-ledger.md`, `04-task-matrix.md`, and `05-acceptance-evidence.md` with current status and evidence.
+12. Ran targeted tests, full smoke tests, browser tests, QA capture, and whitespace checks.
+
+Files added:
+- `web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-icon-registry.js`
+- `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_registry.mjs`
+- `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs`
+- `qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs`
+- `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`
+- `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.pdf`
+- `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/01-loaded.png`
+- `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/02-profile-selected.png`
+- `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/03-run-blocked-power.png`
+- `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/04-run-blocked-reference-evidence.png`
+- `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/05-controls-active.png`
+- `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/06-hal-diagnostics.png`
+
+Files updated in this execution:
+- `web-rtcp-5axis-sim-plan/app/package.json`
+- `web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js`
+- `web-rtcp-5axis-sim-plan/app/src/state/store.js`
+- `web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css`
+- `web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
+- `web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html`
+- `work/working4/03-progress-ledger.md`
+- `work/working4/04-task-matrix.md`
+- `work/working4/05-acceptance-evidence.md`
+- `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`
+
+Important fixes/adjustments during validation:
+- Initial `verify_gmoccapy_xyzab_gates.mjs` fixture lacked fields required by `applyIniConfigToProfile`; added minimal profile-derived INI structures.
+- Initial strict RUN/JOG/HOME policy was too broad and broke existing `verify_rtcp_store.mjs`; narrowed strict RUN checks to `gmoccapy-xyzab`.
+- Full `smoke:node` initially failed in `verify_linuxcnc_task_hal_runtime.mjs` because JOG/HOME gating treated a manual-mode stale `runState=running` as active AUTO execution. Adjusted running detection to block JOG/HOME only for AUTO reading/waiting/running, preserving the existing task/HAL HOME/JOG continuity behavior.
+- One build attempt failed with `ENOTEMPTY` because `npm run build` and dist browser smoke were run in parallel against `app/dist`; reran build sequentially and it passed. This was a command concurrency issue, not a source failure.
+
+Validation commands and final results:
+- `node --check web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
+ - Passed.
+- `node --check web-rtcp-5axis-sim-plan/app/src/state/store.js`
+ - Passed.
+- `node --check web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js`
+ - Passed.
+- `node --check qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs`
+ - Passed.
+- `node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_registry.mjs`
+ - `gmoccapy_icon_registry_smoke=ok`
+- `node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs`
+ - `gmoccapy_xyzab_gates_smoke=ok`
+- `node web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs`
+ - `rtcp_store_smoke=ok`
+- `node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs`
+ - `run_preconditions_ini_profile_smoke=ok`
+ - `run_preconditions_kinematics_smoke=ok`
+ - `run_preconditions_machine_file_smoke=ok`
+- `node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_task_hal_runtime.mjs`
+ - `linuxcnc_task_hal_runtime_smoke=ok`
+ - `task_hal_machine_file_smoke=ok`
+ - `switchkins_remap_hal_sync_smoke=ok`
+ - `browser_task_hal_worker_smoke=ok`
+- `npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node`
+ - Passed all chained Node tests, including `gmoccapy_icon_registry_smoke=ok` and `gmoccapy_xyzab_gates_smoke=ok`.
+- `npm --prefix web-rtcp-5axis-sim-plan/app run build`
+ - Final sequential run: `gmoccapy_static_build=ok`
+- `bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh`
+ - `gmoccapy_shell_smoke=ok`
+- `bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh`
+ - `gmoccapy_dist_smoke=ok`
+- `node qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs`
+ - `gmoccapy_xyzab_function_status=PASS`
+ - `gmoccapy_xyzab_function_job_id=xyzab-20260626190724-7b80b3aa`
+ - `gmoccapy_xyzab_function_report_id=report-xyzab-20260626190724-7b80b3aa`
+ - JSON: `/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`
+ - PDF: `/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.pdf`
+ - Screenshots: `/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/`
+- `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`
+ - Returned 0 with no whitespace errors.
+
+QA report checks from JSON:
+- PASS XYZAB profile selected
+- PASS Reference profile blocks RTCP promotion
+- PASS RUN blocked before power
+- PASS RUN blocked by reference evidence
+- PASS Core icons rendered
+- PASS Spindle/coolant state updates
+- PASS NML command diagnostic
+- PASS Web runtime diagnostic
+- PASS HAL postgui diagnostic
+- PASS Screenshots are nonblank
+
+Task matrix outcome:
+- Marked W4-U01..W4-U05 done.
+- Marked W4-G01..W4-G05 done.
+- Marked W4-T05..W4-T07 done.
+- Updated W4 evidence with current JSON/PDF/screenshots and latest job/report IDs.
+
+Final note:
+- `gmoccapy-xyzab` remains a `trivkins` reference-only profile. This work validates UI assets, diagnostics, task gate semantics, blocked reasons, and reporting evidence; it does not promote XYZAB as TCP/RTCP proof or native LinuxCNC realtime execution.
+
+---
+
+## 2026-06-26 15:11:55 EDT - working4 remaining-work completion check
+
+User request:
+- Continue and finish remaining work in `/home/meswork/cnc_wams/work/working4`.
+
+Process:
+- Checked `work/working4/04-task-matrix.md` for task rows still in `todo`, `in_progress`, `blocked`, or `deferred`; none were found.
+- Checked `work/working4/04-task-matrix.md` and `work/working4/05-acceptance-evidence.md` for stale TODO/pending markers. Remaining `blocked` matches were status vocabulary, expected QA screenshot names/check labels, and completed gate evidence text.
+- Parsed `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`; result was `status=PASS`, `steps=6`, `checks=10`.
+- Ran `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`; returned 0.
+- Updated `work/working4/04-task-matrix.md` wording for W4-D06 and W4-T08 to remove stale "pending evidence" wording from completed rows.
+- Re-ran the remaining-task scan, QA JSON status check, and whitespace check after the wording update. The final task-row scan still found no unfinished W4 task rows; the final QA report status remained `PASS`; the final whitespace check returned 0.
+
+Outcome:
+- No remaining `working4` task-matrix work was found.
+- `gmoccapy-xyzab-function-report.json` remains PASS with 6 steps and 10 checks.
+- Only documentation wording was changed in this continuation pass, plus this required process-log append.
+
+Addendum:
+- After the log append, ran `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`; returned 0.
+- Checked focused status for `work/working4/04-task-matrix.md` and `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`; status showed the required log file modified and `work/working4/04-task-matrix.md` present as an untracked working file.
+- Appended this addendum as the final tool action for this execution.
+
+---
+
+## 2026-06-26 15:17:57 EDT - working4 status-document closure
+
+User request:
+- Continue completing `/home/meswork/cnc_wams/work/working4`.
+
+Process:
+- Inspected git status for `work/working4`, `web-rtcp-5axis-sim-plan`, and `qa/web-rtcp-5axis-site-test`.
+- Checked `work/working4/04-task-matrix.md` for unfinished W4 task rows; none were found.
+- Read `work/working4/README.md`, `01-function-content.md`, `02-program-development-steps.md`, `03-progress-ledger.md`, `04-task-matrix.md`, and `05-acceptance-evidence.md`.
+- Found remaining planning-era wording in `README.md`, `01-function-content.md`, `02-program-development-steps.md`, and `05-acceptance-evidence.md`: the docs still described the stage as planning-only, had feature package rows marked pending/partial, and mentioned no job/report IDs even though implementation and QA evidence now exist.
+- Updated `README.md` to state that working4 is implemented, verified, and archived, with the current boundary that `gmoccapy-xyzab` remains reference-only.
+- Updated `01-function-content.md` to mark F-XYZAB-01 through F-XYZAB-07 as completed or boundary-completed, and adjusted the success definition to the actual evidence scope.
+- Updated `02-program-development-steps.md` to state that phases A through G are complete and that JSON/PDF QA capture is generated.
+- Updated `04-task-matrix.md` with W4-D08 for status-document closure and updated W4-V05 to include the QA path in `git diff --check`.
+- Updated `05-acceptance-evidence.md` to remove the obsolete "documentation only/no job_id" wording, add icon registry/dist smoke/whitespace evidence rows, and change the document task range to W4-D01 through W4-D08.
+- Updated `03-progress-ledger.md` with W4-DOC-002, including files changed, validation commands, results, retained semantic boundaries, and final next step.
+
+Validation:
+- `rg -n "\\| W4-[A-Z0-9-]+ \\| (todo|in_progress|blocked|deferred) \\|" work/working4/04-task-matrix.md || true`
+ - Returned no unfinished task rows.
+- `rg -n "待实施|待验证|待生成|PDF 任务保持 todo|本轮只创建文档|没有生成新的 job_id|后续新增:|TODO" work/working4/README.md work/working4/01-function-content.md work/working4/02-program-development-steps.md work/working4/05-acceptance-evidence.md || true`
+ - Returned no stale current-state wording.
+- `node -e "const r=JSON.parse(require('fs').readFileSync('qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json','utf8')); if(r.status!=='PASS') process.exit(1); console.log('qa_report_status='+r.status); console.log('qa_report_steps='+r.steps.length); console.log('qa_report_checks='+r.checks.length);"`
+ - `qa_report_status=PASS`
+ - `qa_report_steps=6`
+ - `qa_report_checks=10`
+- `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`
+ - Returned 0.
+
+Outcome:
+- `working4` no longer has current task rows pending or current status docs saying the work is still planning-only/pending.
+- The remaining caveats are intentional semantic boundaries: `gmoccapy-xyzab` is still a `trivkins` reference-only profile and this stage does not prove native LinuxCNC realtime execution.
+
+---
+
+## 2026-06-26 15:20:53 EDT - working4 wording finalization
+
+User request:
+- Continue completing `/home/meswork/cnc_wams/work/working4`.
+
+Process:
+- Re-scanned `work/working4/04-task-matrix.md` for W4 rows still in `todo`, `in_progress`, `blocked`, or `deferred`; none were found.
+- Re-scanned current state docs for stale planning wording such as pending implementation, pending validation, pending PDF generation, documentation-only state, missing job_id/report_id, and planning-only headings.
+- Inspected `work/working4/06-decision-records.md`, `04-task-matrix.md`, and `03-progress-ledger.md`.
+- Identified remaining wording that was not a functional gap but could make the archive look like a plan rather than a completed record:
+ - `README.md` document descriptions still said "to be improved" and "files to change".
+ - `02-program-development-steps.md` used "suggested new or updated files" throughout completed phases.
+ - `03-progress-ledger.md` preserved early historical "remaining issues" without a top-level note explaining that later W4 entries closed them.
+ - `04-task-matrix.md` still said W4-D01 listed a future execution entry and W4-T07 output PDF only "if needed".
+- Updated `README.md` to describe `01-function-content.md` and `02-program-development-steps.md` as completed/recorded content.
+- Updated `02-program-development-steps.md` wording from planning suggestions to archived "main new or updated files".
+- Added a note near the top of `03-progress-ledger.md` that early remaining issues and next steps are historical and that current state is governed by the task matrix and acceptance evidence.
+- Updated the W4-DOC-002 ledger entry to include the `02-program-development-steps.md` wording closure.
+- Updated `04-task-matrix.md` W4-D01 and W4-T07 acceptance wording to match the completed archive state.
+- Added W4-DOC-003 to `03-progress-ledger.md` for this final wording cleanup.
+
+Validation:
+- `rg -n "\\| W4-[A-Z0-9-]+ \\| (todo|in_progress|blocked|deferred) \\|" work/working4/04-task-matrix.md || true`
+ - Returned no unfinished W4 task rows.
+- `rg -n "待实施|待验证|待生成|PDF 任务保持 todo|本轮只创建文档|没有生成新的 job_id|后续新增:|建议新增|建议新增或更新|必要时输出 PDF|后续执行入口|拟完善功能包|TODO" work/working4/README.md work/working4/01-function-content.md work/working4/02-program-development-steps.md work/working4/04-task-matrix.md work/working4/05-acceptance-evidence.md || true`
+ - Returned no stale planning/current-state wording.
+- `node -e "const r=JSON.parse(require('fs').readFileSync('qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json','utf8')); if(r.status!=='PASS') process.exit(1); console.log('qa_report_status='+r.status); console.log('qa_report_steps='+r.steps.length); console.log('qa_report_checks='+r.checks.length);"`
+ - `qa_report_status=PASS`
+ - `qa_report_steps=6`
+ - `qa_report_checks=10`
+- `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`
+ - Returned 0.
+- After adding W4-DOC-003, re-ran the W4 task-row scan, stale current-state wording scan, and `git diff --check`; all remained clean.
+
+Outcome:
+- No additional implementation work was needed.
+- `working4` docs now read consistently as a completed implementation/evidence archive.
+- The only retained caveats are the intended semantic boundaries: `gmoccapy-xyzab` is reference-only and this stage does not prove TCP/RTCP or native LinuxCNC realtime execution.
+
+---
+
+## 2026-06-26 15:22:49 EDT - working4 recheck no-op closure
+
+User request:
+- Continue completing `/home/meswork/cnc_wams/work/working4`.
+
+Process:
+- Rechecked `work/working4/04-task-matrix.md` for W4 task rows in `todo`, `in_progress`, `blocked`, or `deferred`; none were found.
+- Rechecked current state docs for stale planning/pending wording:
+ - `work/working4/README.md`
+ - `work/working4/01-function-content.md`
+ - `work/working4/02-program-development-steps.md`
+ - `work/working4/04-task-matrix.md`
+ - `work/working4/05-acceptance-evidence.md`
+- Read recent `03-progress-ledger.md`, `README.md`, and the acceptance evidence summary to confirm the previous closure entries and retained semantic boundaries.
+- Parsed `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`.
+- Ran whitespace validation for the project, QA, and working4 paths.
+- Checked focused git status for `work/working4`, `gpdlog.md`, and the gmoccapy QA report artifacts.
+
+Validation:
+- W4 unfinished task-row scan returned no rows.
+- Stale planning/current-state wording scan returned no rows.
+- QA report remained `PASS` with 6 steps and 10 checks.
+- `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4` returned 0.
+- Focused git status showed expected uncommitted artifacts only:
+ - modified `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`
+ - untracked `work/working4/`
+ - untracked gmoccapy QA report JSON/PDF
+
+Outcome:
+- No new code or documentation edits were needed for `working4` in this pass.
+- `working4` remains complete as an implementation/evidence archive, with only the intentional reference-only/native-runtime caveats retained.
+
+---
+
+## 2026-06-26 15:24:59 EDT - working4 QA id-field evidence clarification
+
+User request:
+- Continue completing `/home/meswork/cnc_wams/work/working4`.
+
+Process:
+- Rechecked `04-task-matrix.md` for unfinished W4 rows; none were found.
+- Rechecked current state docs for stale planning or pending wording; none was found.
+- Parsed `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json` and initially checked snake_case `job_id/report_id`, which were empty because the JSON uses camelCase `jobId/reportId`.
+- Inspected the QA report JSON structure and confirmed top-level fields include `jobId` and `reportId`.
+- Read `qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs` and confirmed it writes `jobId/reportId` to JSON while printing `gmoccapy_xyzab_function_job_id` and `gmoccapy_xyzab_function_report_id` to stdout.
+- Cross-checked `work/working4/05-acceptance-evidence.md` references for job/report IDs.
+- Updated `05-acceptance-evidence.md` to clarify that QA JSON uses camelCase `jobId/reportId`, while console output and the archive table use `job_id/report_id` labels for the same identifiers.
+- Added W4-DOC-004 to `03-progress-ledger.md` documenting this evidence-field clarification.
+
+Validation:
+- `rg -n "\\| W4-[A-Z0-9-]+ \\| (todo|in_progress|blocked|deferred) \\|" work/working4/04-task-matrix.md || true`
+ - Returned no unfinished W4 task rows.
+- `rg -n "待实施|待验证|待生成|PDF 任务保持 todo|本轮只创建文档|没有生成新的 job_id|后续新增:|建议新增|建议新增或更新|必要时输出 PDF|后续执行入口|拟完善功能包|TODO" work/working4/README.md work/working4/01-function-content.md work/working4/02-program-development-steps.md work/working4/04-task-matrix.md work/working4/05-acceptance-evidence.md || true`
+ - Returned no stale current-state wording.
+- `node -e "const r=JSON.parse(require('fs').readFileSync('qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json','utf8')); if(r.status!=='PASS') process.exit(1); console.log('qa_report_jobId='+r.jobId); console.log('qa_report_reportId='+r.reportId); console.log('qa_report_status='+r.status); console.log('qa_report_steps='+r.steps.length); console.log('qa_report_checks='+r.checks.length);"`
+ - `qa_report_jobId=xyzab-20260626190724-7b80b3aa`
+ - `qa_report_reportId=report-xyzab-20260626190724-7b80b3aa`
+ - `qa_report_status=PASS`
+ - `qa_report_steps=6`
+ - `qa_report_checks=10`
+- `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`
+ - Returned 0.
+
+Outcome:
+- No implementation changes were needed.
+- `working4` evidence now explicitly documents the QA report field-name mapping, preventing future false-negative checks for `job_id/report_id`.
+
+---
+
+## 2026-06-26 15:26:22 EDT - working4 clean recheck
+
+User request:
+- Continue completing `/home/meswork/cnc_wams/work/working4`.
+
+Process:
+- Rechecked `work/working4/04-task-matrix.md` for unfinished W4 task rows.
+- Rechecked current state docs for stale planning/pending wording:
+ - `work/working4/README.md`
+ - `work/working4/01-function-content.md`
+ - `work/working4/02-program-development-steps.md`
+ - `work/working4/04-task-matrix.md`
+ - `work/working4/05-acceptance-evidence.md`
+- Parsed `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json` using `jobId/reportId`.
+- Ran whitespace validation on `web-rtcp-5axis-sim-plan`, `qa/web-rtcp-5axis-site-test`, and `work/working4`.
+- Checked focused git status for `work/working4`, `gpdlog.md`, and the gmoccapy QA report JSON/PDF.
+
+Validation:
+- W4 unfinished task-row scan returned no rows.
+- Stale current-state wording scan returned no rows.
+- QA report remained `PASS`:
+ - `jobId=xyzab-20260626190724-7b80b3aa`
+ - `reportId=report-xyzab-20260626190724-7b80b3aa`
+ - `steps=6`
+ - `checks=10`
+- `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4` returned 0.
+
+Outcome:
+- No new `working4` work was found.
+- No code or documentation changes were needed in this pass.
+
+---
+
+## 2026-06-26 15:27:29 EDT - working4 no-op verification
+
+User request:
+- Continue completing `/home/meswork/cnc_wams/work/working4`.
+
+Process:
+- Ran the standard W4 unfinished-task scan against `work/working4/04-task-matrix.md`.
+- Ran the current-state stale wording scan against the active `working4` docs.
+- Parsed `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`.
+- Ran `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`.
+- Checked focused status for `work/working4`, `gpdlog.md`, and the gmoccapy QA report JSON/PDF.
+
+Validation:
+- No W4 rows were in `todo`, `in_progress`, `blocked`, or `deferred`.
+- No stale current-state planning/pending wording was found.
+- QA report remained `PASS`:
+ - `jobId=xyzab-20260626190724-7b80b3aa`
+ - `reportId=report-xyzab-20260626190724-7b80b3aa`
+ - `steps=6`
+ - `checks=10`
+- `git diff --check` returned 0.
+
+Outcome:
+- No new work was needed for `working4`.
+- No `working4` source, evidence, or implementation files were changed in this pass.
+
+---
+
+## 2026-06-26 15:32:48 EDT - working4 final closure page
+
+User request:
+- Continue completing `/home/meswork/cnc_wams/work/working4`.
+
+Process:
+- Performed a higher-level closure audit instead of only repeating keyword checks.
+- Inspected the file list under `work/working4`; only README and documents 01 through 06 existed.
+- Read `README.md`, `04-task-matrix.md`, and recent `03-progress-ledger.md`.
+- Determined that the work was complete but the archive lacked a single final closure entry point summarizing completion, evidence, retained boundaries, and future-change rules.
+- Added `work/working4/07-final-closure.md`.
+- Updated `README.md` to mark `07-final-closure.md` as the final closure entry and add it to the document list and maintenance steps.
+- Updated `04-task-matrix.md` with W4-D09 for the final closure document and adjusted W4-D01 to list 01-07 documents.
+- Updated `03-progress-ledger.md` with W4-DOC-005.
+- Updated `05-acceptance-evidence.md` to include `07-final-closure.md` in the document output list and extend the document closure row from W4-D01..W4-D08 to W4-D01..W4-D09.
+- Fixed the stale-wording example command in `07-final-closure.md` so the command itself does not trip the stale-wording scan.
+- Updated W4-DOC-005 results after validation.
+
+Validation:
+- `rg -n "\\| W4-[A-Z0-9-]+ \\| (todo|in_progress|blocked|deferred) \\|" work/working4/04-task-matrix.md || true`
+ - Returned no unfinished W4 task rows.
+- `rg -n "待实施|待验证|待生成|PDF 任务保持 todo|本轮只创建文档|没有生成新的 job_id|后续新增:|建议新增|建议新增或更新|必要时输出 PDF|后续执行入口|拟完善功能包|TODO" work/working4/README.md work/working4/01-function-content.md work/working4/02-program-development-steps.md work/working4/04-task-matrix.md work/working4/05-acceptance-evidence.md work/working4/07-final-closure.md || true`
+ - Returned no stale current-state wording.
+- `node -e "const r=JSON.parse(require('fs').readFileSync('qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json','utf8')); if(r.status!=='PASS') process.exit(1); console.log('qa_report_jobId='+r.jobId); console.log('qa_report_reportId='+r.reportId); console.log('qa_report_status='+r.status); console.log('qa_report_steps='+r.steps.length); console.log('qa_report_checks='+r.checks.length);"`
+ - `qa_report_jobId=xyzab-20260626190724-7b80b3aa`
+ - `qa_report_reportId=report-xyzab-20260626190724-7b80b3aa`
+ - `qa_report_status=PASS`
+ - `qa_report_steps=6`
+ - `qa_report_checks=10`
+- `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`
+ - Returned 0.
+
+Outcome:
+- `working4` now has a single final closure document: `work/working4/07-final-closure.md`.
+- Task matrix and evidence archive are aligned through W4-D09.
+- No implementation changes were needed.
+
+---
+
+## 2026-06-26 15:34:24 EDT - working4 final closure recheck
+
+User request:
+- Continue completing `/home/meswork/cnc_wams/work/working4`.
+
+Process:
+- Verified `work/working4` file list includes README and documents 01 through 07.
+- Rechecked `04-task-matrix.md` for unfinished W4 task rows; none were found.
+- Rechecked current state docs, including `07-final-closure.md`, for stale planning/pending wording; none was found.
+- Parsed `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`.
+- Ran `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`.
+- Read `07-final-closure.md` to verify it contains the final conclusion, QA evidence paths, identifiers, retained boundaries, validation commands, and future-change rules.
+- Checked focused git status for `work/working4`, `gpdlog.md`, and gmoccapy QA report JSON/PDF.
+
+Validation:
+- File list includes `07-final-closure.md`.
+- W4 unfinished task-row scan returned no rows.
+- Stale current-state wording scan returned no rows.
+- QA report remained `PASS`:
+ - `jobId=xyzab-20260626190724-7b80b3aa`
+ - `reportId=report-xyzab-20260626190724-7b80b3aa`
+ - `steps=6`
+ - `checks=10`
+- `git diff --check` returned 0.
+
+Outcome:
+- No additional `working4` edits were needed.
+- Final closure documentation remains complete and consistent.
+
+---
+
+## 2026-06-26 15:35:37 EDT - working4 final state verification
+
+User request:
+- Continue completing `/home/meswork/cnc_wams/work/working4`.
+
+Process:
+- Verified `work/working4/07-final-closure.md` exists.
+- Rechecked `work/working4/04-task-matrix.md` for W4 rows in `todo`, `in_progress`, `blocked`, or `deferred`.
+- Rechecked current state docs for stale planning/pending wording:
+ - `README.md`
+ - `01-function-content.md`
+ - `02-program-development-steps.md`
+ - `04-task-matrix.md`
+ - `05-acceptance-evidence.md`
+ - `07-final-closure.md`
+- Parsed `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`.
+- Ran `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`.
+- Checked focused git status for `work/working4`, `gpdlog.md`, and gmoccapy QA report JSON/PDF.
+
+Validation:
+- `final_closure=present`
+- No unfinished W4 task rows were found.
+- No stale current-state wording was found.
+- QA report remained `PASS`:
+ - `jobId=xyzab-20260626190724-7b80b3aa`
+ - `reportId=report-xyzab-20260626190724-7b80b3aa`
+ - `steps=6`
+ - `checks=10`
+- `git diff --check` returned 0.
+
+Outcome:
+- No new `working4` work was found.
+- No source, evidence, or implementation files required changes in this pass.
+
+---
+
+## 2026-06-26 17:46:14 EDT - working4 resumed validation and QA refresh
+
+User request:
+- Continue the prior turn and finish the work under `/home/meswork/cnc_wams/work/working4`.
+- The prior turn was intentionally interrupted by the user, then the user requested "continue".
+
+Process:
+- Restored workspace context from `/home/meswork/cnc_wams`.
+- Listed `work/working4` files and confirmed the document set:
+ - `README.md`
+ - `01-function-content.md`
+ - `02-program-development-steps.md`
+ - `03-progress-ledger.md`
+ - `04-task-matrix.md`
+ - `05-acceptance-evidence.md`
+ - `06-decision-records.md`
+ - `07-final-closure.md`
+- Checked git status and observed existing working tree changes in `web-rtcp-5axis-sim-plan`, QA outputs, and `work/working4`.
+- Read workspace `AGENTS.md` and confirmed the required process log path:
+ - `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`
+- Read `work/working4/README.md`, `03-progress-ledger.md`, `04-task-matrix.md`, and `07-final-closure.md`.
+- Confirmed the current `working4` documents already recorded all W4-D/W4-A/W4-P/W4-U/W4-G/W4-T/W4-V tasks as complete.
+- Ran the unfinished W4 task-row scan:
+ - `rg -n "\\| W4-[A-Z0-9-]+ \\| (todo|in_progress|blocked|deferred) \\|" work/working4/04-task-matrix.md`
+ - Result: no matches, command exited 1 as expected for no unfinished task rows.
+- Ran the stale current-state wording scan against:
+ - `work/working4/README.md`
+ - `work/working4/01-function-content.md`
+ - `work/working4/02-program-development-steps.md`
+ - `work/working4/04-task-matrix.md`
+ - `work/working4/05-acceptance-evidence.md`
+ - `work/working4/07-final-closure.md`
+ - Result: no matches, command exited 1 as expected for no stale wording.
+- Parsed the existing QA JSON before rerunning capture:
+ - `jobId=xyzab-20260626190724-7b80b3aa`
+ - `reportId=report-xyzab-20260626190724-7b80b3aa`
+ - `status=PASS`
+ - `steps=6`
+ - `checks=10`
+- Ran `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`.
+ - Result: returned 0.
+- Ran full validation:
+ - `npm --prefix web-rtcp-5axis-sim-plan/app run build`
+ - `npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node`
+ - `bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh`
+ - `bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh`
+ - `node qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs`
+- Validation outputs:
+ - Build: `gmoccapy_static_build=ok`
+ - Node smoke included:
+ - `linuxcnc_kinematics_runtime_smoke=ok`
+ - `linuxcnc_interpreter_runtime_smoke=ok`
+ - `linuxcnc_ini_runtime_smoke=ok`
+ - `run_preconditions_ini_profile_smoke=ok`
+ - `run_preconditions_kinematics_smoke=ok`
+ - `run_preconditions_machine_file_smoke=ok`
+ - `run_feedback_status_loop_smoke=ok`
+ - `run_ready_sequence_smoke=ok`
+ - `linuxcnc_task_hal_runtime_smoke=ok`
+ - `task_hal_machine_file_smoke=ok`
+ - `switchkins_remap_hal_sync_smoke=ok`
+ - `browser_task_hal_worker_smoke=ok`
+ - `native_task_hal_source_artifact_audit=ok`
+ - `full_linuxcnc_5axis_source_node_smoke=ok`
+ - `real_linuxcnc_5axis_program_cases_smoke=ok`
+ - `full_execution_boundary_smoke=ok`
+ - `machine_file_staging_smoke=ok`
+ - `five_axis_session_smoke=ok`
+ - `rtcp_store_smoke=ok`
+ - `profile_boundary_smoke=ok`
+ - `gmoccapy_xyzab_profile_smoke=ok`
+ - `gmoccapy_icon_manifest_smoke=ok`
+ - `gmoccapy_icon_registry_smoke=ok`
+ - `gmoccapy_communication_model_smoke=ok`
+ - `gmoccapy_hal_model_smoke=ok`
+ - `gmoccapy_xyzab_gates_smoke=ok`
+ - `linear_unit_conversion_smoke=ok`
+ - Browser shell smoke: `gmoccapy_shell_smoke=ok`
+ - Dist browser smoke: `gmoccapy_dist_smoke=ok`
+ - QA capture:
+ - `gmoccapy_xyzab_function_status=PASS`
+ - `gmoccapy_xyzab_function_job_id=xyzab-20260626214346-f4b4aa36`
+ - `gmoccapy_xyzab_function_report_id=report-xyzab-20260626214346-f4b4aa36`
+ - JSON path: `/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`
+ - PDF path: `/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.pdf`
+ - Screenshot directory: `/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab`
+- Parsed the refreshed QA JSON:
+ - `jobId=xyzab-20260626214346-f4b4aa36`
+ - `reportId=report-xyzab-20260626214346-f4b4aa36`
+ - `status=PASS`
+ - `steps=6`
+ - `checks=10`
+ - `generatedAt=2026-06-26T21:43:46.310Z`
+- Updated `work/working4/05-acceptance-evidence.md`:
+ - Changed the current QA capture command output to the refreshed `job_id` and `report_id`.
+ - Added a new job/report table row for the resumed QA capture.
+- Updated `work/working4/07-final-closure.md`:
+ - Changed final QA identifiers to the refreshed `jobId` and `reportId`.
+ - Added `generatedAt=2026-06-26T21:43:46.310Z`.
+ - Added the full build, node smoke, browser smoke, dist smoke, and QA capture commands to final validation commands.
+ - Updated final validation results to mention the full validation chain.
+- Updated `work/working4/03-progress-ledger.md`:
+ - Appended `W4-VERIFY-006` with process, files changed, validation commands, results, retained boundaries, and next-step rule.
+- Rechecked identifier usage:
+ - Old `20260626190724` identifiers remain only in historical ledger/history rows.
+ - Current final closure and current QA evidence point to `xyzab-20260626214346-f4b4aa36`.
+- Rechecked unfinished task rows:
+ - No matches.
+- Rechecked stale current-state wording:
+ - No matches.
+- Rechecked whitespace with `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4`.
+ - Result: returned 0.
+
+Files changed in this execution:
+- `/home/meswork/cnc_wams/work/working4/03-progress-ledger.md`
+- `/home/meswork/cnc_wams/work/working4/05-acceptance-evidence.md`
+- `/home/meswork/cnc_wams/work/working4/07-final-closure.md`
+- `/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`
+- `/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.pdf`
+- `/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/`
+- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`
+
+Outcome:
+- `working4` remains complete.
+- The current final closure evidence now points to the refreshed QA report:
+ - `jobId=xyzab-20260626214346-f4b4aa36`
+ - `reportId=report-xyzab-20260626214346-f4b4aa36`
+- Full build, Node smoke, browser smoke, dist smoke, QA capture, stale wording scan, unfinished task scan, and whitespace checks all pass.
+- Retained boundary remains unchanged: `gmoccapy-xyzab` is a `trivkins` reference-only profile and is not a TCP/RTCP or native LinuxCNC realtime execution proof.
diff --git a/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html b/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html
index c7b8d27..ea3702f 100644
--- a/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html
+++ b/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html
@@ -263,6 +263,37 @@
if (!doc.querySelector('[data-linuxcnc-task-policy="source"]')?.textContent.includes("linuxcnc/src/emc/task/emctaskmain.cc")) {
throw new Error("missing LinuxCNC emctaskmain source diagnostic");
}
+ for (const [selector, iconName] of [
+ ['[data-action="estop"]', "main_switch_off"],
+ ['[data-action="power"]', "power_off"],
+ ['[data-action="mode-manual"]', "mode_manual_active"],
+ ['[data-action="mode-auto"]', "mode_auto_inactive"],
+ ['[data-action="mode-mdi"]', "mode_mdi_inactive"],
+ ['[data-action="RUN"]', "play"],
+ ['[data-action="STOP"]', "stop"],
+ ['[data-action="PAUSE"]', "pause"],
+ ['[data-action="HOME"]', "ref_all"],
+ ['[data-action="toggle-flood"]', "coolant_flood_active"],
+ ['[data-action="toggle-mist"]', "coolant_mist_inactive"],
+ ['[data-action="view-x"]', "tool_axis_x"],
+ ]) {
+ const button = doc.querySelector(selector);
+ if (!button || button.dataset.iconName !== iconName || !button.querySelector("img")) {
+ throw new Error(`missing gmoccapy icon ${selector} expected ${iconName}, got ${button?.dataset.iconName}`);
+ }
+ }
+ if (!doc.querySelector('[data-gmoccapy-comm="boundary"]')?.textContent.includes("native_gmoccapy_nml_hal_reference")) {
+ throw new Error("missing gmoccapy communication boundary diagnostic");
+ }
+ if (!doc.querySelector('[data-gmoccapy-comm="native-command"]')?.textContent.includes("NML emcCommand")) {
+ throw new Error("missing gmoccapy NML command diagnostic");
+ }
+ if (!doc.querySelector('[data-gmoccapy-comm="web-path"]')?.textContent.includes("store.dispatch")) {
+ throw new Error("missing gmoccapy Web runtime mapping");
+ }
+ if (!doc.querySelector('[data-gmoccapy-hal="postgui"]')?.textContent.includes("tool-change-loop")) {
+ throw new Error("missing gmoccapy HAL postgui diagnostic");
+ }
if (!doc.querySelector('[data-tool-preview="summary"]')?.textContent.includes("T1")) {
throw new Error("missing tool preview summary");
}
diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs
new file mode 100644
index 0000000..a52c785
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs
@@ -0,0 +1,75 @@
+import assert from "node:assert/strict";
+
+import {
+ createGmoccapyCommunicationSummary,
+ gmoccapyCommunicationModel,
+} from "../../app/src/runtime/gmoccapy-communication-model.js";
+
+const model = gmoccapyCommunicationModel;
+
+assert.equal(model.apiName, "web-rtcp-5axis-gmoccapy-communication-model");
+assert.equal(model.profileId, "gmoccapy-xyzab");
+assert.equal(model.nativeConfigPath.endsWith("gmoccapy_XYZAB.ini"), true);
+assert.equal(model.cycleTimeMs, 100);
+assert.equal(model.semanticBoundary, "native_gmoccapy_nml_hal_reference_web_store_runtime_mapping");
+
+assert.deepEqual(model.nativePaths.command.path, ["gmoccapy", "linuxcnc.command()", "NML emcCommand", "milltask"]);
+assert.equal(model.nativePaths.status.path.includes("linuxcnc.stat()"), true);
+assert.equal(model.nativePaths.status.path.includes("linuxcnc.error_channel()"), true);
+assert.equal(model.nativePaths.hal.path.includes("HAL shared memory"), true);
+assert.equal(model.nativePaths.remap.path.includes("Python remap prolog/ngc/epilog"), true);
+assert.deepEqual(model.webPath.path, ["button", "store.dispatch", "linuxcnc-task-policy", "runtime/status snapshot", "UI/Three.js"]);
+
+assert.equal(model.startupStages.length, 9);
+assert.equal(model.startupStages[0].id, "parse-ini");
+assert.equal(model.startupStages.at(-1).id, "postgui");
+assert.equal(model.startupStages.at(-1).nativeStep.includes("halcomp.ready()"), true);
+
+const actionIds = model.actionMappings.map((mapping) => mapping.actionId);
+for (const actionId of [
+ "estop",
+ "estop-reset",
+ "power-on",
+ "mode-manual",
+ "mode-mdi",
+ "mode-auto",
+ "jog",
+ "home",
+ "mdi",
+ "auto-run",
+ "auto-pause",
+ "auto-resume",
+ "abort",
+ "spindle",
+ "coolant",
+]) {
+ assert.equal(actionIds.includes(actionId), true, `${actionId} missing`);
+}
+
+const runMapping = model.actionMappings.find((mapping) => mapping.actionId === "auto-run");
+assert.equal(runMapping.nativeCommand, "command.auto(AUTO_RUN, start_line)");
+assert.equal(runMapping.webAction, "RUN");
+assert.equal(runMapping.gate.includes("INI loaded"), true);
+assert.equal(runMapping.gate.includes("runtime ready"), true);
+
+const jogMapping = model.actionMappings.find((mapping) => mapping.actionId === "jog");
+assert.equal(jogMapping.nativeCommand.includes("JOG_CONTINUOUS"), true);
+assert.equal(jogMapping.gate, "machine on, manual mode, not running");
+
+const spindleMapping = model.actionMappings.find((mapping) => mapping.actionId === "spindle");
+assert.equal(spindleMapping.nativeStatus.includes("spindle.0.forward"), true);
+assert.equal(spindleMapping.gate.includes("restricted during interpreter"), true);
+
+const summary = createGmoccapyCommunicationSummary(model);
+assert.equal(summary.apiName, "web-rtcp-5axis-gmoccapy-communication-model-summary");
+assert.equal(summary.profileId, "gmoccapy-xyzab");
+assert.equal(summary.nativePathCount, 4);
+assert.equal(summary.startupStageCount, 9);
+assert.equal(summary.actionCount, 15);
+assert.equal(summary.postguiAfterHalcompReady, true);
+assert.equal(summary.hasNativeNmlCommandPath, true);
+assert.equal(summary.hasNativeHalPath, true);
+assert.equal(summary.hasWebStorePath, true);
+assert.equal(summary.semanticBoundary, model.semanticBoundary);
+
+console.log("gmoccapy_communication_model_smoke=ok");
diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs
new file mode 100644
index 0000000..4589abf
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs
@@ -0,0 +1,72 @@
+import assert from "node:assert/strict";
+
+import {
+ createGmoccapyHalSummary,
+ gmoccapyHalModel,
+} from "../../app/src/runtime/gmoccapy-hal-model.js";
+
+const model = gmoccapyHalModel;
+
+assert.equal(model.apiName, "web-rtcp-5axis-gmoccapy-hal-model");
+assert.equal(model.profileId, "gmoccapy-xyzab");
+assert.equal(model.nativeConfigPath.endsWith("gmoccapy_XYZAB.ini"), true);
+assert.equal(model.semanticBoundary, "gmoccapy_hal_pin_postgui_reference_web_diagnostic_only");
+
+assert.equal(model.halFiles.length, 4);
+assert.equal(model.halFiles.some((file) => file.path.endsWith("core_sim_XYZAB.hal") && file.stage === "HALFILE"), true);
+assert.equal(model.halFiles.some((file) => file.path.endsWith("spindle_sim.hal") && file.stage === "HALFILE"), true);
+assert.equal(model.halFiles.some((file) => file.path.endsWith("simulated_home.hal") && file.stage === "HALFILE"), true);
+assert.equal(model.halFiles.some((file) => file.path.endsWith("gmoccapy_postgui.hal") && file.requiresHalcompReady), true);
+
+const groups = new Map(model.nativePins.map((group) => [group.group, group]));
+for (const groupName of [
+ "hard-buttons",
+ "jog",
+ "override",
+ "tool",
+ "program",
+ "message",
+ "spindle-postgui",
+]) {
+ assert.equal(groups.has(groupName), true, `${groupName} missing`);
+}
+
+assert.equal(groups.get("hard-buttons").pins.includes("gmoccapy.h-button.button-0"), true);
+assert.equal(groups.get("hard-buttons").pins.includes("gmoccapy.v-button.button-6"), true);
+assert.equal(groups.get("jog").pins.includes("gmoccapy.jog.axis.jog-b-minus"), true);
+assert.equal(groups.get("override").pins.includes("gmoccapy.rapid.rapid-override.direct-value"), true);
+assert.equal(groups.get("tool").pins.includes("gmoccapy.toolchange-changed"), true);
+assert.equal(groups.get("program").pins.includes("gmoccapy.program.current-line"), true);
+assert.equal(groups.get("message").pins.includes("gmoccapy.error"), true);
+assert.equal(groups.get("spindle-postgui").pins.includes("gmoccapy.spindle_at_speed_led"), true);
+
+assert.equal(model.coreNets.length, 10);
+assert.equal(model.coreNets.some((net) => net.signal === "Xpos" && net.target === "joint.0.motor-pos-fb"), true);
+assert.equal(model.coreNets.some((net) => net.signal === "Bpos" && net.target === "joint.4.motor-pos-fb"), true);
+assert.equal(model.coreNets.some((net) => net.signal === "estop-loop"), true);
+assert.equal(model.coreNets.some((net) => net.signal === "tool-change-loop"), true);
+
+assert.equal(model.spindleNets.length, 4);
+assert.equal(model.spindleNets.some((net) => net.signal === "spindle-speed-cmd"), true);
+assert.equal(model.spindleNets.some((net) => net.signal === "spindle-at-speed"), true);
+
+assert.equal(model.postguiNets.length, 5);
+assert.equal(model.postguiNets.every((net) => net.requiresHalcompReady), true);
+assert.equal(model.postguiNets.some((net) => net.target === "gmoccapy.spindle_feedback_bar"), true);
+assert.equal(model.postguiNets.some((net) => net.target === "gmoccapy.tooloffset-x"), true);
+assert.equal(model.postguiNets.some((net) => net.simulationLoop && net.signal === "tool-change-loop"), true);
+
+const summary = createGmoccapyHalSummary(model);
+assert.equal(summary.apiName, "web-rtcp-5axis-gmoccapy-hal-model-summary");
+assert.equal(summary.profileId, "gmoccapy-xyzab");
+assert.equal(summary.halFileCount, 4);
+assert.equal(summary.nativePinGroupCount, 7);
+assert.equal(summary.nativePinCount >= 50, true);
+assert.equal(summary.coreNetCount, 10);
+assert.equal(summary.spindleNetCount, 4);
+assert.equal(summary.postguiNetCount, 5);
+assert.equal(summary.postguiRequiresHalcompReady, true);
+assert.equal(summary.hasToolChangeSimulationLoop, true);
+assert.equal(summary.semanticBoundary, model.semanticBoundary);
+
+console.log("gmoccapy_hal_model_smoke=ok");
diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs
new file mode 100644
index 0000000..f8010e3
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs
@@ -0,0 +1,84 @@
+import assert from "node:assert/strict";
+import { access, readFile } from "node:fs/promises";
+import { join } from "node:path";
+
+const repoRoot = new URL("../../..", import.meta.url).pathname.replace(/\/$/, "");
+const manifestPath = join(repoRoot, "web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json");
+const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
+
+assert.equal(manifest.schema, "web-rtcp-5axis-gmoccapy-button-icons-v1");
+assert.equal(manifest.semanticBoundary, "gmoccapy_button_icon_asset_reference_only_not_control_semantics");
+assert.equal(manifest.generatedFrom.csv, "work/working3/gmoccapy_button_icons/button_icon_inventory.csv");
+assert.equal(manifest.rowCount, 211);
+assert.equal(manifest.entries.length, 211);
+assert.equal(manifest.copiedIconCount, 112);
+
+const entriesByButton = new Map();
+for (const entry of manifest.entries) {
+ if (!entriesByButton.has(entry.buttonId)) entriesByButton.set(entry.buttonId, []);
+ entriesByButton.get(entry.buttonId).push(entry);
+ if (entry.projectAsset) {
+ await access(join(repoRoot, "web-rtcp-5axis-sim-plan", entry.projectAsset.replace(/^app\//, "app/")));
+ }
+}
+
+const coreButtons = [
+ "tbtn_estop",
+ "tbtn_on",
+ "rbt_manual",
+ "rbt_mdi",
+ "rbt_auto",
+ "tbtn_setup",
+ "tbtn_user_tabs",
+ "btn_homing",
+ "btn_tool",
+ "btn_touch",
+ "btn_load",
+ "btn_reload",
+ "btn_run",
+ "btn_stop",
+ "tbtn_pause",
+ "btn_step",
+ "ref_all",
+ "home_axis_x",
+ "home_axis_y",
+ "home_axis_z",
+ "home_axis_a",
+ "home_axis_b",
+ "touch_x",
+ "touch_y",
+ "touch_z",
+ "touch_a",
+ "touch_b",
+ "rbt_forward",
+ "rbt_reverse",
+ "rbt_stop",
+ "tbtn_flood",
+ "tbtn_mist",
+ "btn_zoom_in",
+ "btn_zoom_out",
+ "rbt_view_x",
+ "rbt_view_y",
+ "rbt_view_z",
+ "rbt_view_p",
+ "tbtn_view_tool_path",
+ "tbtn_view_dimension",
+];
+
+for (const buttonId of coreButtons) {
+ const entries = entriesByButton.get(buttonId) || [];
+ assert.equal(entries.length > 0, true, `${buttonId} missing from manifest`);
+ assert.equal(entries.some((entry) => entry.projectAsset || entry.sourceType === "none"), true, `${buttonId} lacks asset/fallback`);
+}
+
+assert.equal(entriesByButton.get("rbt_auto").some((entry) => entry.stateOrVariant === "active" && entry.iconName === "mode_auto_active"), true);
+assert.equal(entriesByButton.get("rbt_auto").some((entry) => entry.iconName === "mode_auto_inactive"), true);
+assert.equal(entriesByButton.get("tbtn_estop").some((entry) => entry.iconName === "main_switch_on"), true);
+assert.equal(entriesByButton.get("tbtn_estop").some((entry) => entry.iconName === "main_switch_off"), true);
+assert.equal(entriesByButton.get("tbtn_flood").some((entry) => entry.iconName === "coolant_flood_active"), true);
+assert.equal(entriesByButton.get("tbtn_mist").some((entry) => entry.iconName === "coolant_mist_inactive"), true);
+
+const copiedAssets = new Set(manifest.entries.map((entry) => entry.projectAsset).filter(Boolean));
+assert.equal(copiedAssets.size, 112);
+
+console.log("gmoccapy_icon_manifest_smoke=ok");
diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_registry.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_registry.mjs
new file mode 100644
index 0000000..5c4c594
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_registry.mjs
@@ -0,0 +1,63 @@
+import assert from "node:assert/strict";
+import { access, readFile } from "node:fs/promises";
+import { join } from "node:path";
+
+import {
+ GMOCAPY_ICON_REGISTRY_BOUNDARY,
+ getGmoccapyIcon,
+ listGmoccapyIconRegistryEntries,
+} from "../../app/src/ui/gmoccapy-icon-registry.js";
+
+const repoRoot = new URL("../../..", import.meta.url).pathname.replace(/\/$/, "");
+const manifestPath = join(repoRoot, "web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json");
+const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
+const manifestIconNames = new Set(manifest.entries.map((entry) => entry.iconName).filter(Boolean));
+const registryEntries = listGmoccapyIconRegistryEntries();
+
+assert.equal(GMOCAPY_ICON_REGISTRY_BOUNDARY, "gmoccapy_icon_registry_from_working3_manifest_core_ui_subset");
+assert.equal(registryEntries.length >= 40, true);
+
+const required = [
+ ["tbtn_estop", "active", "main_switch_on"],
+ ["tbtn_estop", "inactive", "main_switch_off"],
+ ["tbtn_on", "active", "power_on"],
+ ["rbt_manual", "active", "mode_manual_active"],
+ ["rbt_mdi", "active", "mode_mdi_active"],
+ ["rbt_auto", "active", "mode_auto_active"],
+ ["btn_load", "default", "open_file"],
+ ["btn_reload", "default", "refresh"],
+ ["btn_run", "default", "play"],
+ ["btn_stop", "default", "stop"],
+ ["tbtn_pause", "active", "pause_active"],
+ ["btn_step", "default", "step"],
+ ["btn_homing", "default", "ref_all"],
+ ["btn_tool", "default", "hsk_mill_tool"],
+ ["btn_touch", "default", "touch_off"],
+ ["rbt_forward", "active", "spindle_right_on"],
+ ["rbt_reverse", "active", "spindle_left_on"],
+ ["rbt_stop", "active", "spindle_stop_on"],
+ ["tbtn_flood", "active", "coolant_flood_active"],
+ ["tbtn_mist", "inactive", "coolant_mist_inactive"],
+ ["rbt_view_x", "default", "tool_axis_x"],
+ ["rbt_view_y", "default", "tool_axis_y"],
+ ["rbt_view_z", "default", "tool_axis_z"],
+ ["rbt_view_p", "default", "tool_axis_p"],
+ ["tbtn_view_tool_path", "default", "toolpath"],
+ ["tbtn_view_dimension", "default", "dimensions"],
+];
+
+for (const [buttonId, variant, expectedIconName] of required) {
+ const icon = getGmoccapyIcon(buttonId, variant);
+ assert.equal(icon.iconName, expectedIconName, `${buttonId}/${variant}`);
+ assert.equal(icon.fallback, false, `${buttonId}/${variant} should use an asset`);
+ assert.equal(icon.semanticBoundary, GMOCAPY_ICON_REGISTRY_BOUNDARY);
+ assert.equal(manifestIconNames.has(icon.iconName), true, `${icon.iconName} missing from manifest`);
+ assert.match(icon.path, /\/assets\/gmoccapy-icons\//);
+ await access(new URL(icon.path));
+}
+
+const fallback = getGmoccapyIcon("unknown_button", "active");
+assert.equal(fallback.iconName, "text_fallback");
+assert.equal(fallback.fallback, true);
+
+console.log("gmoccapy_icon_registry_smoke=ok");
diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs
new file mode 100644
index 0000000..2c11b40
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs
@@ -0,0 +1,180 @@
+import assert from "node:assert/strict";
+
+import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
+import { createSimulationStore, validateRunPreconditions } from "../../app/src/state/store.js";
+import { gateLinuxCncTaskAction } from "../../app/src/state/linuxcnc-task-policy.js";
+
+const profile = getFiveAxisProfile("gmoccapy-xyzab");
+const store = createSimulationStore();
+store.dispatch({ type: "SET_PROFILE", profileId: "gmoccapy-xyzab" });
+
+assert.equal(store.getState().profile.id, profile.id);
+assert.equal(store.getState().machine.noForceHoming, false);
+
+let gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
+assert.equal(gate.allowed, false);
+assert.equal(gate.operatorMessage, "run blocked: machine must be on");
+
+store.dispatch({ type: "TOGGLE_POWER" });
+gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
+assert.equal(gate.allowed, false);
+assert.equal(gate.operatorMessage, "run blocked: switch to auto mode first");
+
+store.dispatch({ type: "SET_MODE", mode: "auto" });
+gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
+assert.equal(gate.allowed, false);
+assert.equal(gate.operatorMessage, "run blocked: home machine first");
+
+store.dispatch({ type: "SET_MODE", mode: "manual" });
+store.dispatch({ type: "HOME" });
+store.dispatch({ type: "SET_MODE", mode: "auto" });
+gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
+assert.equal(gate.allowed, false);
+assert.equal(gate.operatorMessage, "run blocked: LinuxCNC INI not loaded");
+
+store.dispatch({
+ type: "ATTACH_INI_CONFIG",
+ profileId: "gmoccapy-xyzab",
+ iniConfig: {
+ profileId: "gmoccapy-xyzab",
+ path: profile.iniPath,
+ sourceText: "[TRAJ]\nCOORDINATES = X Y Z A B\nNO_FORCE_HOMING = 0\n[KINS]\nKINEMATICS = trivkins coordinates=xyzab\n",
+ validation: { ready: true },
+ traj: { coordinates: "XYZAB", noForceHoming: false },
+ kinematics: { name: "trivkins", sparm: "coordinates=xyzab" },
+ kinematicsParameters: profile.kinematicsParameters,
+ kinematicsModuleId: profile.kinematicsModuleId,
+ axisLimits: profile.axisLimits,
+ jointConfig: profile.jointConfig,
+ display: profile.display,
+ rs274ngc: profile.rs274ngc,
+ hal: {
+ ...profile.hal,
+ halcmd: profile.hal.halcmd,
+ initialSets: [],
+ },
+ halui: profile.halui,
+ emcmot: { servoPeriodNs: 1000000 },
+ emcio: {},
+ task: { cycleTimeSeconds: 0.001 },
+ },
+});
+
+gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
+assert.equal(gate.allowed, false);
+assert.equal(gate.operatorMessage, "run blocked: LinuxCNC machine files not staged");
+
+store.dispatch({
+ type: "MACHINE_FILE_STAGING_COMPLETE",
+ plan: {
+ profileId: "gmoccapy-xyzab",
+ selectedProgramSourceRel: null,
+ },
+ save: {
+ fileCount: 2,
+ files: [
+ {
+ sourceRel: "configs/sim/gmoccapy/gmoccapy_XYZAB.ini",
+ wasmPath: "/work/sim/gmoccapy/gmoccapy_XYZAB.ini",
+ kind: "ini",
+ text: "",
+ bytes: 0,
+ },
+ ],
+ summary: { gcodeFileCount: 0 },
+ },
+});
+
+gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
+assert.equal(gate.allowed, false);
+assert.equal(gate.operatorMessage, "run blocked: no machine-file G-code opened for task/HAL session");
+
+store.dispatch({
+ type: "MACHINE_FILE_STAGING_COMPLETE",
+ plan: {
+ profileId: "gmoccapy-xyzab",
+ selectedProgramSourceRel: "configs/sim/gmoccapy/demos/xyzab-demo.ngc",
+ },
+ save: {
+ fileCount: 2,
+ files: [
+ {
+ sourceRel: "configs/sim/gmoccapy/demos/xyzab-demo.ngc",
+ wasmPath: "/work/sim/gmoccapy/demos/xyzab-demo.ngc",
+ kind: "demo",
+ text: "G0 X0\nM2\n",
+ bytes: 9,
+ },
+ ],
+ summary: { gcodeFileCount: 1 },
+ },
+ selectedGcodeSourceRel: "configs/sim/gmoccapy/demos/xyzab-demo.ngc",
+});
+
+gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
+assert.equal(gate.allowed, false);
+assert.equal(gate.operatorMessage, "run blocked: task/HAL runtime not ready");
+
+store.dispatch({
+ type: "ATTACH_TASK_HAL_RUNTIME",
+ runtime: {
+ loaded: true,
+ readiness() {
+ return {
+ loaded: true,
+ taskRuntimeReady: true,
+ motionRuntimeReady: true,
+ halRuntimeReady: true,
+ };
+ },
+ },
+});
+
+gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
+assert.equal(gate.allowed, true);
+assert.equal(gate.status.canRunAutoStrict, true);
+assert.equal(gate.status.profileId, "gmoccapy-xyzab");
+
+const runPreconditions = validateRunPreconditions(store.getState(), {
+ requireTaskHalRuntime: false,
+ requireTaskHalSession: false,
+});
+assert.equal(runPreconditions.ok, false);
+assert.equal(runPreconditions.operatorMessage, "run blocked: unsupported five-axis profile gmoccapy-xyzab");
+
+const manualStore = createSimulationStore();
+manualStore.dispatch({ type: "SET_PROFILE", profileId: "gmoccapy-xyzab" });
+manualStore.dispatch({ type: "TOGGLE_POWER" });
+gate = gateLinuxCncTaskAction(manualStore.getState(), { type: "JOG", axis: "x", direction: 1 });
+assert.equal(gate.allowed, true);
+gate = gateLinuxCncTaskAction(manualStore.getState(), { type: "HOME" });
+assert.equal(gate.allowed, true);
+
+manualStore.dispatch({ type: "HOME" });
+manualStore.dispatch({ type: "SET_MODE", mode: "mdi" });
+gate = gateLinuxCncTaskAction(manualStore.getState(), { type: "RUN_MDI" });
+assert.equal(gate.allowed, true);
+
+const controlStore = createSimulationStore();
+controlStore.dispatch({ type: "SET_PROFILE", profileId: "gmoccapy-xyzab" });
+controlStore.dispatch({ type: "ESTOP" });
+gate = gateLinuxCncTaskAction(controlStore.getState(), { type: "TOGGLE_COOLANT", kind: "flood" });
+assert.equal(gate.allowed, false);
+assert.equal(gate.operatorMessage, "coolant blocked: machine must be on");
+gate = gateLinuxCncTaskAction(controlStore.getState(), { type: "SET_SPINDLE_DIRECTION", direction: "forward" });
+assert.equal(gate.allowed, false);
+assert.equal(gate.operatorMessage, "spindle blocked: machine must be on");
+
+controlStore.dispatch({ type: "RESET" });
+controlStore.dispatch({ type: "TOGGLE_POWER" });
+controlStore.dispatch({ type: "SET_SPINDLE_DIRECTION", direction: "forward" });
+assert.equal(controlStore.getState().spindle.enabled, true);
+assert.equal(controlStore.getState().spindle.direction, "forward");
+controlStore.dispatch({ type: "SET_SPINDLE_DIRECTION", direction: "stop" });
+assert.equal(controlStore.getState().spindle.enabled, false);
+assert.equal(controlStore.getState().spindle.direction, "stop");
+
+controlStore.dispatch({ type: "TOGGLE_COOLANT", kind: "flood" });
+assert.equal(controlStore.getState().coolant.flood, true);
+
+console.log("gmoccapy_xyzab_gates_smoke=ok");
diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs
new file mode 100644
index 0000000..9b7c4ae
--- /dev/null
+++ b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs
@@ -0,0 +1,116 @@
+import assert from "node:assert/strict";
+
+import { getFiveAxisProfile, fiveAxisProfiles } from "../../app/src/profiles/index.js";
+import { buildRtcpFrame } from "../../app/src/runtime/rtcp-frame.js";
+import { createLinuxCncBoundaryAdapter, createLinuxCncBoundaryReadiness } from "../../app/src/runtime/linuxcnc-boundary-adapter.js";
+import { createProfileSourceReferenceSummary } from "../../app/src/profiles/source-reference-map.js";
+import { createSimulationStore, validateRunPreconditions } from "../../app/src/state/store.js";
+
+const profile = getFiveAxisProfile("gmoccapy-xyzab");
+
+assert.equal(profile.id, "gmoccapy-xyzab");
+assert.equal(profile.title, "gmoccapy XYZAB trivkins reference");
+assert.equal(profile.iniPath, "linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini");
+assert.deepEqual(profile.coordinates, ["X", "Y", "Z", "A", "B"]);
+assert.equal(profile.traj.coordinates, "XYZAB");
+assert.equal(profile.joints.length, 5);
+assert.equal(profile.jointConfig.length, 5);
+assert.equal(profile.kinematics, "trivkins coordinates=xyzab");
+assert.equal(profile.kinematicsModuleId, "gmoccapy-xyzab-trivkins-reference");
+assert.equal(profile.tcpCapable, false);
+assert.equal(profile.rtcpProof, false);
+assert.equal(profile.linuxCncKinematicsReady, false);
+assert.equal(profile.promotionAllowed, false);
+assert.equal(profile.semanticBoundary, "gmoccapy_xyzab_trivkins_reference_only_not_rtcp_proof");
+assert.equal(profile.homing.noForceHoming, false);
+assert.equal(profile.homing.noForceHomingIniValue, 0);
+assert.equal(profile.hal.postguiRequiresHalcompReady, true);
+assert.deepEqual(profile.hal.halFiles, ["core_sim_XYZAB.hal", "spindle_sim.hal", "simulated_home.hal"]);
+assert.deepEqual(profile.hal.postguiHalFiles, ["gmoccapy_postgui.hal"]);
+assert.equal(profile.nativeRuntime.task, "milltask");
+assert.equal(profile.nativeRuntime.emcmot, "motmod");
+assert.equal(profile.nativeRuntime.halui, "halui");
+assert.equal(profile.nativeRuntime.displayCycleTimeMs, 100);
+assert.equal(profile.nativeRuntime.servoPeriodNs, 1000000);
+assert.equal(profile.axisLimits.X.max, 600);
+assert.equal(profile.axisLimits.Y.max, 400);
+assert.equal(profile.axisLimits.Z.min, -400);
+assert.equal(profile.axisLimits.B.maxVelocity, 90);
+assert.equal(profile.jointConfig[4].axis, "B");
+assert.equal(profile.jointConfig[4].homeOffset, 45);
+assert.equal(profile.hal.halcmd.feedbackNets.length, 5);
+assert.equal(profile.hal.halcmd.offsetNets.some((net) => net.target === "gmoccapy.tooloffset-z"), true);
+assert.equal(profile.hal.halcmd.simulationLoops.some((net) => net.signal === "tool-change-loop"), true);
+assert.equal(profile.halPins.includes("gmoccapy.program.current-line"), true);
+assert.equal(profile.halPins.includes("gmoccapy.spindle_at_speed_led"), true);
+assert.equal(profile.toolTable.toolCount, 17);
+assert.equal(profile.macros.includes("go_to_position X-pos Y-pos Z-pos"), true);
+assert.equal(profile.panelSchema.id, "gmoccapy-xyzab-hal-component");
+assert.equal(profile.panelSchema.boundary, "gmoccapy_hal_component_reference_only");
+assert.equal(profile.panelSchema.groups.length, 2);
+
+assert.deepEqual(fiveAxisProfiles.map(({ id }) => id), ["xyzac-trt", "xyzbc-trt", "gmoccapy-xyzab"]);
+
+const sourceSummary = createProfileSourceReferenceSummary("gmoccapy-xyzab");
+assert.equal(sourceSummary.profileId, "gmoccapy-xyzab");
+assert.equal(sourceSummary.referenceCount >= 10, true);
+assert.equal(sourceSummary.kinds.includes("ini"), true);
+assert.equal(sourceSummary.kinds.includes("halfile"), true);
+assert.equal(sourceSummary.kinds.includes("postgui_hal"), true);
+assert.equal(sourceSummary.kinds.includes("icon_manifest"), true);
+assert.equal(sourceSummary.kinds.includes("python_gui"), true);
+assert.equal(sourceSummary.promotionAllowed, false);
+assert.equal(sourceSummary.semanticBoundary, "profile_source_map_only_not_runtime_proof");
+assert.ok(sourceSummary.references.some((reference) => reference.path.endsWith("gmoccapy_XYZAB.ini")));
+assert.ok(sourceSummary.references.some((reference) => reference.path.endsWith("gmoccapy_postgui.hal")));
+assert.ok(sourceSummary.references.some((reference) => reference.path.endsWith("gmoccapy-button-icons.json")));
+
+const adapter = createLinuxCncBoundaryAdapter({ profile });
+assert.equal(adapter.profileId, "gmoccapy-xyzab");
+assert.equal(adapter.profileSummary.coordinates, "XYZAB");
+assert.equal(adapter.profileSummary.jointCount, 5);
+assert.equal(adapter.profileSummary.mdiCommandCount, 0);
+assert.equal(adapter.profileSummary.remapCount, 2);
+assert.equal(adapter.profileSummary.toolCount, 17);
+assert.equal(adapter.profileSummary.feedbackNetCount, 5);
+assert.equal(adapter.profileSummary.halFileCount, 4);
+assert.equal(adapter.promotionAllowed, false);
+assert.equal(adapter.linuxCncKinematicsReady, false);
+
+const readiness = createLinuxCncBoundaryReadiness(adapter);
+assert.equal(readiness.ready, false);
+assert.equal(readiness.promotionAllowed, false);
+assert.equal(readiness.missing.includes("kinematics runtime"), true);
+
+const frame = buildRtcpFrame({
+ axisPose: { x: 10, y: 20, z: -5, a: 30, b: 45, c: 99 },
+ activeLine: 10,
+ profile,
+});
+assert.equal(frame.profileId, "gmoccapy-xyzab");
+assert.deepEqual(frame.jointPose.map((joint) => joint.axis), ["X", "Y", "Z", "A", "B"]);
+assert.equal(frame.jointPose[4].value, 45);
+assert.equal(frame.readiness.linuxCncKinematicsReady, false);
+assert.equal(frame.readiness.promotionAllowed, false);
+
+const store = createSimulationStore();
+store.dispatch({ type: "SET_PROFILE", profileId: "gmoccapy-xyzab" });
+assert.equal(store.getState().machineProfile, "gmoccapy-xyzab");
+assert.equal(store.getState().profile.tcpCapable, false);
+assert.equal(store.getState().rtcpState, "off");
+store.dispatch({ type: "SET_KINS_TYPE", kinsType: "tcp-xyzac" });
+assert.equal(store.getState().kinsType, "identity");
+assert.equal(store.getState().rtcpState, "off");
+assert.equal(store.getState().operatorMessage, "kinematics tcp-xyzac blocked: gmoccapy-xyzab is not TCP capable");
+store.dispatch({ type: "SET_RTCP", enabled: true });
+assert.equal(store.getState().rtcpState, "off");
+assert.equal(store.getState().operatorMessage, "RTCP blocked: gmoccapy-xyzab is trivkins coordinates=xyzab reference only");
+
+const preconditions = validateRunPreconditions(store.getState(), {
+ requireTaskHalRuntime: false,
+ requireTaskHalSession: false,
+});
+assert.equal(preconditions.ok, false);
+assert.equal(preconditions.operatorMessage, "run blocked: unsupported five-axis profile gmoccapy-xyzab");
+
+console.log("gmoccapy_xyzab_profile_smoke=ok");
diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs
index 906bc1d..dda20aa 100644
--- a/web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs
+++ b/web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs
@@ -30,7 +30,7 @@ assert.equal(xyzacTrtProfile.halPins.includes("motion.switchkins-type"), true);
assert.equal(xyzacTrtProfile.halPins.includes("xyzac-trt-kins.tool-offset"), true);
assert.equal(xyzacTrtProfile.promotionAllowed, false);
assert.equal(xyzacTrtProfile.linuxCncKinematicsReady, false);
-assert.deepEqual(fiveAxisProfiles.map(({ id }) => id), ["xyzac-trt", "xyzbc-trt"]);
+assert.deepEqual(fiveAxisProfiles.map(({ id }) => id), ["xyzac-trt", "xyzbc-trt", "gmoccapy-xyzab"]);
const xyzbcTrtProfile = getFiveAxisProfile("xyzbc-trt");
assert.equal(xyzbcTrtProfile.id, "xyzbc-trt");
@@ -59,6 +59,19 @@ assert.equal(xyzbcSourceSummary.referenceCount >= 7, true);
assert.ok(xyzbcSourceSummary.references.some((reference) => reference.path.endsWith("xyzbc-trt.ini")));
assert.ok(xyzbcSourceSummary.references.some((reference) => reference.path.endsWith("xyzbc-trt-kins.c")));
+const gmoccapyXyzabProfile = getFiveAxisProfile("gmoccapy-xyzab");
+assert.equal(gmoccapyXyzabProfile.id, "gmoccapy-xyzab");
+assert.deepEqual(gmoccapyXyzabProfile.coordinates, ["X", "Y", "Z", "A", "B"]);
+assert.equal(gmoccapyXyzabProfile.kinematics, "trivkins coordinates=xyzab");
+assert.equal(gmoccapyXyzabProfile.tcpCapable, false);
+assert.equal(gmoccapyXyzabProfile.rtcpProof, false);
+assert.equal(gmoccapyXyzabProfile.promotionAllowed, false);
+assert.equal(gmoccapyXyzabProfile.hal.postguiRequiresHalcompReady, true);
+const gmoccapySourceSummary = createProfileSourceReferenceSummary("gmoccapy-xyzab");
+assert.equal(gmoccapySourceSummary.referenceCount >= 10, true);
+assert.ok(gmoccapySourceSummary.references.some((reference) => reference.path.endsWith("gmoccapy_XYZAB.ini")));
+assert.ok(gmoccapySourceSummary.references.some((reference) => reference.path.endsWith("gmoccapy_postgui.hal")));
+
const panelSummary = createPyvcpHalBindingSummary(xyzacTrtPyvcpPanelSchema);
assert.equal(panelSummary.schemaId, "xyzac-trt-switchkins-pyvcp");
assert.equal(panelSummary.controlCount, 5);
diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs
index b8a7d90..10b1355 100644
--- a/web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs
+++ b/web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs
@@ -261,7 +261,10 @@ assert.equal(kinematicsState.rtcpFrame.readiness.linuxCncKinematicsReady, false)
const store = createSimulationStore();
let state;
-assert.equal(store.getState().availableProfiles.length, 2);
+assert.deepEqual(
+ store.getState().availableProfiles.map((profile) => profile.id),
+ ["xyzac-trt", "xyzbc-trt", "gmoccapy-xyzab"],
+);
store.dispatch({ type: "SET_PROFILE", profileId: "xyzbc-trt" });
assert.equal(store.getState().machineProfile, "xyzbc-trt");
assert.equal(store.getState().profile.traj.coordinates, "XYZBC");
diff --git a/work/working4/01-function-content.md b/work/working4/01-function-content.md
new file mode 100644
index 0000000..10ef401
--- /dev/null
+++ b/work/working4/01-function-content.md
@@ -0,0 +1,98 @@
+# 01 功能内容
+
+生成日期:2026-06-26
+
+## 1. 目标
+
+使用 `working3` 中的 gmoccapy_XYZAB 执行链路分析和按钮图标资料,完善
+`web-rtcp-5axis-sim-plan` 的 gmoccapy 风格 Web 五轴仿真功能。
+
+本阶段目标不是把 LinuxCNC GTK/gmoccapy 原程序移植到浏览器,也不是让浏览器直接运行
+LinuxCNC native NML/HAL 进程。目标是把 gmoccapy_XYZAB 的运行条件、按钮语义、NML/HAL
+通信模型和图标资产转化为 Web 仿真项目可实施、可验证、可追溯的功能包。
+
+## 2. 当前项目现状
+
+`web-rtcp-5axis-sim-plan` 已具备这些基础能力:
+
+- gmoccapy 风格 Web shell:标题栏、预览区、DRO、G-code 面板、右侧模式栏、信息面板、override、主轴/冷却、底部控制栏。
+- 五轴 profile:当前已有 `xyzac-trt`、`xyzbc-trt`。
+- LinuxCNC source-derived runtime:已有 kinematics、interpreter、task/HAL runtime 接入和 worker fallback。
+- machine-file staging:可加载 LinuxCNC vendored 源程序。
+- RUN 主链路验证:已有 run preconditions、feedback loop、linear unit、browser evidence 等测试文档和证据。
+- 已有追溯矩阵:`docs/traceability-matrix.md` 记录了 Web 实现和 LinuxCNC 来源的边界。
+
+## 3. working3 新增价值
+
+`working3` 提供了两类资料,本阶段已将其转化为项目内 profile、runtime model、UI 图标、
+task gate、测试和 QA 证据:
+
+### 3.1 gmoccapy_XYZAB 执行与通信链路
+
+输入文件:
+
+```text
+/home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md
+```
+
+可转化为 Web 功能的关键点:
+
+- `gmoccapy_XYZAB.ini` 是 LinuxCNC 运行配置,不是单独程序。
+- 启动链路包括 `linuxcncsvr`、realtime/RTAPI/HAL、`milltask`、`halui`、HALFILE、`halcmd start`、`gmoccapy -ini`。
+- GUI 主要通过 `linuxcnc.command()`、`linuxcnc.stat()`、`linuxcnc.error_channel()` 与 `milltask` 通信。
+- HAL 侧通过 `gmoccapy.*` pins、`POSTGUI_HALFILE`、`halui`、`iocontrol`、`motmod`、`spindle_sim` 等连接状态和外部控制。
+- 当前 XYZAB 配置为 `trivkins coordinates=xyzab`,`JOINTS=5`,属于仿真回环机,不是 TCP/RTCP 运动学。
+- `NO_FORCE_HOMING = 0`,自动运行和 MDI 必须经过急停复位、上电、回零、模式切换、解释器空闲等互锁。
+
+### 3.2 gmoccapy 按钮图标和控制清单
+
+输入文件:
+
+```text
+/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.md
+/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.csv
+/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/files/
+```
+
+清单规模:
+
+- 清单行总数:211。
+- 有图标或图像的按钮/状态:116。
+- 纯文字或无图标按钮/状态:95。
+- 已复制图标文件:112 个。
+- 覆盖右侧主状态栏、底部主按钮栏、Auto 程序运行栏、MDI/宏、回零、Touch Off、Jog、主轴冷却、预览、编辑页、刀具页、文件选择页、内嵌 ToolEdit/OffsetPage、设置页控件。
+
+## 4. 本阶段功能包状态
+
+| 功能包 | 内容 | 当前状态 | 完成后效果 |
+| --- | --- | --- | --- |
+| F-XYZAB-01 gmoccapy_XYZAB 参考 profile | 把 `gmoccapy_XYZAB.ini` 的坐标、HALFILE、POSTGUI_HALFILE、`trivkins xyzab`、回零策略整理为 Web profile/source map。 | 已完成 | Web UI 可切换到 XYZAB 参考配置,并清楚标注其为 trivkins 仿真 profile。 |
+| F-XYZAB-02 启动链路和通信诊断 | 在信息面板展示 LinuxCNC native 启动阶段、NML command/status/error、HAL/postgui 通信边界。 | 已完成 | 用户能看到 Web 仿真与 native gmoccapy 的通信差异,不把 UI 行为误认为真实 NML/HAL 进程。 |
+| F-XYZAB-03 G-code 执行互锁强化 | 按 gmoccapy_XYZAB 的三层保护补齐 RUN/MDI/JOG/HOME/SPINDLE/COOLANT gate 说明和测试。 | 已完成 | RUN/MDI 等按钮 disabled 原因可解释,Node/browser 测试覆盖典型非法状态。 |
+| F-XYZAB-04 按钮语义和图标资产映射 | 从 CSV 生成 Web 可消费按钮 manifest,并把核心 icon 接入右侧栏、底部栏、Auto 栏、预览栏、文件栏。 | 已完成 | UI 从文字按钮升级为 gmoccapy 风格图标按钮,active/inactive 状态可验证。 |
+| F-XYZAB-05 HAL pin 与 postgui 状态面板 | 展示 `gmoccapy.*` pins、spindle feedback、tool offset、program progress、HALUI 等诊断状态。 | 已完成 | 信息面板可解释哪些是 NML 状态,哪些是 HAL pin 状态,哪些是 Web 仿真状态。 |
+| F-XYZAB-06 宏/MDI/Tool/Touch 工作流 | 参考 gmoccapy 宏和工具页按钮逻辑,补齐 Web 侧宏按钮、MDI 历史、Tool/Touch 操作互锁。 | 边界内完成 | W4 验收覆盖 MDI gate、home/tool/touch 图标和互锁;宏编辑和 MDI 历史扩展未作为本阶段新增验收项。 |
+| F-XYZAB-07 验收证据自动化 | 为新增 profile、按钮、互锁、图标、HAL 诊断补 Node/browser/PDF/截图证据。 | 已完成 | 每个功能都有命令、页面、JSON、截图或 PDF 证据。 |
+
+## 5. 不做事项
+
+本阶段不做:
+
+- 不移植 GTK、Glade、Python gmoccapy runtime。
+- 不启动 native `linuxcncsvr`、`milltask`、`halui`、`motmod` 作为浏览器运行依赖。
+- 不把 `gmoccapy_XYZAB` 的 `trivkins` profile 声称为 RTCP/TCP 运动学 proof。
+- 不用 JavaScript 自行发明 G-code 解释器、刀补、REMAP 或运动规划语义。
+- 不把 icon 清单复制到 UI 后就认定控制语义完成,按钮仍必须通过 task policy 和 runtime 证据验收。
+
+## 6. 已满足的成功定义
+
+本阶段收口时满足:
+
+- 代码中有 `gmoccapy-xyzab` 或等价 reference profile,并显示 `XYZAB`、`trivkins`、HALFILE、POSTGUI_HALFILE、`NO_FORCE_HOMING=0` 等来源信息。
+- UI 核心按钮使用 `working3` 清单中的图标或 manifest,且 active/inactive 状态可被浏览器测试识别。
+- RUN、MDI、JOG、HOME、ESTOP、POWER、SPINDLE、COOLANT 的 gate 与 gmoccapy_XYZAB 文档一致。
+- HAL/postgui 诊断能区分 NML 命令、NML 状态、HAL pin、Web task/HAL simulation status。
+- `npm --prefix web-rtcp-5axis-sim-plan/app run build` 通过。
+- `npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node` 通过。
+- 浏览器 smoke 覆盖 profile 切换、按钮 active 图标、canvas 非空、RUN blocked reason 和 HAL diagnostics;source-derived RTCP 运行证据仍由既有 `xyzac-trt`、`xyzbc-trt` 验收链路承担。
+- `05-acceptance-evidence.md` 已追加实际 JSON、截图、PDF 和报告路径。
diff --git a/work/working4/02-program-development-steps.md b/work/working4/02-program-development-steps.md
new file mode 100644
index 0000000..97c790f
--- /dev/null
+++ b/work/working4/02-program-development-steps.md
@@ -0,0 +1,316 @@
+# 02 程序开发详细步骤
+
+生成日期:2026-06-26
+
+## 1. 开发原则
+
+- 先复用现有 `app/src` 架构:`profiles`、`runtime`、`state`、`ui`、`visualization`、`tests`。
+- 不引入 React/Vue 等 UI 框架。
+- 不把 gmoccapy native Python/GTK 代码作为浏览器依赖。
+- 图标资产可以使用 `working3/gmoccapy_button_icons/files/`,但按钮控制语义必须由现有 store/task policy/runtime 驱动。
+- 每个阶段必须同步更新 `04-task-matrix.md` 和 `03-progress-ledger.md`。
+
+当前状态:阶段 A 到 G 已按 `04-task-matrix.md` 收口为 `done`。本文件保留为复现步骤和后续变更清单。
+
+## 2. 阶段 A:资料和资产落库
+
+目标:把 `working3` 资料变成项目内可追溯输入,而不是散落引用。
+
+主要新增或更新文件:
+
+```text
+web-rtcp-5axis-sim-plan/docs/gmoccapy-xyzab-reference.md
+web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json
+web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/
+web-rtcp-5axis-sim-plan/docs/traceability-matrix.md
+```
+
+实施步骤:
+
+1. 新建 `docs/gmoccapy-xyzab-reference.md`,摘要吸收 `gmoccapy_XYZAB_execution_analysis.md`。
+2. 将 `button_icon_inventory.csv` 转换为 JSON manifest,保留字段:分类、button_id、label、tooltip、icon_name、requested_size、source_type、copied_file、signals、notes。
+3. 将核心按钮图标复制到 `app/src/assets/gmoccapy-icons/`,至少覆盖:estop、power、manual、mdi、auto、settings、home、tool、touch、open、run、stop、pause、step、reload、fullscreen、spindle、coolant、view/zoom。
+4. 在 manifest 中记录原始来源和 `working3` 复制文件路径。
+5. 更新 `docs/traceability-matrix.md`,新增 gmoccapy_XYZAB 和按钮 icon 追溯记录。
+
+验收命令:
+
+```bash
+find web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons -maxdepth 1 -type f | wc -l
+node -e "JSON.parse(require('fs').readFileSync('web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json','utf8')); console.log('icon_manifest_json=ok')"
+git diff --check -- web-rtcp-5axis-sim-plan
+```
+
+验收标准:
+
+- JSON manifest 可解析。
+- 图标文件存在,文件名稳定,不依赖 `working3` 运行时路径。
+- 文档明确图标只证明 UI 资产来源,不证明按钮控制语义。
+
+## 3. 阶段 B:新增 gmoccapy_XYZAB reference profile
+
+目标:在 Web 项目中建立 XYZAB/gmoccapy 参考 profile,表达 native INI/HAL 拓扑。
+
+主要新增或更新文件:
+
+```text
+web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js
+web-rtcp-5axis-sim-plan/app/src/profiles/index.js
+web-rtcp-5axis-sim-plan/app/src/profiles/source-reference-map.js
+web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs
+```
+
+实施步骤:
+
+1. 新增 profile id:`gmoccapy-xyzab`。
+2. 设置坐标:`["X", "Y", "Z", "A", "B"]`。
+3. 设置 kinematics:`trivkins coordinates=xyzab`,并标记 `tcpCapable=false`、`rtcpProof=false`。
+4. 记录 INI 来源:`linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini`。
+5. 记录 HALFILE:`core_sim_XYZAB.hal`、`spindle_sim.hal`、`simulated_home.hal`。
+6. 记录 POSTGUI_HALFILE:`gmoccapy_postgui.hal`。
+7. 记录 `TASK=milltask`、`EMCMOT=motmod`、`HALUI=halui`、`CYCLE_TIME=100`、`SERVO_PERIOD=1000000`。
+8. 加入 `fiveAxisProfiles` 列表,但 UI 文案必须显示它是 gmoccapy/trivkins 参考机型。
+9. Node 测试断言 profile 坐标、joint 数、HAL files、postgui、no-force-homing gate、semantic boundary。
+
+验收命令:
+
+```bash
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs
+npm --prefix web-rtcp-5axis-sim-plan/app run build
+```
+
+验收标准:
+
+- `gmoccapy-xyzab` 可通过 profile selector 选择。
+- 信息面板显示 `XYZAB`、`trivkins`、`gmoccapy_XYZAB.ini`、HALFILE、POSTGUI_HALFILE。
+- 不显示 `RTCP proof ready`,除非后续接入真实 source-derived TCP 运动学。
+
+## 4. 阶段 C:NML/HAL 通信边界诊断
+
+目标:让 Web UI 能解释 gmoccapy native 与 Web 仿真的通信差异。
+
+主要新增或更新文件:
+
+```text
+web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js
+web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
+web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
+web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs
+```
+
+实施步骤:
+
+1. 建立 communication model 数据结构:
+ - native command path:`gmoccapy -> linuxcnc.command -> NML emcCommand -> milltask`。
+ - native status path:`milltask -> NML emcStatus/emcError -> stat/error poll`。
+ - native HAL path:`gmoccapy.* pins <-> HAL shared memory`。
+ - Web path:`button -> store dispatch -> task policy/runtime -> status snapshot -> UI/Three.js`。
+2. 在 info tabs 中新增 `gmoccapy comms` 或合并到 RTCP diagnostics。
+3. 对每个 action 显示 native equivalent command,例如 `STATE_ON`、`MODE_AUTO`、`AUTO_RUN`、`JOG_CONTINUOUS`。
+4. 显示 postgui 顺序:`halcomp.ready()` 后才能执行 POSTGUI_HALFILE。
+5. Node 测试断言主要命令链路和 HAL/postgui 说明存在。
+
+验收命令:
+
+```bash
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs
+```
+
+验收标准:
+
+- UI 能区分 NML command、NML status/error、HAL pin、Web runtime feedback。
+- 文案不暗示浏览器直接连接 native NML buffer 或 HAL shared memory。
+
+## 5. 阶段 D:按钮图标和语义映射
+
+目标:将当前文字按钮升级为 gmoccapy 风格 icon button,同时保持 task policy gate。
+
+主要新增或更新文件:
+
+```text
+web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-icon-registry.js
+web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
+web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
+web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs
+web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html
+```
+
+实施步骤:
+
+1. 从 JSON manifest 建立 `getGmoccapyIcon(buttonId, stateVariant)`。
+2. 右侧主状态栏接入:
+ - `tbtn_estop`:`main_switch_on/off`。
+ - `tbtn_on`:`power_off/on`。
+ - `rbt_manual`、`rbt_mdi`、`rbt_auto`:inactive/active 图标。
+ - `tbtn_setup`、`tbtn_user_tabs`。
+3. 底部栏接入:
+ - exit、homing、tool、touch、fullscreen、open、reload、run、stop、pause、step。
+4. Spindle/Coolant 接入:
+ - spindle forward/reverse/stop active 状态。
+ - flood/mist active/inactive 状态。
+5. Preview toolbar 接入:
+ - view x/y/z/p、zoom in/out、toolpath、dimensions。
+6. 保留按钮 `aria-label`、title 和 disabled reason,不能只显示图标。
+7. browser smoke 增加断言:核心按钮存在 `data-icon-name`、active variant 和 disabled reason。
+
+验收命令:
+
+```bash
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs
+npm --prefix web-rtcp-5axis-sim-plan/app run build
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
+```
+
+验收标准:
+
+- 核心按钮有图标、tooltip 或 aria-label。
+- active/inactive 状态随 store 状态变化。
+- RUN disabled 时仍显示 blocked reason。
+- 图标未加载时有稳定文本 fallback。
+
+## 6. 阶段 E:G-code 执行互锁和按钮 gate 扩展
+
+目标:按 gmoccapy_XYZAB 的执行条件补齐 Web gate 和测试。
+
+主要新增或更新文件:
+
+```text
+web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js
+web-rtcp-5axis-sim-plan/app/src/state/store.js
+web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs
+web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html
+```
+
+实施步骤:
+
+1. RUN gate 覆盖:
+ - INI loaded。
+ - machine-file opened。
+ - not estop。
+ - machine on。
+ - all homed when `NO_FORCE_HOMING=0`。
+ - mode auto。
+ - interpreter idle。
+ - task/HAL runtime ready when required。
+2. MDI gate 覆盖:
+ - machine on。
+ - all homed when `NO_FORCE_HOMING=0`。
+ - mode mdi 或可安全切换 MDI。
+ - interpreter idle 或允许队列追加。
+3. JOG gate 覆盖:
+ - machine on。
+ - mode manual。
+ - not running。
+ - axis/joint available。
+4. HOME gate 覆盖:
+ - machine on。
+ - not running。
+ - no joint already homing。
+5. SPINDLE/COOLANT/OVERRIDE gate 覆盖:
+ - estop/off 状态禁用。
+ - auto/mdi reading/waiting 时限制手动覆盖主轴方向。
+6. Stop/Abort 必须在多数状态下可用,并清理 pause/start_line/active UI 状态。
+7. 增加每个 gate 的 operatorMessage,供按钮 title 和证据 JSON 使用。
+
+验收命令:
+
+```bash
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs
+node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
+node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs
+```
+
+验收标准:
+
+- 非法状态有明确 blocked reason。
+- 合法状态 RUN/MDI/JOG/HOME 可通过。
+- 原有 `verify_run_preconditions.mjs` 不回归。
+
+## 7. 阶段 F:HAL pin/postgui 诊断面板
+
+目标:把 gmoccapy_XYZAB 的 HAL pin 和 postgui 连接以 Web 诊断方式展示。
+
+主要新增或更新文件:
+
+```text
+web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js
+web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
+web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs
+```
+
+实施步骤:
+
+1. 建立 HAL model 分类:
+ - hard buttons:`gmoccapy.h-button.*`、`gmoccapy.v-button.*`。
+ - jog pins:`gmoccapy.jog.axis.*`、`gmoccapy.jog.jog-inc-*`。
+ - override pins:feed/spindle/jog/rapid counts 和 direct-value。
+ - tool pins:tooloffset、toolchange、diameter。
+ - program pins:length、current-line、progress。
+ - error/message pins。
+2. 建立 postgui nets:
+ - spindle feedback bar。
+ - spindle at-speed LED。
+ - tooloffset x/z。
+ - simulated tool-change loop。
+3. UI 显示 `native pin name`、`web source`、`current value`、`semantic boundary`。
+4. Node 测试断言关键 pins 和 postgui nets 存在。
+
+验收命令:
+
+```bash
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs
+```
+
+验收标准:
+
+- 诊断面板清楚显示哪些是 native gmoccapy pins,哪些是 Web runtime 映射。
+- tool-change loop 被标记为 simulation loop,不表示真实人工换刀。
+
+## 8. 阶段 G:浏览器证据和报告
+
+目标:为本功能包生成可复查证据。
+
+主要新增或更新文件:
+
+```text
+qa/web-rtcp-5axis-site-test/
+qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json
+qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.pdf
+qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/
+work/working4/05-acceptance-evidence.md
+```
+
+实施步骤:
+
+1. 增加页面自动化用例:
+ - profile 切换到 `gmoccapy-xyzab`。
+ - 检查 DRO 显示 X/Y/Z/A/B。
+ - 检查 `trivkins`、`NO_FORCE_HOMING=0`、HALFILE、POSTGUI_HALFILE。
+ - 检查按钮图标 active/inactive。
+ - 检查 RUN blocked reason。
+ - 完成一次合法状态 RUN 或模拟 RUN。
+2. 保存截图:
+ - loaded。
+ - profile selected。
+ - run blocked。
+ - run ready。
+ - running。
+ - HAL diagnostics。
+3. 保存 JSON report。
+4. 输出 JSON 和 PDF 报告;本阶段已生成 `gmoccapy-xyzab-function-report.json` 和 `.pdf`。
+5. 更新 `05-acceptance-evidence.md`。
+
+验收命令:
+
+```bash
+npm --prefix web-rtcp-5axis-sim-plan/app run build
+npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
+node qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs
+```
+
+验收标准:
+
+- JSON report 中所有 case 为 PASS。
+- 截图文件存在且页面非空。
+- PDF 或替代报告路径写入 `05-acceptance-evidence.md`。
diff --git a/work/working4/03-progress-ledger.md b/work/working4/03-progress-ledger.md
new file mode 100644
index 0000000..55f8533
--- /dev/null
+++ b/work/working4/03-progress-ledger.md
@@ -0,0 +1,512 @@
+# 03 推进台账
+
+生成日期:2026-06-26
+
+说明:本台账保留历史推进记录。早期条目中的“遗留问题”和“下一步”已由后续 W4-IMPL-002
+和 W4-DOC-002 收口;当前状态以 `04-task-matrix.md` 和 `05-acceptance-evidence.md` 为准。
+
+## 1. 台账规则
+
+每轮结束必须追加一条记录,格式保持一致:
+
+```text
+轮次:
+日期:
+目标:
+做了什么:
+改了哪些文件:
+验证了什么:
+结果:
+遗留问题:
+下一步:
+```
+
+如果一轮只写文档,没有改代码,也必须明确写出“未改代码”。如果运行了测试,必须记录命令和结果。如果没有运行测试,必须说明原因。
+
+## 2. 当前台账
+
+### W4-DOC-001
+
+日期:2026-06-26
+
+目标:
+
+使用 `working3` 资料,为 `web-rtcp-5axis-sim-plan` 下一阶段功能完善建立文档包。
+
+做了什么:
+
+- 梳理 `working3/gmoccapy_XYZAB_execution_analysis.md`,提取 gmoccapy_XYZAB native 启动顺序、NML command/status/error、HAL/postgui、G-code 运行互锁和按钮控制逻辑。
+- 梳理 `working3/gmoccapy_button_icons/`,确认按钮清单、CSV、112 个图标资源和 211 行按钮/状态记录。
+- 对照 `web-rtcp-5axis-sim-plan` 当前 README、docs、working_run、app/src、tests,确认已有 gmoccapy shell、xyzac/xyzbc profile、kinematics/interpreter/task-HAL runtime 和 RUN 验证。
+- 建立 `working4` 文档集,覆盖功能内容、开发步骤、推进台账、任务矩阵、验收证据、决策记录。
+
+改了哪些文件:
+
+```text
+/home/meswork/cnc_wams/work/working4/README.md
+/home/meswork/cnc_wams/work/working4/01-function-content.md
+/home/meswork/cnc_wams/work/working4/02-program-development-steps.md
+/home/meswork/cnc_wams/work/working4/03-progress-ledger.md
+/home/meswork/cnc_wams/work/working4/04-task-matrix.md
+/home/meswork/cnc_wams/work/working4/05-acceptance-evidence.md
+/home/meswork/cnc_wams/work/working4/06-decision-records.md
+```
+
+验证了什么:
+
+- 验证 `working3` 输入文件存在。
+- 验证 `working4` 起始为空,可作为本轮输出目录。
+- 验证目标项目已有构建、Node smoke、browser smoke、QA evidence 目录和相关文档。
+- 本轮以文档生成为主,未修改 `web-rtcp-5axis-sim-plan/app/src` 代码。
+
+结果:
+
+- 文档规划完成。
+- 任务矩阵建立,后续实施可按任务编号推进,避免重复做。
+
+遗留问题:
+
+- 尚未把 `working3` 图标复制到 app assets。
+- 尚未新增 `gmoccapy-xyzab` profile。
+- 尚未新增图标 manifest、HAL diagnostics、XYZAB gate 测试和浏览器报告。
+
+下一步:
+
+从 `04-task-matrix.md` 的 `W4-A01` 开始,将资料和图标资产落库。
+
+### W4-IMPL-001
+
+日期:2026-06-26
+
+目标:
+
+按 `working4` 从 W4-A01 开始实施资料落库、gmoccapy_XYZAB reference profile、通信/HAL 诊断模型和 Node 验收。
+
+做了什么:
+
+- 新增 `docs/gmoccapy-xyzab-reference.md`,记录 native 启动顺序、NML command/status/error、HAL/postgui、互锁和图标资产边界。
+- 将 `working3/gmoccapy_button_icons/button_icon_inventory.csv` 转成 `app/src/ui-reference/gmoccapy-button-icons.json`,保留 211 行清单。
+- 将 `working3/gmoccapy_button_icons/files/` 的 112 个 PNG/SVG 图标复制到 `app/src/assets/gmoccapy-icons/`。
+- 新增 `gmoccapy-xyzab` profile,坐标为 XYZAB,标记 `trivkins coordinates=xyzab`、`tcpCapable=false`、`rtcpProof=false`、`promotionAllowed=false`。
+- 新增 gmoccapy communication model 和 HAL/postgui model。
+- 在 UI/store 层阻止 `gmoccapy-xyzab` 这种 reference-only profile 被切到 TCP/RTCP。
+- 修正 fallback RTCP frame 的 joint pose 轴映射,使 XYZAB 显示 B 轴而不是误用 C 轴。
+- 新增并接入 Node 测试:XYZAB profile、icon manifest、communication model、HAL model。
+- 更新 `docs/traceability-matrix.md`、`04-task-matrix.md`、`05-acceptance-evidence.md`。
+
+改了哪些文件:
+
+```text
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/docs/gmoccapy-xyzab-reference.md
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/docs/traceability-matrix.md
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/package.json
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/profiles/index.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/profiles/source-reference-map.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/runtime/rtcp-frame.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/state/store.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs
+/home/meswork/cnc_wams/work/working4/03-progress-ledger.md
+/home/meswork/cnc_wams/work/working4/04-task-matrix.md
+/home/meswork/cnc_wams/work/working4/05-acceptance-evidence.md
+```
+
+验证了什么:
+
+```bash
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs
+node web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs
+node web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs
+npm --prefix web-rtcp-5axis-sim-plan/app run build
+npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh
+git diff --check -- web-rtcp-5axis-sim-plan work/working4
+```
+
+结果:
+
+- 新增 Node 测试全部通过。
+- `npm run build` 通过。
+- `npm run smoke:node` 通过。
+- browser shell smoke 和 dist browser smoke 均通过。
+- `git diff --check` 通过。
+
+遗留问题:
+
+- 尚未建立 icon registry,也未将图标接入实际按钮 DOM。
+- communication/HAL model 已有数据和测试,但尚未接入信息面板页面。
+- 尚未新增 gmoccapy_XYZAB gate 独立测试。
+- 尚未生成 QA JSON/PDF/截图报告。
+
+下一步:
+
+继续 `W4-U01`,建立 `app/src/ui/gmoccapy-icon-registry.js`,并将 manifest 资产接入右侧栏、底部栏、Auto 栏、spindle/coolant 和 preview controls。
+
+### W4-IMPL-002
+
+日期:2026-06-26
+
+目标:
+
+继续 `working4` 的 UI/icon、gmoccapy_XYZAB gate、browser smoke 和 QA 报告任务。
+
+做了什么:
+
+- 新增 `gmoccapy-icon-registry.js`,为核心 gmoccapy 按钮提供 icon name、asset path、variant 和 fallback。
+- 将右侧状态栏、底部控制栏、preview toolbar、spindle/coolant 控件接入 gmoccapy 图标,并保留 `aria-label`、`data-icon-name`、blocked reason。
+- 将 gmoccapy communication model 和 HAL/postgui model 接入信息面板,显示 NML command/status、Web runtime path、postgui 顺序和 HAL pins/nets。
+- 扩展 `linuxcnc-task-policy.js`,为 `gmoccapy-xyzab` RUN gate 增加 INI、machine-file、opened program、interp idle、task/HAL runtime ready 检查;补齐 MDI、JOG/HOME、spindle/coolant/override gate。
+- 在 store 中新增 spindle direction 行为,并对 coolant/spindle/override 操作应用 gate。
+- 新增 icon registry 和 gmoccapy_XYZAB gate Node 测试。
+- 更新 browser smoke,断言核心按钮 icon 和 gmoccapy NML/HAL diagnostics。
+- 新增 QA capture 脚本,生成 gmoccapy_XYZAB JSON/PDF 报告和 6 张截图。
+- 更新 `04-task-matrix.md` 和 `05-acceptance-evidence.md`。
+
+改了哪些文件:
+
+```text
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/package.json
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-icon-registry.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/state/store.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_registry.mjs
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs
+/home/meswork/cnc_wams/work/working4/03-progress-ledger.md
+/home/meswork/cnc_wams/work/working4/04-task-matrix.md
+/home/meswork/cnc_wams/work/working4/05-acceptance-evidence.md
+```
+
+验证了什么:
+
+```bash
+node --check web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
+node --check web-rtcp-5axis-sim-plan/app/src/state/store.js
+node --check web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js
+node --check qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_registry.mjs
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs
+node web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs
+node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
+npm --prefix web-rtcp-5axis-sim-plan/app run build
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
+node qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs
+```
+
+结果:
+
+- 新增 Node 测试全部通过。
+- `verify_rtcp_store` 和 `verify_run_preconditions` 回归通过。
+- 静态 build 通过。
+- browser shell smoke 通过。
+- QA capture 返回 `gmoccapy_xyzab_function_status=PASS`,最新 job_id 为 `xyzab-20260626190724-7b80b3aa`,生成 JSON、PDF 和 6 张截图。
+
+遗留问题:
+
+- `gmoccapy-xyzab` 仍为 `trivkins` reference-only profile,不提供 TCP/RTCP proof。
+- 本轮 QA 证明 profile、图标、diagnostics 和 blocked gate,不证明 native `linuxcncsvr/milltask/HAL` 实机运行。
+
+下一步:
+
+完整 `npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node`、build、browser smoke、dist browser smoke、QA capture 和 `git diff --check` 已作为本轮最终回归执行。
+
+### W4-DOC-002
+
+日期:2026-06-26
+
+目标:
+
+继续收口 `working4`,消除状态文档中仍停留在初始规划阶段的过期描述。
+
+做了什么:
+
+- 更新 `README.md`,明确 `working4` 当前是已实施、已验证、已归档状态,不再描述为仅生成规划文档。
+- 更新 `01-function-content.md`,将 F-XYZAB-01 到 F-XYZAB-07 的状态对齐到当前完成情况,并保留 `gmoccapy-xyzab` reference-only 边界。
+- 更新 `02-program-development-steps.md`,标明阶段 A 到 G 已收口,QA capture JSON/PDF 已生成。
+- 更新 `02-program-development-steps.md` 的阶段文件清单措辞,从规划口径调整为已归档的主要新增/更新文件。
+- 更新 `04-task-matrix.md`,新增 W4-D08 状态文档收口任务并标记 `done`。
+- 更新 `05-acceptance-evidence.md`,修正“只创建文档、没有 job_id/report_id”的旧说明,并补充 W4-U01、W4-V04、W4-V05 证据行。
+
+改了哪些文件:
+
+```text
+/home/meswork/cnc_wams/work/working4/README.md
+/home/meswork/cnc_wams/work/working4/01-function-content.md
+/home/meswork/cnc_wams/work/working4/02-program-development-steps.md
+/home/meswork/cnc_wams/work/working4/03-progress-ledger.md
+/home/meswork/cnc_wams/work/working4/04-task-matrix.md
+/home/meswork/cnc_wams/work/working4/05-acceptance-evidence.md
+```
+
+验证了什么:
+
+```bash
+rg -n "\\| W4-[A-Z0-9-]+ \\| (todo|in_progress|blocked|deferred) \\|" work/working4/04-task-matrix.md
+node -e "const r=JSON.parse(require('fs').readFileSync('qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json','utf8')); if(r.status!=='PASS') process.exit(1); console.log(r.status, r.steps.length, r.checks.length)"
+git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4
+```
+
+结果:
+
+- `04-task-matrix.md` 未发现 `todo`、`in_progress`、`blocked` 或 `deferred` 的任务行。
+- 过期状态扫描未发现当前 README、功能内容、开发步骤或验收证据中的待实施/待验证旧口径;命中项仅为状态词定义、README 的“当前没有待执行任务”说明,以及早期台账的历史遗留记录。
+- QA JSON 保持 `PASS`,包含 6 个步骤和 10 个检查。
+- `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4` 返回 0。
+
+遗留问题:
+
+- `gmoccapy-xyzab` 仍为 `trivkins` reference-only profile,不提供 TCP/RTCP proof。
+- 本阶段不证明 native `linuxcncsvr/milltask/HAL` 实机运行。
+
+下一步:
+
+- `working4` 当前无剩余任务;后续新增工作需先在 `04-task-matrix.md` 新增任务编号。
+
+### W4-DOC-003
+
+日期:2026-06-26
+
+目标:
+
+继续完成 `working4` 收口,清理剩余会让读者误判为规划阶段的措辞。
+
+做了什么:
+
+- 更新 `README.md` 文档清单说明,将“要完善/应改”口径改为“已完善/已改”口径。
+- 更新 `02-program-development-steps.md`,将各阶段“建议新增或更新文件”统一改为“主要新增或更新文件”。
+- 更新 `03-progress-ledger.md` 说明,明确早期条目的“遗留问题/下一步”是历史记录,当前状态以矩阵和验收证据为准。
+- 更新 `04-task-matrix.md`,将 W4-D01 验收标准改为后续维护入口,将 W4-T07 验收标准改为已输出 JSON、PDF 和截图。
+
+改了哪些文件:
+
+```text
+/home/meswork/cnc_wams/work/working4/README.md
+/home/meswork/cnc_wams/work/working4/02-program-development-steps.md
+/home/meswork/cnc_wams/work/working4/03-progress-ledger.md
+/home/meswork/cnc_wams/work/working4/04-task-matrix.md
+```
+
+验证了什么:
+
+```bash
+rg -n "\\| W4-[A-Z0-9-]+ \\| (todo|in_progress|blocked|deferred) \\|" work/working4/04-task-matrix.md
+rg -n "待实施|待验证|待生成|PDF 任务保持 todo|本轮只创建文档|没有生成新的 job_id|后续新增:|建议新增|建议新增或更新|必要时输出 PDF|后续执行入口|拟完善功能包|TODO" work/working4/README.md work/working4/01-function-content.md work/working4/02-program-development-steps.md work/working4/04-task-matrix.md work/working4/05-acceptance-evidence.md
+node -e "const r=JSON.parse(require('fs').readFileSync('qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json','utf8')); if(r.status!=='PASS') process.exit(1); console.log('qa_report_status='+r.status); console.log('qa_report_steps='+r.steps.length); console.log('qa_report_checks='+r.checks.length);"
+git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4
+```
+
+结果:
+
+- 未发现未完成 W4 任务行。
+- 当前状态文档未发现规划阶段残留关键词。
+- QA JSON 保持 `PASS`,包含 6 个步骤和 10 个检查。
+- `git diff --check` 返回 0。
+
+遗留问题:
+
+- 无新的 `working4` 剩余任务。
+- 保留既定边界:`gmoccapy-xyzab` 是 `trivkins` reference-only profile,不证明 TCP/RTCP 或 native LinuxCNC realtime 执行。
+
+下一步:
+
+- 当前无需继续执行;后续新增需求先在 `04-task-matrix.md` 新增任务编号。
+
+### W4-DOC-004
+
+日期:2026-06-26
+
+目标:
+
+继续复核 `working4` 验收证据,确认 QA report 编号字段与归档表一致。
+
+做了什么:
+
+- 重新扫描 `04-task-matrix.md`,确认没有未完成 W4 任务行。
+- 重新扫描当前状态文档,确认没有规划阶段残留关键词。
+- 读取 `gmoccapy-xyzab-function-report.json`,确认报告字段为 `jobId` 和 `reportId`。
+- 读取 QA capture 脚本,确认控制台输出为 `gmoccapy_xyzab_function_job_id` 和 `gmoccapy_xyzab_function_report_id`,来源是同一组 report 字段。
+- 更新 `05-acceptance-evidence.md`,说明 JSON camelCase 字段与归档表 `job_id/report_id` 标签的对应关系。
+
+改了哪些文件:
+
+```text
+/home/meswork/cnc_wams/work/working4/03-progress-ledger.md
+/home/meswork/cnc_wams/work/working4/05-acceptance-evidence.md
+```
+
+验证了什么:
+
+```bash
+rg -n "\\| W4-[A-Z0-9-]+ \\| (todo|in_progress|blocked|deferred) \\|" work/working4/04-task-matrix.md
+rg -n "待实施|待验证|待生成|PDF 任务保持 todo|本轮只创建文档|没有生成新的 job_id|后续新增:|建议新增|建议新增或更新|必要时输出 PDF|后续执行入口|拟完善功能包|TODO" work/working4/README.md work/working4/01-function-content.md work/working4/02-program-development-steps.md work/working4/04-task-matrix.md work/working4/05-acceptance-evidence.md
+node -e "const r=JSON.parse(require('fs').readFileSync('qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json','utf8')); if(r.status!=='PASS') process.exit(1); console.log(r.jobId, r.reportId, r.status, r.steps.length, r.checks.length)"
+git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4
+```
+
+结果:
+
+- 未发现未完成 W4 任务行。
+- 当前状态文档未发现规划阶段残留关键词。
+- QA JSON 保持 `PASS`,包含 `jobId=xyzab-20260626190724-7b80b3aa`、`reportId=report-xyzab-20260626190724-7b80b3aa`、6 个步骤和 10 个检查。
+- `git diff --check` 返回 0。
+
+遗留问题:
+
+- 无新的 `working4` 剩余任务。
+- 保留既定边界:`gmoccapy-xyzab` 是 `trivkins` reference-only profile,不证明 TCP/RTCP 或 native LinuxCNC realtime 执行。
+
+下一步:
+
+- 当前无需继续执行;后续新增需求先在 `04-task-matrix.md` 新增任务编号。
+
+### W4-DOC-005
+
+日期:2026-06-26
+
+目标:
+
+建立 `working4` 一眼可读的最终收口入口,避免完成状态分散在 README、任务矩阵、验收证据和台账中。
+
+做了什么:
+
+- 新增 `07-final-closure.md`,汇总收口结论、最终证据、QA identifiers、最终校验命令、保留边界和后续变更规则。
+- 更新 `README.md`,将 `07-final-closure.md` 标为最终收口入口,并加入文档清单和后续维护步骤。
+- 更新 `04-task-matrix.md`,新增 W4-D09 并标记 `done`。
+
+改了哪些文件:
+
+```text
+/home/meswork/cnc_wams/work/working4/07-final-closure.md
+/home/meswork/cnc_wams/work/working4/README.md
+/home/meswork/cnc_wams/work/working4/03-progress-ledger.md
+/home/meswork/cnc_wams/work/working4/04-task-matrix.md
+```
+
+验证了什么:
+
+```bash
+rg -n "\\| W4-[A-Z0-9-]+ \\| (todo|in_progress|blocked|deferred) \\|" work/working4/04-task-matrix.md
+# stale wording scan command is recorded in 07-final-closure.md
+node -e "const r=JSON.parse(require('fs').readFileSync('qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json','utf8')); if(r.status!=='PASS') process.exit(1); console.log(r.jobId, r.reportId, r.status, r.steps.length, r.checks.length)"
+git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4
+```
+
+结果:
+
+- 未发现未完成 W4 任务行。
+- 当前状态文档未发现规划阶段残留关键词;`07-final-closure.md` 中的示例扫描命令已使用 escaped pattern 和拆分 token,避免自命中。
+- QA JSON 保持 `PASS`,包含 `jobId=xyzab-20260626190724-7b80b3aa`、`reportId=report-xyzab-20260626190724-7b80b3aa`、6 个步骤和 10 个检查。
+- `git diff --check` 返回 0。
+
+遗留问题:
+
+- 无新的 `working4` 剩余任务。
+- 保留既定边界:`gmoccapy-xyzab` 是 `trivkins` reference-only profile,不证明 TCP/RTCP 或 native LinuxCNC realtime 执行。
+
+下一步:
+
+- `working4` 已有最终收口入口;后续新增需求先在 `04-task-matrix.md` 新增任务编号,并同步更新 `07-final-closure.md`。
+
+### W4-VERIFY-006
+
+日期:2026-06-26
+
+目标:
+
+接续上一轮中断后的 `working4` 收口状态,重新运行关键验收链路,确认当前代码、报告和最终证据仍一致。
+
+做了什么:
+
+- 读取 `AGENTS.md` 和 `working4` README、任务矩阵、推进台账、最终收口说明,确认当前范围和日志规则。
+- 扫描 `04-task-matrix.md`,确认没有未完成 W4 任务行。
+- 扫描当前状态文档,确认没有规划阶段残留关键词。
+- 重新运行 build、Node smoke、gmoccapy shell browser smoke、dist browser smoke 和 QA capture。
+- QA capture 重新生成 `gmoccapy-xyzab-function-report.json`、`.pdf` 和截图目录内容。
+- 更新 `05-acceptance-evidence.md` 和 `07-final-closure.md`,将当前最终证据编号对齐到最新 QA report。
+
+改了哪些文件:
+
+```text
+/home/meswork/cnc_wams/work/working4/03-progress-ledger.md
+/home/meswork/cnc_wams/work/working4/05-acceptance-evidence.md
+/home/meswork/cnc_wams/work/working4/07-final-closure.md
+```
+
+验证了什么:
+
+```bash
+rg -n "\\| W4-[A-Z0-9-]+ \\| (todo|in_progress|blocked|deferred) \\|" work/working4/04-task-matrix.md
+TD_WORD="$(printf '%s%s' TO DO)"; STALE_W4_PATTERN="$(printf '\\u5f85\\u5b9e\\u65bd|\\u5f85\\u9a8c\\u8bc1|\\u5f85\\u751f\\u6210|PDF \\u4efb\\u52a1\\u4fdd\\u6301 todo|\\u672c\\u8f6e\\u53ea\\u521b\\u5efa\\u6587\\u6863|\\u6ca1\\u6709\\u751f\\u6210\\u65b0\\u7684 job_id|\\u540e\\u7eed\\u65b0\\u589e\\uff1a|\\u5efa\\u8bae\\u65b0\\u589e|\\u5efa\\u8bae\\u65b0\\u589e\\u6216\\u66f4\\u65b0|\\u5fc5\\u8981\\u65f6\\u8f93\\u51fa PDF|\\u540e\\u7eed\\u6267\\u884c\\u5165\\u53e3|\\u62df\\u5b8c\\u5584\\u529f\\u80fd\\u5305|%s' "$TD_WORD")"; rg -n "$STALE_W4_PATTERN" work/working4/README.md work/working4/01-function-content.md work/working4/02-program-development-steps.md work/working4/04-task-matrix.md work/working4/05-acceptance-evidence.md work/working4/07-final-closure.md
+node -e "const r=JSON.parse(require('fs').readFileSync('qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json','utf8')); if(r.status!=='PASS') process.exit(1); console.log(r.jobId, r.reportId, r.status, r.steps.length, r.checks.length)"
+npm --prefix web-rtcp-5axis-sim-plan/app run build
+npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh
+node qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs
+git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4
+```
+
+结果:
+
+- 未发现未完成 W4 任务行。
+- 当前状态文档未发现规划阶段残留关键词。
+- `npm --prefix web-rtcp-5axis-sim-plan/app run build` 返回 0,输出 `gmoccapy_static_build=ok`。
+- `npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node` 返回 0,覆盖既有 smoke 和新增 gmoccapy_XYZAB profile/icon/communication/HAL/gate smoke。
+- gmoccapy shell browser smoke 返回 `gmoccapy_shell_smoke=ok`。
+- dist browser smoke 返回 `gmoccapy_dist_smoke=ok`。
+- QA capture 返回 `gmoccapy_xyzab_function_status=PASS`,最新 `job_id=xyzab-20260626214346-f4b4aa36`,`report_id=report-xyzab-20260626214346-f4b4aa36`,生成 JSON、PDF 和 6 张截图。
+- `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4` 返回 0。
+
+遗留问题:
+
+- 无新的 `working4` 剩余任务。
+- 保留既定边界:`gmoccapy-xyzab` 是 `trivkins` reference-only profile,不证明 TCP/RTCP 或 native LinuxCNC realtime 执行。
+
+下一步:
+
+- 当前无需继续执行;后续新增需求先在 `04-task-matrix.md` 新增任务编号,并同步更新 `05-acceptance-evidence.md` 和 `07-final-closure.md`。
+
+## 3. 后续追加模板
+
+### W4-XXX
+
+日期:
+
+目标:
+
+做了什么:
+
+改了哪些文件:
+
+```text
+
+```
+
+验证了什么:
+
+```bash
+
+```
+
+结果:
+
+遗留问题:
+
+下一步:
diff --git a/work/working4/04-task-matrix.md b/work/working4/04-task-matrix.md
new file mode 100644
index 0000000..8f8ac79
--- /dev/null
+++ b/work/working4/04-task-matrix.md
@@ -0,0 +1,86 @@
+# 04 任务矩阵
+
+生成日期:2026-06-26
+
+状态取值:
+
+- `done`:已完成并有证据。
+- `in_progress`:正在做。
+- `todo`:未开始。
+- `blocked`:被外部条件阻塞。
+- `deferred`:暂缓,不影响当前阶段验收。
+
+## 1. 文档任务
+
+| 任务编号 | 状态 | 任务 | 产物 | 验收标准 |
+| --- | --- | --- | --- | --- |
+| W4-D01 | done | 建立 working4 文档索引 | `README.md` | 列出 01-07 文档和后续维护入口。 |
+| W4-D02 | done | 编写功能内容文档 | `01-function-content.md` | 覆盖目标、现状、working3 新价值、功能包、不做事项、成功定义。 |
+| W4-D03 | done | 编写开发步骤文档 | `02-program-development-steps.md` | 按阶段列出应改文件、步骤、命令、验收标准。 |
+| W4-D04 | done | 建立推进台账 | `03-progress-ledger.md` | 包含 W4-DOC-001 和后续追加模板。 |
+| W4-D05 | done | 建立任务矩阵 | `04-task-matrix.md` | 所有后续任务有编号、状态、验收标准。 |
+| W4-D06 | done | 建立验收证据文档 | `05-acceptance-evidence.md` | 记录现有证据、实际 QA 证据和验收边界。 |
+| W4-D07 | done | 建立决策记录 | `06-decision-records.md` | 记录边界、技术路线、验收策略。 |
+| W4-D08 | done | 收口 working4 状态文档 | `README.md`、`01-function-content.md`、`02-program-development-steps.md`、`05-acceptance-evidence.md` | 规划口径与已完成实施、QA 证据和验收边界一致。 |
+| W4-D09 | done | 建立最终收口说明 | `07-final-closure.md` | 汇总完成结论、最终证据、保留边界和后续变更规则。 |
+
+## 2. 资料和资产任务
+
+| 任务编号 | 状态 | 任务 | 主要文件 | 验收标准 |
+| --- | --- | --- | --- | --- |
+| W4-A01 | done | 建立 gmoccapy_XYZAB reference 文档 | `web-rtcp-5axis-sim-plan/docs/gmoccapy-xyzab-reference.md` | 文档覆盖 native 启动、NML、HAL、postgui、互锁、按钮控制逻辑。 |
+| W4-A02 | done | 生成按钮图标 JSON manifest | `app/src/ui-reference/gmoccapy-button-icons.json` | JSON 可解析,记录 211 行清单并保留来源路径。 |
+| W4-A03 | done | 复制核心 gmoccapy 图标到 app assets | `app/src/assets/gmoccapy-icons/` | 已复制 112 个图标,覆盖主状态栏、底部栏、Auto 栏、spindle/coolant、preview。 |
+| W4-A04 | done | 更新追溯矩阵 | `docs/traceability-matrix.md` | 新增 gmoccapy_XYZAB、button icon manifest、communication model、HAL diagnostics 追溯项。 |
+
+## 3. Profile 和 runtime model 任务
+
+| 任务编号 | 状态 | 任务 | 主要文件 | 验收标准 |
+| --- | --- | --- | --- | --- |
+| W4-P01 | done | 新增 `gmoccapy-xyzab` profile | `app/src/profiles/gmoccapy-xyzab.js`、`app/src/profiles/index.js` | profile 可选择,坐标为 XYZAB,标记 `trivkins`、`tcpCapable=false` 和 `rtcpProof=false`。 |
+| W4-P02 | done | 新增 XYZAB source reference map | `app/src/profiles/source-reference-map.js` | 包含 `gmoccapy_XYZAB.ini`、`core_sim_XYZAB.hal`、`spindle_sim.hal`、`simulated_home.hal`、`gmoccapy_postgui.hal`。 |
+| W4-P03 | done | 新增 gmoccapy communication model | `app/src/runtime/gmoccapy-communication-model.js` | 能列出 native NML command/status/error、HAL pin、Web runtime 映射。 |
+| W4-P04 | done | 新增 gmoccapy HAL model | `app/src/runtime/gmoccapy-hal-model.js` | 能列出关键 `gmoccapy.*` pins 和 postgui nets。 |
+
+## 4. UI 和按钮任务
+
+| 任务编号 | 状态 | 任务 | 主要文件 | 验收标准 |
+| --- | --- | --- | --- | --- |
+| W4-U01 | done | 建立 icon registry | `app/src/ui/gmoccapy-icon-registry.js` | `getGmoccapyIcon(buttonId, variant)` 可返回 icon path/name/fallback。 |
+| W4-U02 | done | 右侧主状态栏接入 icon | `app/src/ui/gmoccapy-shell.js`、`app/src/styles/gmoccapy.css` | estop/power/manual/mdi/auto/settings/user tabs 有 active/inactive 图标。 |
+| W4-U03 | done | 底部和 Auto 控制栏接入 icon | `gmoccapy-shell.js`、`gmoccapy.css` | open/reload/run/stop/pause/step/home/tool/touch/fullscreen 有图标和 aria-label。 |
+| W4-U04 | done | spindle/coolant/preview 接入 icon | `gmoccapy-shell.js`、`gmoccapy.css` | spindle forward/reverse/stop、flood/mist、view/zoom/toolpath/dimensions 有图标或 fallback。 |
+| W4-U05 | done | 信息面板展示 communication/HAL diagnostics | `gmoccapy-shell.js` | UI 可见 NML/HAL/Web runtime 边界和 postgui 顺序。 |
+
+## 5. Gate 和业务语义任务
+
+| 任务编号 | 状态 | 任务 | 主要文件 | 验收标准 |
+| --- | --- | --- | --- | --- |
+| W4-G01 | done | RUN gate 对齐 gmoccapy_XYZAB | `app/src/state/linuxcnc-task-policy.js`、`store.js` | 覆盖 INI、machine-file、estop、power、home、auto、interp idle、runtime ready。 |
+| W4-G02 | done | MDI gate 对齐 gmoccapy_XYZAB | `linuxcnc-task-policy.js`、`store.js` | 未上电、未回零、运行中等状态有明确 blocked reason。 |
+| W4-G03 | done | JOG/HOME gate 对齐 gmoccapy_XYZAB | `linuxcnc-task-policy.js`、`store.js` | manual/on/not-running 状态规则可测。 |
+| W4-G04 | done | spindle/coolant/override gate 对齐 gmoccapy_XYZAB | `linuxcnc-task-policy.js`、`gmoccapy-shell.js` | 急停/下电禁用,运行中限制手动主轴方向覆盖。 |
+| W4-G05 | done | Stop/Abort 清理 UI 状态 | `store.js`、`gmoccapy-shell.js` | Stop 后 pause/start_line/active run state 清理,browser 可测。 |
+
+## 6. 测试和证据任务
+
+| 任务编号 | 状态 | 任务 | 主要文件 | 验收标准 |
+| --- | --- | --- | --- | --- |
+| W4-T01 | done | 新增 XYZAB profile Node 测试 | `tests/node/verify_gmoccapy_xyzab_profile.mjs` | 断言坐标、HALFILE、POSTGUI、trivkins、boundary。 |
+| W4-T02 | done | 新增 icon manifest Node 测试 | `tests/node/verify_gmoccapy_icon_manifest.mjs` | manifest 可解析,核心按钮均有 icon 或 fallback。 |
+| W4-T03 | done | 新增 communication model Node 测试 | `tests/node/verify_gmoccapy_communication_model.mjs` | NML/HAL/Web 映射完整。 |
+| W4-T04 | done | 新增 HAL model Node 测试 | `tests/node/verify_gmoccapy_hal_model.mjs` | 关键 pins 和 postgui nets 存在。 |
+| W4-T05 | done | 新增 gate Node 测试 | `tests/node/verify_gmoccapy_xyzab_gates.mjs` | RUN/MDI/JOG/HOME/SPINDLE/COOLANT 合法和非法状态均覆盖。 |
+| W4-T06 | done | 更新 browser smoke | `tests/browser/gmoccapy_shell_smoke.html` | 覆盖 profile 切换、icon active、blocked reason、HAL diagnostics。 |
+| W4-T07 | done | 新增 QA 截图和报告脚本 | `qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs` | 输出 JSON、PDF 和截图。 |
+| W4-T08 | done | 更新验收证据文档 | `work/working4/05-acceptance-evidence.md` | 追加实际命令、JSON、PDF、截图和资产路径。 |
+
+## 7. 全局验收任务
+
+| 任务编号 | 状态 | 任务 | 命令 | 验收标准 |
+| --- | --- | --- | --- | --- |
+| W4-V01 | done | 静态构建 | `npm --prefix web-rtcp-5axis-sim-plan/app run build` | 返回 0。 |
+| W4-V02 | done | Node smoke | `npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node` | 返回 0,原有测试不回归。 |
+| W4-V03 | done | Browser smoke | `bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh` | 返回 0,页面非空,canvas 非空。 |
+| W4-V04 | done | Dist browser smoke | `bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh` | 返回 0。 |
+| W4-V05 | done | 文档和空白检查 | `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4` | 返回 0。 |
diff --git a/work/working4/05-acceptance-evidence.md b/work/working4/05-acceptance-evidence.md
new file mode 100644
index 0000000..1b30e6c
--- /dev/null
+++ b/work/working4/05-acceptance-evidence.md
@@ -0,0 +1,306 @@
+# 05 验收证据
+
+生成日期:2026-06-26
+
+## 1. 证据规则
+
+每个功能完成时必须至少提供一种证据:
+
+- 命令输出:Node、npm build、browser smoke、QA capture。
+- 页面证据:本地 URL、云端 URL、页面状态说明。
+- JSON:机器可读 PASS/FAIL 报告。
+- 截图:关键页面状态。
+- PDF/DOCX/Markdown 报告:适合人工归档的报告。
+- job_id/report_id:如果由 CI、云端任务或报告生成器产生,必须记录。
+
+本目录先由文档规划创建;后续 W4-IMPL-001、W4-IMPL-002 和收口检查已补齐实现、QA 证据和最新
+job_id/report_id。
+
+注:QA JSON 内部字段使用 camelCase:`jobId`、`reportId`;控制台输出和本归档表使用
+`job_id`、`report_id` 标签,两者指向同一组编号。
+
+## 2. 本轮文档证据
+
+本轮输入资料确认:
+
+```bash
+find /home/meswork/cnc_wams/work/working3 -maxdepth 3 -type f | sort
+find /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan -maxdepth 3 -type f | sort
+find /home/meswork/cnc_wams/work/working4 -maxdepth 2 -type f -print | sort
+```
+
+本轮读取的核心资料:
+
+```text
+/home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md
+/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/README.md
+/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.md
+/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.csv
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/README.md
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/docs/technical-roadmap.md
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/docs/traceability-matrix.md
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/main.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/state/store.js
+```
+
+本轮输出文件:
+
+```text
+/home/meswork/cnc_wams/work/working4/README.md
+/home/meswork/cnc_wams/work/working4/01-function-content.md
+/home/meswork/cnc_wams/work/working4/02-program-development-steps.md
+/home/meswork/cnc_wams/work/working4/03-progress-ledger.md
+/home/meswork/cnc_wams/work/working4/04-task-matrix.md
+/home/meswork/cnc_wams/work/working4/05-acceptance-evidence.md
+/home/meswork/cnc_wams/work/working4/06-decision-records.md
+/home/meswork/cnc_wams/work/working4/07-final-closure.md
+```
+
+## 3. 可复用的现有项目证据
+
+### 3.1 Node 和 build 命令
+
+项目已有命令:
+
+```bash
+npm --prefix web-rtcp-5axis-sim-plan/app run build
+npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh
+```
+
+`smoke:node` 当前覆盖:
+
+```text
+verify_linuxcnc_kinematics_runtime.mjs
+verify_linuxcnc_interpreter_runtime.mjs
+verify_linuxcnc_ini_runtime.mjs
+verify_run_preconditions.mjs
+verify_run_feedback_loop.mjs
+verify_linuxcnc_task_hal_runtime.mjs
+verify_native_task_hal_audit.mjs
+verify_full_linuxcnc_5axis_source.mjs
+verify_real_linuxcnc_5axis_program_cases.mjs
+verify_full_execution_boundary.mjs
+verify_machine_file_staging.mjs
+verify_five_axis_session.mjs
+verify_rtcp_store.mjs
+verify_profile_boundary.mjs
+verify_linear_unit_conversion.mjs
+```
+
+### 3.2 项目内已有 JSON/截图/PDF/DOCX
+
+可复用证据路径:
+
+```text
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/build/readiness/native-task-hal-readiness.json
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/working_run/run-feedback-evidence/run-feedback-local.json
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/working_run/run-feedback-evidence/run-feedback-local.png
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/toolpath-preview-cases.json
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/run-preconditions-feedback.json
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/meter-scene-evidence.json
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/native-task-hal-comparison-report.json
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/native-task-hal-comparison-report.md
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/button-control-evidence-report.json
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/button-control-evidence-report.pdf
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-button-control-evidence-report.json
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-button-control-evidence-report.pdf
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report.json
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report-2026-06-22.docx
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/test-linuxcnc-source-run-report-2026-06-23.docx
+```
+
+云端 RUN 截图目录:
+
+```text
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-run-after-deploy/
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-run-stop-after-deploy/
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-run-debug/
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/cloud-run-debug-current/
+```
+
+## 4. working4 已生成的证据
+
+| 任务 | 证据类型 | 目标路径 | 状态 |
+| --- | --- | --- | --- |
+| W4-A02 icon manifest | JSON | `web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json` | 已生成,211 行清单 |
+| W4-P01 XYZAB profile | Node output | `tests/node/verify_gmoccapy_xyzab_profile.mjs` 控制台输出 | 已通过 |
+| W4-P03 communication model | Node output | `tests/node/verify_gmoccapy_communication_model.mjs` 控制台输出 | 已通过 |
+| W4-P04 HAL model | Node output | `tests/node/verify_gmoccapy_hal_model.mjs` 控制台输出 | 已通过 |
+| W4-U01 icon registry | Node output | `tests/node/verify_gmoccapy_icon_registry.mjs` 控制台输出 | 已通过 |
+| W4-G01 到 W4-G05 gate | Node output | `tests/node/verify_gmoccapy_xyzab_gates.mjs` 控制台输出 | 已通过 |
+| W4-U02 到 W4-U05 UI | Browser screenshot | `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/*.png` | 已生成 6 张截图 |
+| W4-T07 QA report | JSON/PDF | `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`、`.pdf` | 已生成,PASS |
+| W4-V01 build | 命令输出 | `npm --prefix ... run build` | 已通过 |
+| W4-V02 node smoke | 命令输出 | `npm --prefix ... run smoke:node` | 已通过 |
+| W4-V03 browser smoke | 命令输出和截图 | `verify_gmoccapy_shell_browser.sh` | 已通过 |
+| W4-V04 dist browser smoke | 命令输出 | `verify_gmoccapy_dist_browser.sh` | 已通过 |
+| W4-V05 whitespace check | 命令输出 | `git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4` | 已通过 |
+
+## 5. W4-IMPL-001 实施证据
+
+日期:2026-06-26
+
+本轮新增和更新的关键产物:
+
+```text
+web-rtcp-5axis-sim-plan/docs/gmoccapy-xyzab-reference.md
+web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json
+web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons/
+web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js
+web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js
+web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js
+web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs
+web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs
+web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs
+web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs
+```
+
+资产证据:
+
+```bash
+find web-rtcp-5axis-sim-plan/app/src/assets/gmoccapy-icons -maxdepth 1 -type f | wc -l
+# 112
+
+node -e "const m=JSON.parse(require('fs').readFileSync('web-rtcp-5axis-sim-plan/app/src/ui-reference/gmoccapy-button-icons.json','utf8')); console.log(m.rowCount,m.copiedIconCount)"
+# 211 112
+```
+
+新增 Node 测试证据:
+
+```bash
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs
+# gmoccapy_xyzab_profile_smoke=ok
+
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_manifest.mjs
+# gmoccapy_icon_manifest_smoke=ok
+
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs
+# gmoccapy_communication_model_smoke=ok
+
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs
+# gmoccapy_hal_model_smoke=ok
+```
+
+全局验收命令:
+
+```bash
+npm --prefix web-rtcp-5axis-sim-plan/app run build
+# gmoccapy_static_build=ok
+
+npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
+# linear_unit_conversion_smoke=ok
+# 全链路返回 0,并包含 gmoccapy_xyzab_profile/icon_manifest/communication_model/hal_model smoke。
+
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
+# gmoccapy_shell_smoke=ok
+
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh
+# gmoccapy_dist_smoke=ok
+
+git diff --check -- web-rtcp-5axis-sim-plan work/working4
+# 返回 0
+```
+
+## 6. job_id 和 report_id 台账
+
+### 6.1 W4-IMPL-002 UI/gate/QA 证据
+
+日期:2026-06-26
+
+本轮新增和更新的关键产物:
+
+```text
+web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-icon-registry.js
+web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js
+web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css
+web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js
+web-rtcp-5axis-sim-plan/app/src/state/store.js
+web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_registry.mjs
+web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs
+web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html
+qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs
+```
+
+新增 Node 测试证据:
+
+```bash
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_icon_registry.mjs
+# gmoccapy_icon_registry_smoke=ok
+
+node web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs
+# gmoccapy_xyzab_gates_smoke=ok
+
+node web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs
+# rtcp_store_smoke=ok
+
+node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
+# run_preconditions_ini_profile_smoke=ok
+# run_preconditions_kinematics_smoke=ok
+# run_preconditions_machine_file_smoke=ok
+```
+
+Build/browser/QA 证据:
+
+```bash
+npm --prefix web-rtcp-5axis-sim-plan/app run build
+# gmoccapy_static_build=ok
+
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
+# gmoccapy_shell_smoke=ok
+
+node qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs
+# gmoccapy_xyzab_function_status=PASS
+# gmoccapy_xyzab_function_job_id=xyzab-20260626214346-f4b4aa36
+# gmoccapy_xyzab_function_report_id=report-xyzab-20260626214346-f4b4aa36
+```
+
+QA 报告路径:
+
+```text
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.pdf
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/01-loaded.png
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/02-profile-selected.png
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/03-run-blocked-power.png
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/04-run-blocked-reference-evidence.png
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/05-controls-active.png
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/06-hal-diagnostics.png
+```
+
+QA JSON 检查结果:
+
+```text
+PASS XYZAB profile selected
+PASS Reference profile blocks RTCP promotion
+PASS RUN blocked before power
+PASS RUN blocked by reference evidence
+PASS Core icons rendered
+PASS Spindle/coolant state updates
+PASS NML command diagnostic
+PASS Web runtime diagnostic
+PASS HAL postgui diagnostic
+PASS Screenshots are nonblank
+```
+
+### 6.2 job_id/report_id 表
+
+归档表:
+
+| 日期 | job_id | report_id | 来源 | 对应任务 | 结果 | 证据路径 |
+| --- | --- | --- | --- | --- | --- | --- |
+| 2026-06-26 | N/A | N/A | 本轮文档生成和状态收口 | W4-D01 到 W4-D09 | done | `/home/meswork/cnc_wams/work/working4/` |
+| 2026-06-26 | N/A | N/A | 本轮本地实施 | W4-A01 到 W4-A04、W4-P01 到 W4-P04、W4-T01 到 W4-T04、W4-V01 到 W4-V05 | done | `web-rtcp-5axis-sim-plan/app/src/`、`docs/`、`tests/node/` |
+| 2026-06-26 | xyzab-20260626190724-7b80b3aa | report-xyzab-20260626190724-7b80b3aa | 本轮 QA capture | W4-U01 到 W4-U05、W4-G01 到 W4-G05、W4-T05 到 W4-T07 | PASS | `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`、`.pdf`、`output/gmoccapy-xyzab/` |
+| 2026-06-26 | xyzab-20260626214346-f4b4aa36 | report-xyzab-20260626214346-f4b4aa36 | 继续复核 QA capture | W4-U01 到 W4-U05、W4-G01 到 W4-G05、W4-T05 到 W4-T07、W4-V01 到 W4-V05 | PASS | `qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json`、`.pdf`、`output/gmoccapy-xyzab/` |
+
+## 7. 验收缺口
+
+当前缺口:
+
+- `gmoccapy-xyzab` 仍是 `trivkins` reference-only profile,不是 RTCP/TCP proof;真实 RTCP promotion 仍只来自 source-derived `xyzac-trt`、`xyzbc-trt`。
+- 本轮 QA 对 `gmoccapy-xyzab` 证明 profile、图标、diagnostics 和 gate blocked reason,不证明 native `linuxcncsvr/milltask/HAL` 实机运行。
+
+本轮新增证据已补齐原先 UI icon、communication/HAL diagnostics、XYZAB gate 和 QA JSON/PDF 缺口。
diff --git a/work/working4/06-decision-records.md b/work/working4/06-decision-records.md
new file mode 100644
index 0000000..a6b8c5c
--- /dev/null
+++ b/work/working4/06-decision-records.md
@@ -0,0 +1,182 @@
+# 06 决策记录
+
+生成日期:2026-06-26
+
+## ADR-001:working3 作为参考输入,不直接作为 runtime 依赖
+
+状态:accepted
+
+决策:
+
+`working3` 中的 Markdown、CSV 和图标文件是功能完善的参考输入。后续实现时应把需要长期使用的资料复制或转换到 `web-rtcp-5axis-sim-plan` 内部目录,而不是让应用运行时依赖 `work/working3`。
+
+原因:
+
+- `work/working3` 是资料整理目录,不是产品运行目录。
+- Web 应用需要稳定的 assets、manifest 和 source map。
+- 这样可以在追溯矩阵中明确来源,同时避免后续清理 `work/` 时破坏应用。
+
+后续影响:
+
+- 图标必须落到 `app/src/assets/gmoccapy-icons/` 或等价目录。
+- CSV 必须转换为 JSON manifest 或构建期可解析资产。
+
+## ADR-002:不移植 native gmoccapy GTK/Glade runtime
+
+状态:accepted
+
+决策:
+
+Web 项目只参考 gmoccapy 的布局、按钮、互锁和通信模型,不把 `gmoccapy.py`、GTK、Glade、Python REMAP runtime 直接搬进浏览器。
+
+原因:
+
+- 项目目标是 Web 仿真产品,不是替代 LinuxCNC native GUI。
+- 浏览器不能等同 native NML buffer、HAL shared memory 和 Linux realtime thread。
+- 当前项目已有原生 HTML/CSS/ES module shell,继续复用成本最低。
+
+后续影响:
+
+- 文档和 UI 必须用 `semanticBoundary` 说明 Web 模拟边界。
+- Python GUI runtime 一直保持 `reference only`。
+
+## ADR-003:gmoccapy_XYZAB 是 trivkins 参考 profile,不是 RTCP proof
+
+状态:accepted
+
+决策:
+
+新增 `gmoccapy-xyzab` 时,必须标记为 `trivkins coordinates=xyzab` 的 gmoccapy 参考机型,不得标记为 RTCP/TCP 运动学 proof。
+
+原因:
+
+- `gmoccapy_XYZAB.ini` 的运动学是 `trivkins`。
+- RTCP/TCP 运动学 proof 当前来自 `xyzac-trt`、`xyzbc-trt` 等 source-derived kinematics runtime。
+- 把 XYZAB trivkins 误标为 RTCP 会污染验收标准。
+
+后续影响:
+
+- UI 可以显示 XYZAB 轴和 gmoccapy native 启动链路。
+- RTCP badge 对该 profile 应显示 `not TCP proof` 或等价说明,除非后续接入真实 TCP 运动学。
+
+## ADR-004:NML/HAL 通信以诊断模型展示,控制仍走 Web store/task policy/runtime
+
+状态:accepted
+
+决策:
+
+Web UI 显示 gmoccapy native 的 NML/HAL 通信路径,但按钮点击仍走现有 `store.dispatch`、`linuxcnc-task-policy`、runtime status snapshot。
+
+原因:
+
+- 当前浏览器应用没有 native NML buffer。
+- 现有 store/task policy/runtime 已覆盖 RUN、MDI、JOG、HOME、task/HAL status loop。
+- 诊断模型能帮助用户理解 native gmoccapy,但不会制造虚假的 native 连接。
+
+后续影响:
+
+- 每个按钮可显示 native equivalent command,例如 `STATE_ON`、`MODE_AUTO`、`AUTO_RUN`。
+- 证据中必须区分 native reference 和 Web execution。
+
+## ADR-005:按钮图标不能替代按钮语义验收
+
+状态:accepted
+
+决策:
+
+图标接入只算 UI 资产验收。按钮是否正确,必须通过 task policy、store、runtime 和 browser action 证据验收。
+
+原因:
+
+- `working3` 图标清单只说明图标来源、按钮名、tooltip 和信号线索。
+- 真正的执行安全依赖 machine state、mode、interp state、home state、runtime readiness。
+- 防止“看起来像 gmoccapy”但控制逻辑不正确。
+
+后续影响:
+
+- 每个核心按钮至少要有 icon 证据和 gate/action 证据。
+- browser smoke 应断言 active icon 和 disabled reason。
+
+## ADR-006:G-code 执行互锁按三层保护建模
+
+状态:accepted
+
+决策:
+
+RUN/MDI/JOG/HOME 等 gate 按 gmoccapy_XYZAB 文档中的三层保护建模:
+
+1. UI 按钮敏感状态。
+2. Action/task policy gate。
+3. runtime 或 LinuxCNC source-derived boundary 最终校验。
+
+原因:
+
+- gmoccapy native 本身不是只靠一个按钮判断。
+- LinuxCNC 后台会再次拒绝非法命令。
+- Web 侧需要给用户可读 blocked reason,同时保持 runtime 校验。
+
+后续影响:
+
+- 测试必须覆盖非法状态和合法状态。
+- Stop/Abort 的可用范围应大于 Run/MDI/Jog。
+
+## ADR-007:POSTGUI_HALFILE 必须作为顺序约束记录
+
+状态:accepted
+
+决策:
+
+HAL/postgui 诊断中必须记录:`gmoccapy_postgui.hal` 只能在 `gmoccapy.halcomp.ready()` 后执行。
+
+原因:
+
+- postgui 文件引用 `gmoccapy.*` pins,普通 HALFILE 阶段这些 pins 尚不存在。
+- 这是 gmoccapy native 启动顺序中的关键边界。
+- Web 侧如果展示 HAL model,也要保留这个顺序概念。
+
+后续影响:
+
+- HAL model 中 postgui nets 独立于普通 HALFILE。
+- 文档和 UI 不应把 postgui pin 连接混入实时 HAL 启动前阶段。
+
+## ADR-008:新增功能必须 evidence-first promotion
+
+状态:accepted
+
+决策:
+
+任何新增功能不能只凭代码合入认定完成,必须补齐 `05-acceptance-evidence.md` 中对应证据。
+
+原因:
+
+- 当前项目已经形成 Node smoke、browser smoke、QA JSON、截图、PDF/DOCX 的证据链。
+- gmoccapy_XYZAB 功能涉及 UI、profile、HAL、NML、gate、图标,多边界容易误判。
+- 证据优先可以防止重复做和回归。
+
+后续影响:
+
+- `04-task-matrix.md` 中没有证据的任务不得标为 `done`。
+- 文档、代码、测试、报告必须一起更新。
+
+## ADR-009:gmoccapy_XYZAB profile 必须阻断 TCP/RTCP 切换
+
+状态:accepted
+
+决策:
+
+`gmoccapy-xyzab` profile 虽然进入 profile selector,但必须标记
+`tcpCapable=false`、`rtcpProof=false`,并在 UI 和 store 层阻断 `tcp-*` kinsType
+或 `SET_RTCP enabled`。
+
+原因:
+
+- `gmoccapy_XYZAB.ini` 的真实运动学是 `trivkins coordinates=xyzab`。
+- 当前 Web 项目的 source-derived TCP/RTCP proof 来自 `xyzac-trt` 和 `xyzbc-trt`。
+- 如果 selector 允许 XYZAB reference profile 打开 TCP,会造成“参考配置可运行 RTCP”的误判。
+
+后续影响:
+
+- 侧栏 TCP 按钮在 `gmoccapy-xyzab` 下应 disabled,并给出 reference-only 原因。
+- `store.dispatch({ type: "SET_KINS_TYPE", kinsType: "tcp-..." })` 和
+ `SET_RTCP enabled` 必须保持防护,即使未来 UI 改动也不能绕过。
+- 如果未来接入真实 XYZAB TCP kinematics,应新增独立 runtime proof 和测试后再修改该决策。
diff --git a/work/working4/07-final-closure.md b/work/working4/07-final-closure.md
new file mode 100644
index 0000000..6bd7a76
--- /dev/null
+++ b/work/working4/07-final-closure.md
@@ -0,0 +1,86 @@
+# 07 最终收口说明
+
+生成日期:2026-06-26
+
+## 1. 收口结论
+
+`working4` 当前已完成。`04-task-matrix.md` 中 W4-D、W4-A、W4-P、W4-U、W4-G、
+W4-T、W4-V 任务均为 `done`,当前没有 `todo`、`in_progress`、`blocked` 或
+`deferred` 任务行。
+
+本阶段已完成:
+
+- `gmoccapy-xyzab` reference profile 和 source reference map。
+- gmoccapy button icon manifest、核心图标资产和 icon registry。
+- gmoccapy native NML/HAL/postgui communication diagnostics。
+- RUN、MDI、JOG、HOME、spindle、coolant、override gate 对齐。
+- Node smoke、browser smoke、dist browser smoke、QA JSON/PDF/截图证据。
+- 文档矩阵、推进台账、验收证据和决策记录归档。
+
+## 2. 最终证据
+
+QA report:
+
+```text
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.pdf
+```
+
+QA identifiers:
+
+```text
+jobId/job_id=xyzab-20260626214346-f4b4aa36
+reportId/report_id=report-xyzab-20260626214346-f4b4aa36
+status=PASS
+steps=6
+checks=10
+generatedAt=2026-06-26T21:43:46.310Z
+```
+
+截图目录:
+
+```text
+/home/meswork/cnc_wams/qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab/
+```
+
+最终校验命令:
+
+```bash
+rg -n "\\| W4-[A-Z0-9-]+ \\| (todo|in_progress|blocked|deferred) \\|" work/working4/04-task-matrix.md
+TD_WORD="$(printf '%s%s' TO DO)"
+STALE_W4_PATTERN="$(printf '\\u5f85\\u5b9e\\u65bd|\\u5f85\\u9a8c\\u8bc1|\\u5f85\\u751f\\u6210|PDF \\u4efb\\u52a1\\u4fdd\\u6301 todo|\\u672c\\u8f6e\\u53ea\\u521b\\u5efa\\u6587\\u6863|\\u6ca1\\u6709\\u751f\\u6210\\u65b0\\u7684 job_id|\\u540e\\u7eed\\u65b0\\u589e\\uff1a|\\u5efa\\u8bae\\u65b0\\u589e|\\u5efa\\u8bae\\u65b0\\u589e\\u6216\\u66f4\\u65b0|\\u5fc5\\u8981\\u65f6\\u8f93\\u51fa PDF|\\u540e\\u7eed\\u6267\\u884c\\u5165\\u53e3|\\u62df\\u5b8c\\u5584\\u529f\\u80fd\\u5305|%s' "$TD_WORD")"
+rg -n "$STALE_W4_PATTERN" work/working4/README.md work/working4/01-function-content.md work/working4/02-program-development-steps.md work/working4/04-task-matrix.md work/working4/05-acceptance-evidence.md work/working4/07-final-closure.md
+node -e "const r=JSON.parse(require('fs').readFileSync('qa/web-rtcp-5axis-site-test/output/gmoccapy-xyzab-function-report.json','utf8')); if(r.status!=='PASS') process.exit(1); console.log(r.jobId, r.reportId, r.status, r.steps.length, r.checks.length)"
+npm --prefix web-rtcp-5axis-sim-plan/app run build
+npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
+bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh
+node qa/web-rtcp-5axis-site-test/capture-gmoccapy-xyzab-function-cases.mjs
+git diff --check -- web-rtcp-5axis-sim-plan qa/web-rtcp-5axis-site-test work/working4
+```
+
+最终校验结果:
+
+- 未发现未完成 W4 任务行。
+- 当前状态文档未发现规划阶段残留关键词。
+- `npm run build`、`npm run smoke:node`、gmoccapy shell browser smoke 和 dist browser smoke 均返回 0。
+- QA capture 重新生成 JSON/PDF/截图,状态为 `PASS`,包含 6 个步骤和 10 个检查。
+- `git diff --check` 返回 0。
+
+## 3. 保留边界
+
+- `gmoccapy-xyzab` 是 `trivkins coordinates=xyzab` reference-only profile。
+- 本阶段不把 `gmoccapy-xyzab` 声称为 TCP/RTCP proof。
+- 本阶段不证明 native `linuxcncsvr`、`milltask`、HAL realtime thread 或 gmoccapy GTK runtime 实机运行。
+- 真实 RTCP promotion 仍来自 source-derived `xyzac-trt`、`xyzbc-trt` 等已有验收链路。
+
+## 4. 后续变更规则
+
+后续如继续扩展 `working4` 范围,必须先在 `04-task-matrix.md` 新增任务编号,不应复用已完成的 W4-D/W4-A/W4-P/W4-U/W4-G/W4-T/W4-V 任务作为未完成项。
+
+每个新增任务必须同步更新:
+
+- `03-progress-ledger.md`
+- `04-task-matrix.md`
+- `05-acceptance-evidence.md`
+- 本文件的最终证据或保留边界
diff --git a/work/working4/README.md b/work/working4/README.md
new file mode 100644
index 0000000..1c83d6e
--- /dev/null
+++ b/work/working4/README.md
@@ -0,0 +1,59 @@
+# working4 文档索引
+
+生成日期:2026-06-26
+
+本目录用于承接 `/home/meswork/cnc_wams/work/working3` 的资料,规划、实施并归档
+`/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan` 的 gmoccapy_XYZAB 功能完善工作。
+
+截至 2026-06-26,`04-task-matrix.md` 中 W4-D、W4-A、W4-P、W4-U、W4-G、W4-T、W4-V
+任务均已收口为 `done`。`07-final-closure.md` 是本阶段最终收口入口。
+
+当前状态:
+
+- 已完成:gmoccapy_XYZAB reference profile、图标 manifest/assets、communication/HAL diagnostics、task gate、browser smoke、QA JSON/PDF/截图证据。
+- 保留边界:`gmoccapy-xyzab` 仍是 `trivkins` reference-only profile,不作为 TCP/RTCP proof,也不证明 native `linuxcncsvr/milltask/HAL` 实机运行。
+- 后续变更:需要先在 `04-task-matrix.md` 新增任务编号,再按 `03-progress-ledger.md` 和 `05-acceptance-evidence.md` 追加证据。
+
+## 文档清单
+
+| 编号 | 文件 | 用途 |
+| --- | --- | --- |
+| 01 | `01-function-content.md` | 功能内容:说明已完善功能、资料来源、边界和成功定义。 |
+| 02 | `02-program-development-steps.md` | 程序开发详细步骤:记录各阶段已改文件、实现动作、命令和验收口径。 |
+| 03 | `03-progress-ledger.md` | 推进台账:记录每轮做了什么、改了哪些文件、验证了什么、下一步是什么。 |
+| 04 | `04-task-matrix.md` | 任务矩阵:统一任务编号、状态、验收标准,防止重复做。 |
+| 05 | `05-acceptance-evidence.md` | 验收证据:命令、页面、job_id、report_id、PDF、截图、JSON 等证据路径。 |
+| 06 | `06-decision-records.md` | 决策记录:记录为什么这么做,后续变更时不重复争论。 |
+| 07 | `07-final-closure.md` | 最终收口说明:汇总完成结论、最终证据、保留边界和后续变更规则。 |
+
+## 资料输入
+
+主要输入资料:
+
+```text
+/home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md
+/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/README.md
+/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.md
+/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.csv
+/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/files/
+```
+
+目标项目现有资料和代码:
+
+```text
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/README.md
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/docs/
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/working_run/
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/
+/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/
+```
+
+## 后续维护入口
+
+当前没有待执行的 `todo` 或 `in_progress` 任务。后续如新增工作,先打开:
+
+1. `04-task-matrix.md`,新增任务编号、状态和验收标准。
+2. `02-program-development-steps.md`,按对应阶段复用或扩展代码和测试步骤。
+3. `03-progress-ledger.md`,每轮结束追加台账。
+4. `05-acceptance-evidence.md`,补齐命令、截图、JSON、PDF 和报告编号。
+5. `07-final-closure.md`,更新最终证据或保留边界。