完善 xyzbc-trt OPFS 与 AXIS 首屏等效
This commit is contained in:
@@ -93,7 +93,7 @@ async function ensureMachineFilesForProfile(store) {
|
||||
if (state.machineFileStaging?.status === "staged" && state.machineFileStaging?.profileId === state.machineProfile) {
|
||||
return state.machineFileStaging;
|
||||
}
|
||||
return await store.stageMachineFiles();
|
||||
return await store.stageMachineFiles({ requireOpfs: true });
|
||||
} catch (error) {
|
||||
return {
|
||||
apiName: "web-rtcp-5axis-machine-file-staging-seed",
|
||||
|
||||
@@ -184,18 +184,22 @@ export function detectBrowserStorageCapability(globalScope = globalThis) {
|
||||
|
||||
function resolveSessionStorage(options = {}) {
|
||||
if (options.storage) {
|
||||
const mode = options.storageMode || storageModeFor(options.storage);
|
||||
if (options.requireOpfs === true && mode !== "opfs") {
|
||||
throw new Error(`OPFS storage required for session snapshots, got ${mode}`);
|
||||
}
|
||||
return {
|
||||
storage: options.storage,
|
||||
mode: options.storageMode || storageModeFor(options.storage),
|
||||
mode,
|
||||
capability: {
|
||||
apiName: "web-rtcp-5axis-storage-capability",
|
||||
opfsAvailable: options.storageMode === "opfs",
|
||||
opfsUnavailable: options.storageMode !== "opfs",
|
||||
opfsAvailable: mode === "opfs",
|
||||
opfsUnavailable: mode !== "opfs",
|
||||
secureContext: true,
|
||||
forcedUnavailable: false,
|
||||
hasNavigatorStorage: false,
|
||||
hasGetDirectory: typeof options.storage.getDirectory === "function",
|
||||
fallbackMode: options.storageMode === "opfs" ? null : options.storageMode || "custom",
|
||||
fallbackMode: mode === "opfs" ? null : mode || "custom",
|
||||
reason: "explicit_storage",
|
||||
},
|
||||
};
|
||||
@@ -209,6 +213,9 @@ function resolveSessionStorage(options = {}) {
|
||||
capability,
|
||||
};
|
||||
}
|
||||
if (options.requireOpfs === true) {
|
||||
throw new Error(`OPFS storage required for session snapshots: ${capability.reason}`);
|
||||
}
|
||||
browserMemorySessionStorage ??= createMemorySessionStorage();
|
||||
return {
|
||||
storage: browserMemorySessionStorage,
|
||||
|
||||
@@ -241,15 +241,19 @@ export function detectMachineFileStorageCapability(globalScope = globalThis) {
|
||||
|
||||
function resolveMachineFileStorage(options = {}) {
|
||||
if (options.storage) {
|
||||
const mode = options.storageMode || storageModeFor(options.storage);
|
||||
if (options.requireOpfs === true && mode !== "opfs") {
|
||||
throw new Error(`OPFS storage required for machine files, got ${mode}`);
|
||||
}
|
||||
return {
|
||||
storage: options.storage,
|
||||
mode: options.storageMode || storageModeFor(options.storage),
|
||||
mode,
|
||||
capability: {
|
||||
apiName: "web-rtcp-5axis-machine-file-storage-capability",
|
||||
opfsAvailable: options.storageMode === "opfs",
|
||||
opfsUnavailable: options.storageMode !== "opfs",
|
||||
opfsAvailable: mode === "opfs",
|
||||
opfsUnavailable: mode !== "opfs",
|
||||
secureContext: true,
|
||||
fallbackMode: options.storageMode === "opfs" ? null : options.storageMode || "custom",
|
||||
fallbackMode: mode === "opfs" ? null : mode || "custom",
|
||||
reason: "explicit_storage",
|
||||
},
|
||||
};
|
||||
@@ -262,6 +266,9 @@ function resolveMachineFileStorage(options = {}) {
|
||||
capability,
|
||||
};
|
||||
}
|
||||
if (options.requireOpfs === true) {
|
||||
throw new Error(`OPFS storage required for machine files: ${capability.reason}`);
|
||||
}
|
||||
browserMemoryMachineFileStorage ??= createMemoryStorage();
|
||||
return {
|
||||
storage: browserMemoryMachineFileStorage,
|
||||
|
||||
@@ -998,14 +998,18 @@ function basename(path = "") {
|
||||
|
||||
function resolveToolDbStorage(options = {}) {
|
||||
if (options.storage) {
|
||||
const mode = options.storageMode || storageModeFor(options.storage);
|
||||
if (options.requireOpfs === true && mode !== "opfs") {
|
||||
throw new Error(`OPFS storage required for tool DB, got ${mode}`);
|
||||
}
|
||||
return {
|
||||
storage: options.storage,
|
||||
mode: options.storageMode || storageModeFor(options.storage),
|
||||
mode,
|
||||
capability: {
|
||||
apiName: "web-rtcp-5axis-tool-db-storage-capability",
|
||||
opfsAvailable: options.storageMode === "opfs",
|
||||
opfsUnavailable: options.storageMode !== "opfs",
|
||||
fallbackMode: options.storageMode === "opfs" ? null : options.storageMode || "custom",
|
||||
opfsAvailable: mode === "opfs",
|
||||
opfsUnavailable: mode !== "opfs",
|
||||
fallbackMode: mode === "opfs" ? null : mode || "custom",
|
||||
reason: "explicit_storage",
|
||||
},
|
||||
};
|
||||
@@ -1018,6 +1022,9 @@ function resolveToolDbStorage(options = {}) {
|
||||
capability,
|
||||
};
|
||||
}
|
||||
if (options.requireOpfs === true) {
|
||||
throw new Error(`OPFS storage required for tool DB: ${capability.reason}`);
|
||||
}
|
||||
browserMemoryToolDbStorage ??= createMemoryToolDbStorage();
|
||||
return {
|
||||
storage: browserMemoryToolDbStorage,
|
||||
|
||||
@@ -99,6 +99,14 @@ function createTaskHalStatusLoopState({
|
||||
};
|
||||
}
|
||||
|
||||
function withDefaultWebOpfsRequirement(options = {}) {
|
||||
if (options.storage || options.requireOpfs !== undefined) {
|
||||
return options;
|
||||
}
|
||||
const isBrowser = typeof globalThis.window === "object" || typeof globalThis.document === "object";
|
||||
return isBrowser ? { ...options, requireOpfs: true } : options;
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
machineProfile: "xyzbc-trt",
|
||||
availableProfiles: fiveAxisProfiles.map(({ id, title, traj, kinematicsModuleId, kinematics }) => ({
|
||||
@@ -2061,13 +2069,15 @@ export function createSimulationStore(seed = {}) {
|
||||
const saveSession = async (options = {}) => {
|
||||
dispatch({ type: "SESSION_SAVE_STARTED" });
|
||||
try {
|
||||
const storageOptions = withDefaultWebOpfsRequirement(options);
|
||||
const sessionId = options.sessionId || state.sessionPersistence.sessionId;
|
||||
const filename = options.filename || state.sessionPersistence.filename;
|
||||
const payload = createFiveAxisSessionPayload(state);
|
||||
const { snapshot, path, storageMode, storageCapability } = await saveFiveAxisSessionSnapshot(sessionId, payload, {
|
||||
filename,
|
||||
storage: options.storage,
|
||||
storageMode: options.storageMode,
|
||||
storage: storageOptions.storage,
|
||||
storageMode: storageOptions.storageMode,
|
||||
requireOpfs: storageOptions.requireOpfs,
|
||||
metadata: options.metadata,
|
||||
});
|
||||
dispatch({
|
||||
@@ -2087,12 +2097,14 @@ export function createSimulationStore(seed = {}) {
|
||||
const restoreSession = async (options = {}) => {
|
||||
dispatch({ type: "SESSION_RESTORE_STARTED" });
|
||||
try {
|
||||
const storageOptions = withDefaultWebOpfsRequirement(options);
|
||||
const sessionId = options.sessionId || state.sessionPersistence.sessionId;
|
||||
const filename = options.filename || state.sessionPersistence.filename;
|
||||
const { snapshot, path, storageMode, storageCapability } = await loadFiveAxisSessionSnapshot(sessionId, {
|
||||
filename,
|
||||
storage: options.storage,
|
||||
storageMode: options.storageMode,
|
||||
storage: storageOptions.storage,
|
||||
storageMode: storageOptions.storageMode,
|
||||
requireOpfs: storageOptions.requireOpfs,
|
||||
});
|
||||
dispatch({
|
||||
type: "SESSION_RESTORE_COMPLETE",
|
||||
@@ -2113,8 +2125,10 @@ export function createSimulationStore(seed = {}) {
|
||||
const stageMachineFiles = async (options = {}) => {
|
||||
dispatch({ type: "MACHINE_FILE_STAGING_STARTED" });
|
||||
try {
|
||||
const storageOptions = withDefaultWebOpfsRequirement(options);
|
||||
const { plan, save } = await stageProfileMachineFiles(state.profile, {
|
||||
...options,
|
||||
...storageOptions,
|
||||
iniText: options.iniText || state.linuxCncIniConfig?.sourceText,
|
||||
});
|
||||
dispatch({ type: "MACHINE_FILE_STAGING_COMPLETE", plan, save });
|
||||
@@ -2153,7 +2167,7 @@ export function createSimulationStore(seed = {}) {
|
||||
if (!state.toolDbSimulation) {
|
||||
throw new Error("tool DB simulation is not ready");
|
||||
}
|
||||
const save = await saveToolDbSimulation(state.toolDbSimulation, options);
|
||||
const save = await saveToolDbSimulation(state.toolDbSimulation, withDefaultWebOpfsRequirement(options));
|
||||
setState({
|
||||
toolDbSimulation: save.toolDb,
|
||||
toolDbReadiness: createToolDbReadiness(save.toolDb),
|
||||
|
||||
@@ -754,6 +754,38 @@ button[data-icon-name] span {
|
||||
background: #eeeae3;
|
||||
}
|
||||
|
||||
.axis-first-screen-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(96px, 1fr));
|
||||
gap: 3px;
|
||||
padding: 5px 6px;
|
||||
border-bottom: 1px solid #aaa59b;
|
||||
background: #151515;
|
||||
color: #e9f8e9;
|
||||
font: 11px/1.2 "Courier New", monospace;
|
||||
}
|
||||
|
||||
.axis-first-screen-strip span {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding: 3px 5px;
|
||||
border: 1px solid #3e5f43;
|
||||
background: #071108;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.axis-first-screen-strip b {
|
||||
color: var(--green);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.axis-first-screen-strip [data-axis-ui-capability-ready="false"] {
|
||||
border-color: #7a6840;
|
||||
background: #1b1508;
|
||||
color: #f0d58c;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
@@ -1074,6 +1106,10 @@ button[data-icon-name] span {
|
||||
.spindle h2 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.axis-first-screen-strip {
|
||||
grid-template-columns: repeat(2, minmax(90px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 720px) {
|
||||
|
||||
@@ -50,6 +50,7 @@ export function mountGmoccapyShell(root, store) {
|
||||
}
|
||||
|
||||
function render(regions, state, dispatch) {
|
||||
renderShellEquivalenceDataset(regions, state);
|
||||
renderTitlebar(regions.titlebar, state);
|
||||
renderPreview(regions.preview, state, dispatch);
|
||||
renderDro(regions.dro, state);
|
||||
@@ -61,6 +62,26 @@ function render(regions, state, dispatch) {
|
||||
renderBottomControls(regions["bottom-controls"], state, dispatch);
|
||||
}
|
||||
|
||||
function renderShellEquivalenceDataset(regions, state) {
|
||||
const sectionMap = {
|
||||
preview: "preview-toolpath-and-machine-model",
|
||||
dro: "coordinates-dro",
|
||||
gcode: "program-and-mdi",
|
||||
"status-sidebar": "status-mode-and-switchkins",
|
||||
"info-tabs": "execution-tool-and-runtime-state",
|
||||
override: "feed-rapid-override",
|
||||
"spindle-coolant": "spindle-coolant",
|
||||
"bottom-controls": "run-jog-home-controls",
|
||||
};
|
||||
for (const [name, section] of Object.entries(sectionMap)) {
|
||||
const element = regions[name];
|
||||
if (!element) continue;
|
||||
element.dataset.axisUiSection = section;
|
||||
element.dataset.axisUiProfile = state.machineProfile;
|
||||
element.dataset.axisUiCoordinates = state.profile?.traj?.coordinates || "";
|
||||
}
|
||||
}
|
||||
|
||||
function renderTitlebar(element, state) {
|
||||
const currentLine = currentGcodeExecutionLine(state);
|
||||
|
||||
@@ -599,7 +620,20 @@ function renderInfoTabs(element, state) {
|
||||
const parityMatrix = state.linuxCncParityMatrix;
|
||||
const commSummary = createGmoccapyCommunicationSummary();
|
||||
const halSummary = createGmoccapyHalSummary();
|
||||
const firstScreen = createAxisFirstScreenSummary(state);
|
||||
element.innerHTML = `
|
||||
<section
|
||||
class="axis-first-screen-strip"
|
||||
data-axis-main-equivalence="${escapeHtml(firstScreen.semanticBoundary)}"
|
||||
data-axis-first-screen-ready="${firstScreen.ready ? "true" : "false"}"
|
||||
data-axis-first-screen-missing="${escapeHtml(firstScreen.missing.join(","))}"
|
||||
>
|
||||
${firstScreen.items.map((item) => `
|
||||
<span data-axis-ui-capability="${escapeHtml(item.id)}" data-axis-ui-capability-ready="${item.ready ? "true" : "false"}">
|
||||
<b>${escapeHtml(item.label)}</b>${escapeHtml(item.value)}
|
||||
</span>
|
||||
`).join("")}
|
||||
</section>
|
||||
<nav class="tabs">
|
||||
<button type="button" class="active">Tool info and G-codes</button>
|
||||
<button type="button">G-code properties</button>
|
||||
@@ -690,6 +724,84 @@ function renderInfoTabs(element, state) {
|
||||
`;
|
||||
}
|
||||
|
||||
function createAxisFirstScreenSummary(state) {
|
||||
const currentLine = currentGcodeExecutionLine(state);
|
||||
const taskPolicy = state.linuxCncTaskPolicy || {};
|
||||
const taskHalReady = state.taskHalRuntimeReadiness?.loaded === true
|
||||
&& state.taskHalRuntimeReadiness?.taskRuntimeReady === true
|
||||
&& state.taskHalRuntimeReadiness?.motionRuntimeReady === true
|
||||
&& state.taskHalRuntimeReadiness?.halRuntimeReady === true;
|
||||
const items = [
|
||||
{
|
||||
id: "program",
|
||||
label: "Program ",
|
||||
ready: Boolean(state.activeProgram),
|
||||
value: `${state.activeProgram || "-"} L${currentLine}`,
|
||||
},
|
||||
{
|
||||
id: "coordinates",
|
||||
label: "XYZBC ",
|
||||
ready: state.profile?.traj?.coordinates === "XYZBC",
|
||||
value: `${formatNumber(state.dro.x)} ${formatNumber(state.dro.y)} ${formatNumber(state.dro.z)} B${formatNumber(state.dro.b)} C${formatNumber(state.dro.c)}`,
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
label: "State ",
|
||||
ready: Boolean(state.machine?.taskState),
|
||||
value: `${state.machine.taskState}/${state.machine.mode}/${state.runState}`,
|
||||
},
|
||||
{
|
||||
id: "mdi",
|
||||
label: "MDI ",
|
||||
ready: Boolean(state.machine?.mdiCommand),
|
||||
value: state.machine.mdiCommand || "-",
|
||||
},
|
||||
{
|
||||
id: "switchkins",
|
||||
label: "KINS ",
|
||||
ready: state.profile?.kinematicsParameters?.switchkinsTypes?.length > 0,
|
||||
value: `${state.kinsType} ${formatSwitchkinsSummary(state.programExecution)}`,
|
||||
},
|
||||
{
|
||||
id: "override",
|
||||
label: "Override ",
|
||||
ready: Number.isFinite(Number(state.feed?.feedOverride)) && Number.isFinite(Number(state.feed?.rapidOverride)),
|
||||
value: `F${state.feed.feedOverride}% R${state.feed.rapidOverride}%`,
|
||||
},
|
||||
{
|
||||
id: "tool",
|
||||
label: "Tool ",
|
||||
ready: Boolean(state.toolPreview?.toolNumber),
|
||||
value: `T${state.toolPreview.toolNumber} D${formatNumber(state.toolPreview.diameter, 2)} L${formatNumber(state.toolPreview.length, 3)}`,
|
||||
},
|
||||
{
|
||||
id: "preview",
|
||||
label: "Preview ",
|
||||
ready: state.preview?.pathPoints > 0,
|
||||
value: `${state.preview?.pathPoints || 0} pts ${state.programExecutionSourceMode}`,
|
||||
},
|
||||
{
|
||||
id: "execution",
|
||||
label: "Execution ",
|
||||
ready: taskHalReady || Boolean(state.taskHalStatus) || Boolean(state.programRuntimeFeedback),
|
||||
value: taskHalReady ? "task/HAL ready" : (state.programRuntimeFeedback ? "runtime feedback" : "pending"),
|
||||
},
|
||||
{
|
||||
id: "axis-buttons",
|
||||
label: "Panel ",
|
||||
ready: taskPolicy.canExecuteMdi !== undefined && taskPolicy.canRunAuto !== undefined,
|
||||
value: `${taskPolicy.canRunAuto ? "AUTO" : "AUTO gated"} ${taskPolicy.canExecuteMdi ? "MDI" : "MDI gated"} ${taskPolicy.canJog ? "JOG" : "JOG gated"}`,
|
||||
},
|
||||
];
|
||||
const missing = items.filter((item) => !item.ready).map((item) => item.id);
|
||||
return {
|
||||
semanticBoundary: "xyzbc_trt_axis_first_screen_program_coordinates_status_mdi_switchkins_override_tool_preview_execution",
|
||||
ready: missing.length === 0,
|
||||
missing,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
function formatLinuxCncTaskGates(taskPolicy) {
|
||||
if (!taskPolicy) return "pending";
|
||||
return [
|
||||
|
||||
@@ -62,7 +62,10 @@
|
||||
throw new Error(`task/HAL runtime should run in worker: ${JSON.stringify(taskHal)}`);
|
||||
}
|
||||
|
||||
await api.machineFileSeedReady;
|
||||
const seedResult = await api.machineFileSeedReady;
|
||||
if (seedResult?.save?.storageMode !== "opfs") {
|
||||
throw new Error(`machine file seed did not use OPFS: ${JSON.stringify(seedResult?.save || seedResult)}`);
|
||||
}
|
||||
await waitFor(() => {
|
||||
const state = api.getState();
|
||||
return !state.interpreterExecutionPending
|
||||
@@ -80,6 +83,13 @@
|
||||
if (state.machineFileStaging?.profileId !== "xyzbc-trt") {
|
||||
throw new Error(`staging profile mismatch: ${state.machineFileStaging?.profileId}`);
|
||||
}
|
||||
if (
|
||||
state.machineFileStaging?.storageMode !== "opfs" ||
|
||||
state.machineFileStaging?.storageCapability?.opfsAvailable !== true ||
|
||||
state.machineFileStaging?.storageCapability?.reason !== "opfs_available"
|
||||
) {
|
||||
throw new Error(`machine files must use browser OPFS: ${JSON.stringify(state.machineFileStaging?.storageCapability)}`);
|
||||
}
|
||||
if (!state.machineFileStaging?.gcodeSources?.some((source) => source.filename === "xyzbc_switchkins.ngc")) {
|
||||
throw new Error("staged sources missing xyzbc_switchkins.ngc");
|
||||
}
|
||||
@@ -89,6 +99,69 @@
|
||||
if (state.programExecution?.summary?.motionEventCount < 1) {
|
||||
throw new Error(`default program did not produce canonical motion: ${JSON.stringify(state.programExecution?.summary)}`);
|
||||
}
|
||||
const selectedSource = state.machineFileStaging.gcodeSources.find((source) => source.filename === "xyzbc_switchkins.ngc");
|
||||
const opfsProgramText = await readOpfsText(win.navigator.storage, selectedSource.opfsPath);
|
||||
if (!opfsProgramText.includes("M428") || !opfsProgramText.includes("M429")) {
|
||||
throw new Error(`OPFS staged program text did not contain switchkins commands: ${selectedSource.opfsPath}`);
|
||||
}
|
||||
const savedSession = await api.saveSession();
|
||||
if (savedSession.storageMode !== "opfs" || savedSession.storageCapability?.opfsAvailable !== true) {
|
||||
throw new Error(`session save must use browser OPFS: ${JSON.stringify(savedSession.storageCapability)}`);
|
||||
}
|
||||
const sessionText = await readOpfsText(win.navigator.storage, savedSession.path);
|
||||
if (!sessionText.includes('"machineProfile": "xyzbc-trt"')) {
|
||||
throw new Error(`OPFS session snapshot did not persist xyzbc-trt profile: ${savedSession.path}`);
|
||||
}
|
||||
const savedToolDb = await api.saveToolDb();
|
||||
if (savedToolDb.storageMode !== "opfs" || savedToolDb.storageCapability?.opfsAvailable !== true) {
|
||||
throw new Error(`tool table save must use browser OPFS: ${JSON.stringify(savedToolDb.storageCapability)}`);
|
||||
}
|
||||
const toolTableText = await readOpfsText(win.navigator.storage, savedToolDb.path);
|
||||
if (!toolTableText.includes("T2") || !toolTableText.includes("Z10")) {
|
||||
throw new Error(`OPFS tool table save did not contain T2/Z10: ${savedToolDb.path}`);
|
||||
}
|
||||
|
||||
const expectedAxisSections = {
|
||||
".preview-panel": "preview-toolpath-and-machine-model",
|
||||
".dro-panel": "coordinates-dro",
|
||||
".gcode-panel": "program-and-mdi",
|
||||
".status-sidebar": "status-mode-and-switchkins",
|
||||
".info-tabs": "execution-tool-and-runtime-state",
|
||||
".override-panel": "feed-rapid-override",
|
||||
".spindle-coolant-panel": "spindle-coolant",
|
||||
".bottom-controls": "run-jog-home-controls",
|
||||
};
|
||||
for (const [selector, section] of Object.entries(expectedAxisSections)) {
|
||||
const element = doc.querySelector(selector);
|
||||
if (!element) {
|
||||
throw new Error(`missing AXIS UI section ${selector}`);
|
||||
}
|
||||
if (
|
||||
element.dataset.axisUiSection !== section ||
|
||||
element.dataset.axisUiProfile !== "xyzbc-trt" ||
|
||||
element.dataset.axisUiCoordinates !== "XYZBC"
|
||||
) {
|
||||
throw new Error(`AXIS section dataset mismatch ${selector}: ${JSON.stringify(element.dataset)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const firstScreen = await waitFor(
|
||||
() => doc.querySelector('[data-axis-main-equivalence="xyzbc_trt_axis_first_screen_program_coordinates_status_mdi_switchkins_override_tool_preview_execution"]'),
|
||||
"AXIS first-screen summary",
|
||||
);
|
||||
const capabilities = Array.from(firstScreen.querySelectorAll("[data-axis-ui-capability]"));
|
||||
const capabilityIds = capabilities.map((element) => element.dataset.axisUiCapability);
|
||||
for (const id of ["program", "coordinates", "status", "mdi", "switchkins", "override", "tool", "preview", "execution", "axis-buttons"]) {
|
||||
if (!capabilityIds.includes(id)) {
|
||||
throw new Error(`missing first-screen capability ${id}: ${capabilityIds.join(",")}`);
|
||||
}
|
||||
}
|
||||
if (firstScreen.dataset.axisFirstScreenReady !== "true") {
|
||||
throw new Error(`first-screen summary not ready: ${firstScreen.dataset.axisFirstScreenMissing}`);
|
||||
}
|
||||
if (!doc.querySelector('[data-action="mdi-command"]') || !doc.querySelector('[data-action="kins-tcp"]')) {
|
||||
throw new Error("MDI input or switchkins TCP control missing from first screen");
|
||||
}
|
||||
|
||||
const canvas = await waitFor(() => doc.querySelector("[data-five-axis-canvas]"), "preview canvas");
|
||||
await waitFor(() => canvas.dataset.threeReady === "true" && Number(canvas.dataset.threePathPoints || 0) > 0, "Three.js dataset");
|
||||
@@ -172,6 +245,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function readOpfsText(storage, path) {
|
||||
if (!storage?.getDirectory) {
|
||||
throw new Error("browser OPFS getDirectory is unavailable");
|
||||
}
|
||||
let current = await storage.getDirectory();
|
||||
const parts = String(path).split("/").filter(Boolean);
|
||||
for (const part of parts.slice(0, -1)) {
|
||||
current = await current.getDirectoryHandle(part);
|
||||
}
|
||||
const fileHandle = await current.getFileHandle(parts.at(-1));
|
||||
return fileHandle.getFile().then((file) => file.text());
|
||||
}
|
||||
|
||||
runSmoke().catch((error) => {
|
||||
result.textContent = `xyzbc_trt_browser_smoke=fail ${error.stack || error.message || error}`;
|
||||
});
|
||||
|
||||
@@ -57,6 +57,7 @@ const toolRuntime = buildToolRuntimeEvidence({
|
||||
fallbackToolLength: state.toolPreview?.length,
|
||||
});
|
||||
const semanticFields = buildSemanticFields({ profile, ini, staged, state, toolRuntime });
|
||||
const axisMainUi = buildAxisMainUiEvidence({ state, profile, paths, toolRuntime, semanticFields });
|
||||
|
||||
const evidence = {
|
||||
apiName: "xyzbc-trt-web-opfs-wasm-evidence",
|
||||
@@ -125,6 +126,7 @@ const evidence = {
|
||||
previewPath: paths.previewPath,
|
||||
executionPath: paths.executionPath,
|
||||
toolRuntime,
|
||||
axisMainUi,
|
||||
...semanticFields,
|
||||
wasm: wasmArtifacts,
|
||||
coverage: {
|
||||
@@ -152,6 +154,7 @@ const evidence = {
|
||||
&& Boolean(semanticFields.axisJointLimits.axisLimits.C),
|
||||
previewPathAvailable: paths.previewPath.sampleCount > 0,
|
||||
executionPathAvailable: paths.executionPath.sampleCount > 0,
|
||||
axisMainUiEquivalent: axisMainUi.ready,
|
||||
toolTableToToolOffsetClosed: toolRuntime.ready
|
||||
&& toolRuntime.toolTable.toolCount > 0
|
||||
&& toolRuntime.activeOffsetApplied
|
||||
@@ -512,6 +515,7 @@ function buildSemanticFields({ profile, ini, staged, state, toolRuntime }) {
|
||||
})),
|
||||
uiEquivalence: {
|
||||
firstViewport: "axis-equivalent-cnc-console",
|
||||
semanticBoundary: "xyzbc_trt_axis_first_screen_program_coordinates_status_mdi_switchkins_override_tool_preview_execution",
|
||||
regions: [
|
||||
"program",
|
||||
"dro",
|
||||
@@ -521,6 +525,18 @@ function buildSemanticFields({ profile, ini, staged, state, toolRuntime }) {
|
||||
"tool",
|
||||
"preview-execution",
|
||||
],
|
||||
axisMainUiCapabilityIds: [
|
||||
"program",
|
||||
"coordinates",
|
||||
"status",
|
||||
"mdi",
|
||||
"switchkins",
|
||||
"override",
|
||||
"tool",
|
||||
"preview",
|
||||
"execution",
|
||||
"axis-buttons",
|
||||
],
|
||||
activeProgram: state.activeProgram,
|
||||
selectedGcodeSourceRel: state.machineFileStaging.selectedGcodeSourceRel,
|
||||
pyvcpPanelSchema: profile.panelSchema?.id,
|
||||
@@ -564,6 +580,92 @@ function buildSemanticFields({ profile, ini, staged, state, toolRuntime }) {
|
||||
};
|
||||
}
|
||||
|
||||
function buildAxisMainUiEvidence({ state, profile, paths, toolRuntime, semanticFields }) {
|
||||
const taskPolicy = state.linuxCncTaskPolicy || {};
|
||||
const requiredSections = [
|
||||
{ id: "preview", section: "preview-toolpath-and-machine-model" },
|
||||
{ id: "dro", section: "coordinates-dro" },
|
||||
{ id: "gcode", section: "program-and-mdi" },
|
||||
{ id: "status-sidebar", section: "status-mode-and-switchkins" },
|
||||
{ id: "info-tabs", section: "execution-tool-and-runtime-state" },
|
||||
{ id: "override", section: "feed-rapid-override" },
|
||||
{ id: "spindle-coolant", section: "spindle-coolant" },
|
||||
{ id: "bottom-controls", section: "run-jog-home-controls" },
|
||||
];
|
||||
const capabilities = [
|
||||
{
|
||||
id: "program",
|
||||
ready: Boolean(state.activeProgram) && state.machineFileStaging?.selectedGcodeSourceRel?.endsWith("xyzbc_switchkins.ngc"),
|
||||
evidence: state.activeProgram,
|
||||
},
|
||||
{
|
||||
id: "coordinates",
|
||||
ready: profile.traj?.coordinates === "XYZBC" && state.profile?.traj?.coordinates === "XYZBC",
|
||||
evidence: { coordinates: profile.traj?.coordinates, droAxes: ["X", "Y", "Z", "B", "C"] },
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
ready: Boolean(state.machine?.taskState) && Boolean(state.runState),
|
||||
evidence: { taskState: state.machine?.taskState, mode: state.machine?.mode, runState: state.runState },
|
||||
},
|
||||
{
|
||||
id: "mdi",
|
||||
ready: Boolean(state.machine?.mdiCommand) && profile.kinematicsParameters?.switchkinsTypes?.some((type) => type.mdiCommand === "M428"),
|
||||
evidence: { command: state.machine?.mdiCommand, switchkinsMdi: profile.kinematicsParameters?.switchkinsTypes?.map((type) => type.mdiCommand) },
|
||||
},
|
||||
{
|
||||
id: "switchkins",
|
||||
ready: profile.kinematicsParameters?.switchkinsTypes?.length >= 3
|
||||
&& semanticFields.switchkinsTransitions?.some((item) => item.mdiCommand === "M428"),
|
||||
evidence: semanticFields.switchkinsTransitions,
|
||||
},
|
||||
{
|
||||
id: "override",
|
||||
ready: Number.isFinite(Number(state.feed?.feedOverride)) && Number.isFinite(Number(state.feed?.rapidOverride)),
|
||||
evidence: { feedOverride: state.feed?.feedOverride, rapidOverride: state.feed?.rapidOverride },
|
||||
},
|
||||
{
|
||||
id: "tool",
|
||||
ready: toolRuntime.ready && toolRuntime.activeOffsetApplied,
|
||||
evidence: toolRuntime.pathTool,
|
||||
},
|
||||
{
|
||||
id: "preview",
|
||||
ready: paths.previewPath?.sampleCount > 0,
|
||||
evidence: { samplePeriodMs: paths.previewPath?.samplePeriodMs, sampleCount: paths.previewPath?.sampleCount },
|
||||
},
|
||||
{
|
||||
id: "execution",
|
||||
ready: paths.executionPath?.sampleCount > 0,
|
||||
evidence: { samplePeriodMs: paths.executionPath?.samplePeriodMs, sampleCount: paths.executionPath?.sampleCount },
|
||||
},
|
||||
{
|
||||
id: "axis-buttons",
|
||||
ready: taskPolicy.canRunAuto !== undefined
|
||||
&& taskPolicy.canExecuteMdi !== undefined
|
||||
&& taskPolicy.canJog !== undefined
|
||||
&& taskPolicy.canHome !== undefined,
|
||||
evidence: {
|
||||
canRunAuto: taskPolicy.canRunAuto,
|
||||
canExecuteMdi: taskPolicy.canExecuteMdi,
|
||||
canJog: taskPolicy.canJog,
|
||||
canHome: taskPolicy.canHome,
|
||||
},
|
||||
},
|
||||
];
|
||||
const missingCapabilities = capabilities.filter((item) => !item.ready).map((item) => item.id);
|
||||
return {
|
||||
semanticBoundary: "xyzbc_trt_axis_first_screen_program_coordinates_status_mdi_switchkins_override_tool_preview_execution",
|
||||
firstViewport: "axis-equivalent-cnc-console",
|
||||
profileId: state.machineProfile,
|
||||
coordinates: state.profile?.traj?.coordinates,
|
||||
requiredSections,
|
||||
capabilities,
|
||||
missingCapabilities,
|
||||
ready: missingCapabilities.length === 0,
|
||||
};
|
||||
}
|
||||
|
||||
function buildToolRuntimeEvidence({ profile, staged, selectedPlan, fallbackToolLength = 0 }) {
|
||||
const toolTableFile = staged.save.files.find((file) => file.sourceRel === profile.toolTablePath)
|
||||
|| staged.save.files.find((file) => file.kind === "toolTable");
|
||||
|
||||
@@ -75,6 +75,9 @@ const checks = [
|
||||
check("limits", "web TRAJ/AXIS/JOINT limits represented", webEvidence.coverage?.axisJointLimitsCovered === true, {
|
||||
axisJointLimits: webEvidence.axisJointLimits,
|
||||
}),
|
||||
check("ui", "web AXIS first screen exposes program, coordinates, status, MDI/switchkins, override, tool, preview, and execution", webEvidence.coverage?.axisMainUiEquivalent === true, {
|
||||
axisMainUi: webEvidence.axisMainUi,
|
||||
}),
|
||||
check("path-preview", "native and web preview path sample period is 20ms", pathComparison.previewVsPreview.periodsMatch === true, {
|
||||
nativeSamplePeriodMs: pathComparison.previewVsPreview.nativeSamplePeriodMs,
|
||||
webSamplePeriodMs: pathComparison.previewVsPreview.webSamplePeriodMs,
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
# 03-推进台账
|
||||
|
||||
## 2026-07-02 Web OPFS 强制与 AXIS 首屏等效实现轮次
|
||||
|
||||
### 本轮目标
|
||||
|
||||
接续用户要求“Web 的文件使用 OPFS 系统”,把浏览器端 machine files、session snapshot、tool table 保存链路从可 fallback 改为目标 app 默认强制 OPFS,并完成 T-026 AXIS 首屏等效验收。
|
||||
|
||||
### 已做事项
|
||||
|
||||
- 修改 `app/src/runtime/linuxcnc-machine-file-staging.js`:
|
||||
- `saveMachineFileStagingPlan()` 的存储解析支持 `requireOpfs`。
|
||||
- 当目标 app 要求 OPFS 但浏览器缺少 `navigator.storage.getDirectory()` 或非安全上下文时直接报错,不再静默落到 memory fallback。
|
||||
- 修改 `app/src/runtime/five-axis-session.js` 和 `app/src/runtime/tool-db-simulation.js`:
|
||||
- session snapshot 与 tool table 保存同样支持 `requireOpfs`。
|
||||
- 修改 `app/src/state/store.js` 和 `app/src/main.js`:
|
||||
- 浏览器 UI 默认对 machine file staging、session save/restore、tool DB save 启用 `requireOpfs=true`。
|
||||
- Node/测试显式传入 storage 时仍保留内存存储能力,用于非浏览器 smoke。
|
||||
- 修改 `tests/browser/xyzbc_trt_browser_smoke.html`:
|
||||
- 断言启动 staging `storageMode=opfs`。
|
||||
- 从 OPFS 读回 `xyzbc_switchkins.ngc`,确认包含 `M428/M429`。
|
||||
- 保存并从 OPFS 读回 session snapshot,确认包含 `machineProfile=xyzbc-trt`。
|
||||
- 保存并从 OPFS 读回 tool table,确认包含 `T2/Z10`。
|
||||
- 断言 AXIS 首屏区域 dataset 和能力矩阵。
|
||||
- 修改 `app/src/ui/gmoccapy-shell.js` 与 `app/src/styles/gmoccapy.css`:
|
||||
- 增加 AXIS 首屏等效状态带,覆盖 program、coordinates、status、MDI、switchkins、override、tool、preview、execution、axis-buttons。
|
||||
- 各首屏区域输出 `data-axis-ui-section/profile/coordinates`,供浏览器验收。
|
||||
- 修改 `tools/collect-web-xyzbc-trt-evidence.mjs` 与 `tools/compare-xyzbc-trt-evidence.mjs`:
|
||||
- 新增 `axisMainUi` evidence 和 compare 检查。
|
||||
- Web evidence 中 Node 采集仍使用 memory storage;真实浏览器 OPFS 以 `smoke:browser` 为准。
|
||||
- 更新 `04-任务矩阵.md`:
|
||||
- T-006/T-014/T-016 增加真实 OPFS 验收描述。
|
||||
- T-026 标记为完成。
|
||||
|
||||
### 验证情况
|
||||
|
||||
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:node` 通过。
|
||||
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run build` 通过,输出 `gmoccapy_static_build=ok`。
|
||||
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:browser` 通过,输出 `xyzbc_trt_browser_smoke=ok`,其中已验证 OPFS 写入和读回。
|
||||
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:web` 通过。
|
||||
- `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:compare` 已生成真实失败报告:当前 20/25 通过;新增 AXIS 首屏检查通过,剩余 5 项仍来自 native preview/execution path 样本缺失。
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 继续 T-019/T-037:在 `/home/mes123456/cnc_wams/linuxcnc/scripts/rip-environment` 下重跑 native evidence,使 native path 进入 20ms 可比状态。
|
||||
2. 继续 T-028/T-030:补 basic_sim 等效 task/HAL 细节和 Ngcgui/remap 子程序执行验收。
|
||||
3. 继续 T-038:基于真实 native 状态流复核 Web 按钮、HAL pin、路径和状态联锁。
|
||||
|
||||
## 2026-07-02 Tool table 到 tool-offset 闭环实现轮次
|
||||
|
||||
### 本轮目标
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
| T-003 | 创建目标 Web app | 完成 | `web-rtcp-5axis-xyzbc-trt-sim-plan/app/index.html` 存在 |
|
||||
| T-004 | 默认机型改为 `xyzbc-trt` | 完成 | `createSimulationStore().getState().machineProfile === "xyzbc-trt"` |
|
||||
| T-005 | 接入 `xyzbc-trt` INI/profile | 完成 | profile 坐标为 `XYZBC`,kinematics 为 `xyzbc-trt-kins` |
|
||||
| T-006 | OPFS 路径隔离到目标项目 | 完成 | staged 文件 path 以 `web-rtcp-5axis-xyzbc-trt-sim-plan/machines/xyzbc-trt/` 开头 |
|
||||
| T-006 | OPFS 路径隔离到目标项目 | 完成 | staged 文件 path 以 `web-rtcp-5axis-xyzbc-trt-sim-plan/machines/xyzbc-trt/` 开头;浏览器 smoke 断言 machine files/session/tool table 实际 `storageMode=opfs` 并可从 OPFS 读回 |
|
||||
| T-007 | 默认加载 `xyzbc_switchkins.ngc` | 完成 | profile `machineFileStaging.defaultProgramFilename` 为 `xyzbc_switchkins.ngc` |
|
||||
| T-008 | Stage remap/tool/PyVCP 文件 | 完成 | staging 包含 `428remap.ngc`、`429remap.ngc`、`430remap.ngc`、`xyzbc-trt.tbl`、`xyzbc-trt.xml` |
|
||||
| T-009 | 增加目标 Node smoke | 完成 | `tests/node/verify_xyzbc_trt_web_app.mjs` 存在并通过 |
|
||||
@@ -15,10 +15,10 @@
|
||||
| T-011 | 完整 kinematics WASM 验证 | 完成 | `SKIP_KINEMATICS_BUILD=1 wasm-port/tests/wasm/node/verify_kinematics_wasm.sh` 通过,覆盖 `xyzbc-trt` |
|
||||
| T-012 | 完整 interpreter/remap WASM 验证 | 完成 | `wasm-port/tests/wasm/node/verify_interp_wasm.sh` 通过,输出 `interp_wasm_node_smoke=ok` |
|
||||
| T-013 | 完整 task/HAL WASM 验证 | 完成 | `SKIP_TASK_HAL_BUILD=1 wasm-port/tests/wasm/node/verify_task_hal_wasm.sh` 通过 |
|
||||
| T-014 | 目标浏览器 smoke | 完成 | `npm run smoke:browser` 通过;页面默认显示 `xyzbc-trt`,kinematics/interpreter/task-HAL worker runtime ready,canvas 非空并暴露 Vismach dataset |
|
||||
| T-014 | 目标浏览器 smoke | 完成 | `npm run smoke:browser` 通过;页面默认显示 `xyzbc-trt`,kinematics/interpreter/task-HAL worker runtime ready,canvas 非空并暴露 Vismach dataset;Web 文件强制使用 OPFS |
|
||||
| T-015 | native 真实执行 JSON 采集 | 完成 | `native-xyzbc-trt-evidence.json` 记录真实执行事件,状态 completed |
|
||||
| T-016 | Web OPFS/WASM readiness JSON 采集 | 完成 | `web-xyzbc-trt-evidence.json` 记录 Web profile/INI/staging/WASM readiness |
|
||||
| T-017 | native/Web JSON 对比 | 部分完成 | `compare-xyzbc-trt-evidence.json` 已生成;当前 19/24 通过,剩余 5 项均为 native preview/execution path 样本缺失 |
|
||||
| T-016 | Web OPFS/WASM readiness JSON 采集 | 完成 | `web-xyzbc-trt-evidence.json` 记录 Web profile/INI/staging/WASM readiness;真实浏览器 OPFS 由 `smoke:browser` 读回验证,Node evidence 为内存采集 |
|
||||
| T-017 | native/Web JSON 对比 | 部分完成 | `compare-xyzbc-trt-evidence.json` 已生成;当前 20/25 通过,剩余 5 项均为 native preview/execution path 样本缺失 |
|
||||
| T-018 | WASM artifact 构建前置 | 完成 | 已生成 core/kinematics/tp/task-hal 所需 `.js/.wasm`,`web-xyzbc-trt-evidence.json.wasm.missing=[]` |
|
||||
| T-019 | native 刀具预览路径 JSON 采集 | 待实现 | `native-xyzbc-trt-evidence.json.previewPath.samples` 使用 20ms 周期记录 LinuxCNC 预览刀路 |
|
||||
| T-020 | native 刀具执行路径 JSON 采集 | 部分完成 | native 脚本已从 `stat()` 执行事件重采样为 20ms `executionPath`;需用新基线重跑生成证据 |
|
||||
@@ -27,7 +27,7 @@
|
||||
| T-023 | 刀路采样周期一致性检查 | 完成 | compare 检查 native/Web path `samplePeriodMs === 20`,否则 fail |
|
||||
| T-024 | 刀路误差统计对比 | 完成 | compare 输出 preview/execution 的 TCP、joint、toolAxis 误差统计和缺样本清单 |
|
||||
| T-025 | 全量对标追踪矩阵 | 完成 | `07-全量对标追踪矩阵.md` 逐项映射 `xyzbc-trt-runtime-files.md` 的运行功能 |
|
||||
| T-026 | AXIS 主界面等效功能 | 待实现 | Web 首屏包含程序、坐标、状态、MDI/switchkins、override、工具和预览/执行区域 |
|
||||
| T-026 | AXIS 主界面等效功能 | 完成 | Web 首屏包含程序、坐标、状态、MDI/switchkins、override、工具和预览/执行区域;browser smoke 和 Web evidence `axisMainUi.ready=true` |
|
||||
| T-027 | POSTGUI HAL/PyVCP 连接等效 | 完成 | Web evidence JSON 记录 `pyvcp.* -> halui.mdi-command-* -> M428/M429/M430` 和 multilabel 状态 |
|
||||
| T-028 | basic_sim 等效 task/HAL | 待前置 | Web task/HAL runtime 覆盖模拟回零、manual toolchange、spindle、joint feedback |
|
||||
| T-029 | Vismach 3D 模型 pin 对标 | 完成 | Web 3D 模型由 `table-x/saddle-y/spindle-z/tilt-b/rotate-c/tool-offset/x-offset/z-offset` 驱动;`web-xyzbc-trt-evidence.json.vismachEquivalent` 记录 pin 值、HAL net 和变换 |
|
||||
|
||||
@@ -1,5 +1,58 @@
|
||||
# 05-验收证据
|
||||
|
||||
## Web OPFS 与 AXIS 首屏等效验收
|
||||
|
||||
### 命令
|
||||
|
||||
```bash
|
||||
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:node
|
||||
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run build
|
||||
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run smoke:browser
|
||||
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:web
|
||||
npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run evidence:compare
|
||||
```
|
||||
|
||||
### 结果
|
||||
|
||||
```text
|
||||
xyzbc_trt_web_app_smoke=ok
|
||||
gmoccapy_static_build=ok
|
||||
xyzbc_trt_browser_smoke=ok
|
||||
web_xyzbc_trt_evidence=.../working/evidence/web-xyzbc-trt-evidence.json
|
||||
compare_xyzbc_trt_status=fail fail_count=5
|
||||
```
|
||||
|
||||
### 浏览器 OPFS 断言
|
||||
|
||||
```text
|
||||
machineFileSeedReady.save.storageMode = opfs
|
||||
machineFileStaging.storageMode = opfs
|
||||
machineFileStaging.storageCapability.opfsAvailable = true
|
||||
OPFS readback xyzbc_switchkins.ngc contains M428/M429
|
||||
saveSession().storageMode = opfs
|
||||
OPFS readback session contains "machineProfile": "xyzbc-trt"
|
||||
saveToolDb().storageMode = opfs
|
||||
OPFS readback tool table contains T2 and Z10
|
||||
```
|
||||
|
||||
### AXIS 首屏证据
|
||||
|
||||
```text
|
||||
web.axisMainUi.ready = true
|
||||
web.axisMainUi.profileId = xyzbc-trt
|
||||
web.axisMainUi.coordinates = XYZBC
|
||||
web.axisMainUi.missingCapabilities = []
|
||||
web.coverage.axisMainUiEquivalent = true
|
||||
compare.summary.passCount = 20
|
||||
compare.summary.failCount = 5
|
||||
```
|
||||
|
||||
结论:
|
||||
|
||||
- 目标 Web app 默认不再把 machine files、session、tool table 静默保存到 memory fallback;浏览器路径强制 OPFS。
|
||||
- `smoke:browser` 已从 OPFS 读回程序、会话和刀具表内容,证明不是仅生成 OPFS path。
|
||||
- Node evidence 仍使用内存存储模拟文件内容,这是非浏览器采集限制;真实 Web 文件系统验收以 browser smoke 为准。
|
||||
|
||||
## Tool table 到 tool-offset 闭环验收
|
||||
|
||||
### 命令
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"apiName": "xyzbc-trt-native-web-evidence-comparison",
|
||||
"status": "fail",
|
||||
"comparedAt": "2026-07-02T13:36:36.368Z",
|
||||
"comparedAt": "2026-07-02T13:57:49.317Z",
|
||||
"nativePath": "/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/evidence/native-xyzbc-trt-evidence.json",
|
||||
"webPath": "/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/evidence/web-xyzbc-trt-evidence.json",
|
||||
"summary": {
|
||||
"checkCount": 24,
|
||||
"passCount": 19,
|
||||
"checkCount": 25,
|
||||
"passCount": 20,
|
||||
"failCount": 5,
|
||||
"blockers": [],
|
||||
"nativeStatus": "ok",
|
||||
@@ -515,6 +515,201 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": "ui",
|
||||
"requirement": "web AXIS first screen exposes program, coordinates, status, MDI/switchkins, override, tool, preview, and execution",
|
||||
"status": "pass",
|
||||
"evidence": {
|
||||
"axisMainUi": {
|
||||
"semanticBoundary": "xyzbc_trt_axis_first_screen_program_coordinates_status_mdi_switchkins_override_tool_preview_execution",
|
||||
"firstViewport": "axis-equivalent-cnc-console",
|
||||
"profileId": "xyzbc-trt",
|
||||
"coordinates": "XYZBC",
|
||||
"requiredSections": [
|
||||
{
|
||||
"id": "preview",
|
||||
"section": "preview-toolpath-and-machine-model"
|
||||
},
|
||||
{
|
||||
"id": "dro",
|
||||
"section": "coordinates-dro"
|
||||
},
|
||||
{
|
||||
"id": "gcode",
|
||||
"section": "program-and-mdi"
|
||||
},
|
||||
{
|
||||
"id": "status-sidebar",
|
||||
"section": "status-mode-and-switchkins"
|
||||
},
|
||||
{
|
||||
"id": "info-tabs",
|
||||
"section": "execution-tool-and-runtime-state"
|
||||
},
|
||||
{
|
||||
"id": "override",
|
||||
"section": "feed-rapid-override"
|
||||
},
|
||||
{
|
||||
"id": "spindle-coolant",
|
||||
"section": "spindle-coolant"
|
||||
},
|
||||
{
|
||||
"id": "bottom-controls",
|
||||
"section": "run-jog-home-controls"
|
||||
}
|
||||
],
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "program",
|
||||
"ready": true,
|
||||
"evidence": "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc"
|
||||
},
|
||||
{
|
||||
"id": "coordinates",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"coordinates": "XYZBC",
|
||||
"droAxes": [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"B",
|
||||
"C"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "status",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"taskState": "estop-reset",
|
||||
"mode": "manual",
|
||||
"runState": "idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mdi",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"command": "G0 X0 Y0 Z0",
|
||||
"switchkinsMdi": [
|
||||
"M429",
|
||||
"M428",
|
||||
"M430"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "switchkins",
|
||||
"ready": true,
|
||||
"evidence": [
|
||||
{
|
||||
"value": 0,
|
||||
"label": "identity",
|
||||
"mdiCommand": "M429",
|
||||
"webKinsType": "identity",
|
||||
"remap": {
|
||||
"code": "M429",
|
||||
"modalGroup": 10,
|
||||
"ngc": "429remap",
|
||||
"path": "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/429remap.ngc",
|
||||
"switchkinsType": 0,
|
||||
"analogOutputIndex": 3,
|
||||
"syncInputIndex": 0,
|
||||
"requiresHalPinVars": true
|
||||
},
|
||||
"haluiCommand": "M429",
|
||||
"halPin": "motion.switchkins-type"
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"label": "XYZBC TCP",
|
||||
"mdiCommand": "M428",
|
||||
"webKinsType": "tcp-xyzbc",
|
||||
"remap": {
|
||||
"code": "M428",
|
||||
"modalGroup": 10,
|
||||
"ngc": "428remap",
|
||||
"path": "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc",
|
||||
"switchkinsType": 1,
|
||||
"analogOutputIndex": 3,
|
||||
"syncInputIndex": 0,
|
||||
"requiresHalPinVars": true
|
||||
},
|
||||
"haluiCommand": "M428",
|
||||
"halPin": "motion.switchkins-type"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": "USERK",
|
||||
"mdiCommand": "M430",
|
||||
"webKinsType": "userk",
|
||||
"remap": {
|
||||
"code": "M430",
|
||||
"modalGroup": 10,
|
||||
"ngc": "430remap",
|
||||
"path": "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/430remap.ngc",
|
||||
"switchkinsType": 2,
|
||||
"analogOutputIndex": 3,
|
||||
"syncInputIndex": 0,
|
||||
"requiresHalPinVars": true
|
||||
},
|
||||
"haluiCommand": "M430",
|
||||
"halPin": "motion.switchkins-type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "override",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"feedOverride": 100,
|
||||
"rapidOverride": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tool",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"id": 2,
|
||||
"pocket": 2,
|
||||
"length": 10,
|
||||
"diameter": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "preview",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"samplePeriodMs": 20,
|
||||
"sampleCount": 3074
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "execution",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"samplePeriodMs": 20,
|
||||
"sampleCount": 569
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "axis-buttons",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"canRunAuto": false,
|
||||
"canExecuteMdi": false,
|
||||
"canJog": false,
|
||||
"canHome": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"missingCapabilities": [],
|
||||
"ready": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": "path-preview",
|
||||
"requirement": "native and web preview path sample period is 20ms",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"apiName": "xyzbc-trt-web-opfs-wasm-evidence",
|
||||
"status": "ready-for-wasm-runtime",
|
||||
"collectedAt": "2026-07-02T13:33:38.273Z",
|
||||
"collectedAt": "2026-07-02T13:57:48.903Z",
|
||||
"profile": {
|
||||
"id": "xyzbc-trt",
|
||||
"machineName": "sim-xyzbc-trt-kins (switchkins)",
|
||||
@@ -117308,6 +117308,194 @@
|
||||
],
|
||||
"defaultActivationUsed": true
|
||||
},
|
||||
"axisMainUi": {
|
||||
"semanticBoundary": "xyzbc_trt_axis_first_screen_program_coordinates_status_mdi_switchkins_override_tool_preview_execution",
|
||||
"firstViewport": "axis-equivalent-cnc-console",
|
||||
"profileId": "xyzbc-trt",
|
||||
"coordinates": "XYZBC",
|
||||
"requiredSections": [
|
||||
{
|
||||
"id": "preview",
|
||||
"section": "preview-toolpath-and-machine-model"
|
||||
},
|
||||
{
|
||||
"id": "dro",
|
||||
"section": "coordinates-dro"
|
||||
},
|
||||
{
|
||||
"id": "gcode",
|
||||
"section": "program-and-mdi"
|
||||
},
|
||||
{
|
||||
"id": "status-sidebar",
|
||||
"section": "status-mode-and-switchkins"
|
||||
},
|
||||
{
|
||||
"id": "info-tabs",
|
||||
"section": "execution-tool-and-runtime-state"
|
||||
},
|
||||
{
|
||||
"id": "override",
|
||||
"section": "feed-rapid-override"
|
||||
},
|
||||
{
|
||||
"id": "spindle-coolant",
|
||||
"section": "spindle-coolant"
|
||||
},
|
||||
{
|
||||
"id": "bottom-controls",
|
||||
"section": "run-jog-home-controls"
|
||||
}
|
||||
],
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "program",
|
||||
"ready": true,
|
||||
"evidence": "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc"
|
||||
},
|
||||
{
|
||||
"id": "coordinates",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"coordinates": "XYZBC",
|
||||
"droAxes": [
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"B",
|
||||
"C"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "status",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"taskState": "estop-reset",
|
||||
"mode": "manual",
|
||||
"runState": "idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mdi",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"command": "G0 X0 Y0 Z0",
|
||||
"switchkinsMdi": [
|
||||
"M429",
|
||||
"M428",
|
||||
"M430"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "switchkins",
|
||||
"ready": true,
|
||||
"evidence": [
|
||||
{
|
||||
"value": 0,
|
||||
"label": "identity",
|
||||
"mdiCommand": "M429",
|
||||
"webKinsType": "identity",
|
||||
"remap": {
|
||||
"code": "M429",
|
||||
"modalGroup": 10,
|
||||
"ngc": "429remap",
|
||||
"path": "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/429remap.ngc",
|
||||
"switchkinsType": 0,
|
||||
"analogOutputIndex": 3,
|
||||
"syncInputIndex": 0,
|
||||
"requiresHalPinVars": true
|
||||
},
|
||||
"haluiCommand": "M429",
|
||||
"halPin": "motion.switchkins-type"
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"label": "XYZBC TCP",
|
||||
"mdiCommand": "M428",
|
||||
"webKinsType": "tcp-xyzbc",
|
||||
"remap": {
|
||||
"code": "M428",
|
||||
"modalGroup": 10,
|
||||
"ngc": "428remap",
|
||||
"path": "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc",
|
||||
"switchkinsType": 1,
|
||||
"analogOutputIndex": 3,
|
||||
"syncInputIndex": 0,
|
||||
"requiresHalPinVars": true
|
||||
},
|
||||
"haluiCommand": "M428",
|
||||
"halPin": "motion.switchkins-type"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": "USERK",
|
||||
"mdiCommand": "M430",
|
||||
"webKinsType": "userk",
|
||||
"remap": {
|
||||
"code": "M430",
|
||||
"modalGroup": 10,
|
||||
"ngc": "430remap",
|
||||
"path": "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/430remap.ngc",
|
||||
"switchkinsType": 2,
|
||||
"analogOutputIndex": 3,
|
||||
"syncInputIndex": 0,
|
||||
"requiresHalPinVars": true
|
||||
},
|
||||
"haluiCommand": "M430",
|
||||
"halPin": "motion.switchkins-type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "override",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"feedOverride": 100,
|
||||
"rapidOverride": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tool",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"id": 2,
|
||||
"pocket": 2,
|
||||
"length": 10,
|
||||
"diameter": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "preview",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"samplePeriodMs": 20,
|
||||
"sampleCount": 3074
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "execution",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"samplePeriodMs": 20,
|
||||
"sampleCount": 569
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "axis-buttons",
|
||||
"ready": true,
|
||||
"evidence": {
|
||||
"canRunAuto": false,
|
||||
"canExecuteMdi": false,
|
||||
"canJog": false,
|
||||
"canHome": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"missingCapabilities": [],
|
||||
"ready": true
|
||||
},
|
||||
"startupSequence": [
|
||||
".desktop",
|
||||
"rip-environment",
|
||||
@@ -117649,6 +117837,7 @@
|
||||
],
|
||||
"uiEquivalence": {
|
||||
"firstViewport": "axis-equivalent-cnc-console",
|
||||
"semanticBoundary": "xyzbc_trt_axis_first_screen_program_coordinates_status_mdi_switchkins_override_tool_preview_execution",
|
||||
"regions": [
|
||||
"program",
|
||||
"dro",
|
||||
@@ -117658,6 +117847,18 @@
|
||||
"tool",
|
||||
"preview-execution"
|
||||
],
|
||||
"axisMainUiCapabilityIds": [
|
||||
"program",
|
||||
"coordinates",
|
||||
"status",
|
||||
"mdi",
|
||||
"switchkins",
|
||||
"override",
|
||||
"tool",
|
||||
"preview",
|
||||
"execution",
|
||||
"axis-buttons"
|
||||
],
|
||||
"activeProgram": "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc",
|
||||
"selectedGcodeSourceRel": "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc",
|
||||
"pyvcpPanelSchema": "xyzbc-trt-switchkins-pyvcp",
|
||||
@@ -117911,6 +118112,7 @@
|
||||
"axisJointLimitsCovered": true,
|
||||
"previewPathAvailable": true,
|
||||
"executionPathAvailable": true,
|
||||
"axisMainUiEquivalent": true,
|
||||
"toolTableToToolOffsetClosed": true,
|
||||
"wasmArtifactsReady": true
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user