完善 xyzbc-trt OPFS 与 AXIS 首屏等效

This commit is contained in:
mes123456
2026-07-02 10:10:54 -04:00
parent 1c8908e3ab
commit 52e54f8089
16 changed files with 925 additions and 28 deletions

View File

@@ -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",

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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),

View File

@@ -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) {

View File

@@ -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 [