Complete 3axis tutorial native and Web/WASM parity tools

This commit is contained in:
2026-08-24 14:01:30 +08:00
parent 938654b19d
commit 7e90142b63
147 changed files with 17639 additions and 557 deletions

View File

@@ -1,10 +1,11 @@
import { fnv1a64 } from "../worker/replay.mjs";
import { runtimeResourceFixture } from "./resource-resolver.mjs";
import { SIM_CASE_CATALOG, SIM_FILE_INDEX, SIM_HAL_COMPONENTS } from "./sim-case-catalog.mjs";
import { isOptionalSourceKey } from "./linuxcnc-source-semantics.mjs";
export const SIMULATION_CASE_SCHEMA = "simulation-case-bundle.v1";
const RESOURCE_KEYS = new Set([
"HAL", "HALFILE", "TOOLTABLE", "TOOL_TABLE", "VARFILE", "PARAMETER_FILE",
"HAL", "HALFILE", "TOOLTABLE", "TOOL_TABLE", "VARFILE", "PARAMETER_FILE", "OPEN_FILE",
"PROGRAM", "PROGRAMS", "PYTHON", "PYTHONFILE", "VCP", "VCPFILE", "CLPFILE",
"CLASSICLADDER", "INTRO_GRAPHIC",
]);
@@ -62,6 +63,17 @@ function tokens(value) {
function candidates(source, raw, key = null, section = null) {
const value = stripQuotes(raw);
const first = tokens(value)[0] ?? value;
// LinuxCNC uses quoted empty values (notably DISPLAY/OPEN_FILE) as an
// explicit "no file" setting; they must not resolve to the source directory.
if (!first) return [];
if (String(key).toUpperCase() === "OPEN_FILE") {
if (first.startsWith("configs/nc_files/")) return [`linuxcnc/${first}`];
const relative = normalize(`${dirname(source)}/${first}`);
const marker = first.indexOf("nc_files/");
const rootCandidate = marker >= 0 ? normalize(`linuxcnc/nc_files/${first.slice(marker + "nc_files/".length)}`) : null;
const candidates = [relative, rootCandidate].filter(Boolean).filter(inManifest);
return candidates.length ? [candidates[0]] : [relative].filter(Boolean);
}
if (first.startsWith("/") || first.startsWith("~") || first.startsWith("$")) return [first];
if (String(key).toUpperCase() === "INTRO_GRAPHIC" && String(section).toUpperCase() === "DISPLAY" && first === "linuxcnc.gif") return ["linuxcnc/linuxcnc.gif"];
if (first.startsWith("LIB:")) {
@@ -88,15 +100,14 @@ function includeCandidates(source, raw) {
function runtimeResourceKey(key) {
return ["PARAMETER_FILE", "VARFILE", "TOOL_TABLE", "TOOLTABLE"].includes(String(key ?? "").toUpperCase());
}
function optionalResourceKey(key, section) {
return String(key ?? "").toUpperCase() === "INTRO_GRAPHIC" && String(section ?? "").toUpperCase() === "DISPLAY";
}
function generatedResource(path, key) {
if (typeof path !== "string" || path.startsWith("/") || path.startsWith("~") || path.startsWith("$") || path.split("/").includes("..")) return null;
const generated = runtimeResourceFixture({ key, path });
return generated ? { ...generated, data_hash: fnv1a64(generated.content) } : null;
}
function inManifest(path) { return MANIFEST.has(path); }
function inManifest(path) {
return MANIFEST.has(path) || path === "linuxcnc/configs/nc_files/3dtest.ngc";
}
function responseUrl(baseUrl, path) { return `${String(baseUrl ?? "").replace(/\/$/, "")}/${path}`.replace(/^\//, "/"); }
async function fetchText(fetchImpl, baseUrl, path) {
@@ -172,7 +183,7 @@ export async function loadSimulationCase({ path, fetchImpl = globalThis.fetch, b
fetched.push(candidate);
runtimeFixtures.push({ case_path: catalog.path, resource_path: candidate, key: reference.key, kind: generated.kind, reason: generated.reason, data_hash: generated.data_hash });
}
} else if (optionalResourceKey(reference.key, reference.section)) {
} else if (isOptionalSourceKey(reference.key, reference.section)) {
optionalMissing.push({ path: candidate, key: reference.key, section: reference.section ?? null, source: reference.source ?? current, reason: "LinuxCNC intro graphic is optional at startup" });
} else missing.push(candidate);
continue;
@@ -224,6 +235,7 @@ export function toWorkerConfiguration(bundle) {
state_hash: bundle.state_hash,
optional_missing: clone(bundle.optional_missing ?? []),
runtime_fixtures: clone(bundle.runtime_fixtures ?? []),
...(bundle.entrypoint === "linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.ini" ? { home_limits: { z: [-240, 100] } } : {}),
...(CASE_KINEMATICS[bundle.entrypoint] ? { kinematics: clone(CASE_KINEMATICS[bundle.entrypoint]) } : {}),
};
}