Make machine configuration authoritative and restore native checkpoints exactly
This commit is contained in:
35
web/workspace/machine-catalog.json
Normal file
35
web/workspace/machine-catalog.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"default_machine": "xyzac-trt",
|
||||
"machines": [
|
||||
{
|
||||
"id": "xyzac-trt",
|
||||
"coordinates": "XYZAC",
|
||||
"kinematics_type": 2,
|
||||
"entrypoint": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
|
||||
"program": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzac.ngc",
|
||||
"switchkins_program": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins.ngc",
|
||||
"source_path": "linuxcnc/src/hal/user_comps/vismach/xyzac-trt-gui.py",
|
||||
"model": "trt-moving-knee",
|
||||
"subroutines": [
|
||||
"xyzac_switchkins_sub",
|
||||
"helix_ac"
|
||||
],
|
||||
"runtime_fixture": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini"
|
||||
},
|
||||
{
|
||||
"id": "xyzbc-trt",
|
||||
"coordinates": "XYZBC",
|
||||
"kinematics_type": 3,
|
||||
"entrypoint": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini",
|
||||
"program": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzbc.ngc",
|
||||
"switchkins_program": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc",
|
||||
"source_path": "linuxcnc/src/hal/user_comps/vismach/xyzbc-trt-gui.py",
|
||||
"model": "trt-moving-head",
|
||||
"subroutines": [
|
||||
"xyzbc_switchkins_sub",
|
||||
"helix_bc"
|
||||
],
|
||||
"runtime_fixture": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,25 +1,12 @@
|
||||
const TRT_ROOT = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting";
|
||||
import catalog from "./machine-catalog.json" with { type: "json" };
|
||||
|
||||
function trt(id, coordinates, type, offsets, model) {
|
||||
const stem = coordinates.toLowerCase();
|
||||
return Object.freeze({
|
||||
id, label: `${coordinates} TRT`, coordinates, axes: [...stem], joint_count: coordinates.length,
|
||||
entrypoint: `${TRT_ROOT}/${id}.ini`, program: `${TRT_ROOT}/demos/boat-${stem}.ngc`,
|
||||
switchkins_program: `${TRT_ROOT}/demos/${stem}_switchkins.ngc`,
|
||||
kinematics_type: type, runtime: "trt", model,
|
||||
source_path: `linuxcnc/src/hal/user_comps/vismach/${id}-gui.py`,
|
||||
subroutines: [`${stem}_switchkins_sub`, `helix_${stem.slice(3)}`],
|
||||
offsets,
|
||||
joint_limits: { x: [-200, 200], y: [-100, 100], z: [-120, 120], [stem[3]]: [-100, 50], c: [-36000, 36000] },
|
||||
axis_limits: { x: [-200, 200], y: [-100, 100], z: [-120, 120], [stem[3]]: coordinates === "XYZAC" ? [-100, 50] : [-36000, 36000], c: [-36000, 36000] },
|
||||
});
|
||||
}
|
||||
|
||||
export const MACHINE_DEFINITIONS = Object.freeze([
|
||||
trt("xyzac-trt", "XYZAC", 2, { x_rot_point: 0, y_offset: 20, z_offset: 10, tool_offset: 0 }, "trt-moving-knee"),
|
||||
trt("xyzbc-trt", "XYZBC", 3, { x_rot_point: 0, y_rot_point: 0, z_rot_point: 0, x_offset: -20, y_offset: 0, z_offset: -15, tool_offset: 0 }, "trt-moving-head"),
|
||||
]);
|
||||
export const DEFAULT_MACHINE = MACHINE_DEFINITIONS[0];
|
||||
export const MACHINE_DEFINITIONS = Object.freeze(catalog.machines.map((entry) => {
|
||||
if (!entry.id || !entry.entrypoint || !entry.program || !entry.source_path || !["XYZAC", "XYZBC"].includes(entry.coordinates)) throw new Error("machine_catalog_invalid");
|
||||
return Object.freeze({ ...entry, label: `${entry.coordinates} TRT`, axes: [...entry.coordinates.toLowerCase()], joint_count: entry.coordinates.length, runtime: "trt" });
|
||||
}));
|
||||
if (new Set(MACHINE_DEFINITIONS.map((entry) => entry.id)).size !== MACHINE_DEFINITIONS.length) throw new Error("machine_catalog_duplicate");
|
||||
export const DEFAULT_MACHINE = MACHINE_DEFINITIONS.find((entry) => entry.id === catalog.default_machine);
|
||||
if (!DEFAULT_MACHINE) throw new Error("machine_catalog_default_missing");
|
||||
export function machineDefinition(value) {
|
||||
return MACHINE_DEFINITIONS.find((entry) => [entry.id, entry.entrypoint, entry.coordinates].includes(value)) ?? null;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ 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";
|
||||
import { MACHINE_DEFINITIONS, machineDefinition } from "./machine-definitions.mjs";
|
||||
import { readTrtGeometry } from "./trt-geometry.mjs";
|
||||
import resources from "./simulation-resources.json" with { type: "json" };
|
||||
import { parseIni } from "./ini-parser.mjs";
|
||||
import { WorkspaceRuntime } from "./runtime.mjs";
|
||||
import { readTrtConfiguration } from "./trt-configuration.mjs";
|
||||
|
||||
export const SIMULATION_CASE_SCHEMA = "simulation-case-bundle.v1";
|
||||
const RESOURCE_KEYS = new Set([
|
||||
@@ -12,53 +15,9 @@ const RESOURCE_KEYS = new Set([
|
||||
"PYVCP", "NGCGUI_SUBFILE", "INTRO_GRAPHIC", "SUBROUTINE_PATH", "REMAP",
|
||||
]);
|
||||
const MANIFEST = new Set(SIM_FILE_INDEX);
|
||||
const PRELOADED_HAL_COMPONENTS = Object.freeze(["corexykins", "lineardeltakins", "motmod", "pid", "rotarydeltakins", "stepgen", "trivkins", "xyzac-trt-kins", "xyzbc-trt-kins", "tpmod", "homemod"]);
|
||||
const CASE_KINEMATICS = Object.freeze({
|
||||
"linuxcnc/configs/sim/axis/corexy/corexy_by_kins.ini": Object.freeze({ type: "corexy", path_mode: "kins" }),
|
||||
"linuxcnc/configs/sim/axis/corexy/corexy_by_hal.ini": Object.freeze({ type: "corexy", path_mode: "hal" }),
|
||||
"linuxcnc/configs/sim/axis/ldelta.ini": Object.freeze({ type: "lineardelta", path_mode: "kins" }),
|
||||
"linuxcnc/configs/sim/axis/rdelta.ini": Object.freeze({ type: "rotarydelta", path_mode: "kins" }),
|
||||
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini": Object.freeze({ type: "xyzac-trt", path_mode: "wasm", coordinates: "XYZAC", joint_count: 5, max_linear_velocity: 35, axis_linear_velocity_limit: 20, max_acceleration: 300, max_jerk: 1000000000, servo_cycle_time: 0.001, task_cycle_time: 0.01, cycle_time: 0.001, switchkins: { 0: "identity", 1: "xyzac", 2: "userk" } }),
|
||||
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini": Object.freeze({ type: "xyzbc-trt", path_mode: "wasm", coordinates: "XYZBC", joint_count: 5, max_linear_velocity: 35, axis_linear_velocity_limit: 20, axis_angular_velocity_limit: 30, max_acceleration: 300, max_jerk: 1000000000, servo_cycle_time: 0.001, task_cycle_time: 0.01, cycle_time: 0.001, switchkins: { 0: "identity", 1: "xyzbc", 2: "userk" } }),
|
||||
});
|
||||
const RUNTIME_FIXTURES = Object.freeze({
|
||||
"linuxcnc/configs/sim/axis/minimal_xyz.ini": Object.freeze({
|
||||
generated_hal: "linuxcnc/configs/sim/axis/minimal_xyz_cmds.hal",
|
||||
replace: "HALFILE = LIB:basic_sim.tcl",
|
||||
with: "HALFILE = minimal_xyz_cmds.hal",
|
||||
reason: "LinuxCNC basic_sim.tcl generated HAL command fixture",
|
||||
}),
|
||||
"linuxcnc/configs/sim/axis/ja_tests/xz/xz.ini": Object.freeze({
|
||||
generated_hal: "linuxcnc/configs/sim/axis/ja_tests/xz/xz_cmds.hal",
|
||||
replace: "HALFILE = LIB:basic_sim.tcl",
|
||||
with: "HALFILE = xz_cmds.hal",
|
||||
reason: "LinuxCNC basic_sim.tcl generated XZ HAL command fixture",
|
||||
}),
|
||||
"linuxcnc/configs/sim/axis/ja_tests/xz/xz_both.ini": Object.freeze({
|
||||
generated_hal: "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_cmds.hal",
|
||||
replace: "HALFILE = LIB:basic_sim.tcl",
|
||||
with: "HALFILE = xz_both_cmds.hal",
|
||||
reason: "LinuxCNC basic_sim.tcl generated XZ BOTH HAL command fixture",
|
||||
}),
|
||||
"linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_auto.ini": Object.freeze({
|
||||
generated_hal: "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_auto_cmds.hal",
|
||||
replace: "HALFILE = LIB:basic_sim.tcl",
|
||||
with: "HALFILE = xz_both_auto_cmds.hal",
|
||||
reason: "LinuxCNC basic_sim.tcl generated XZ BOTH AUTO HAL command fixture",
|
||||
}),
|
||||
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini": Object.freeze({
|
||||
generated_hal: "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt-headless_cmds.hal",
|
||||
replace: "HALFILE = LIB:basic_sim.tcl",
|
||||
with: "HALFILE = xyzac-trt-headless_cmds.hal",
|
||||
reason: "LinuxCNC basic_sim.tcl generated XYZAC HAL command fixture",
|
||||
}),
|
||||
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini": Object.freeze({
|
||||
generated_hal: "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt_cmds.hal",
|
||||
replace: "HALFILE = LIB:basic_sim.tcl",
|
||||
with: "HALFILE = xyzbc-trt_cmds.hal",
|
||||
reason: "LinuxCNC basic_sim.tcl generated XYZBC HAL command fixture",
|
||||
}),
|
||||
});
|
||||
const PRELOADED_HAL_COMPONENTS = Object.freeze(resources.preloaded_hal_components);
|
||||
const CASE_KINEMATICS = Object.freeze(resources.kinematics);
|
||||
const RUNTIME_FIXTURES = Object.freeze(resources.runtime_fixtures);
|
||||
|
||||
function clone(value) { return structuredClone(value); }
|
||||
function text(value) { return typeof value === "string" && value.trim() ? value.trim() : null; }
|
||||
@@ -180,7 +139,8 @@ function resourceReferences(source, content) {
|
||||
}
|
||||
|
||||
export function findSimulationCase(path) {
|
||||
return SIM_CASE_CATALOG.find((entry) => entry.path === path) ?? null;
|
||||
const registered = machineDefinition(path);
|
||||
return SIM_CASE_CATALOG.find((entry) => entry.path === path) ?? (registered ? { path: registered.entrypoint, coordinates: registered.coordinates, label: registered.label, status: "registered" } : null);
|
||||
}
|
||||
|
||||
export async function loadSimulationCase({ path, fetchImpl = globalThis.fetch, baseUrl = "" } = {}) {
|
||||
@@ -243,7 +203,7 @@ export async function loadSimulationCase({ path, fetchImpl = globalThis.fetch, b
|
||||
}
|
||||
}
|
||||
const sourceIni = files[catalog.path];
|
||||
const fixture = RUNTIME_FIXTURES[catalog.path];
|
||||
const fixture = RUNTIME_FIXTURES[catalog.path] ?? RUNTIME_FIXTURES[machine?.runtime_fixture];
|
||||
if (fixture) {
|
||||
if (!inManifest(fixture.generated_hal)) missing.push(fixture.generated_hal);
|
||||
else if (!files[fixture.generated_hal]) {
|
||||
@@ -280,7 +240,23 @@ export async function loadSimulationCase({ path, fetchImpl = globalThis.fetch, b
|
||||
export function toWorkerConfiguration(bundle) {
|
||||
if (!bundle || bundle.schema !== SIMULATION_CASE_SCHEMA) throw new Error("simulation_case_bundle_invalid");
|
||||
const kinematics = CASE_KINEMATICS[bundle.entrypoint] ? clone(CASE_KINEMATICS[bundle.entrypoint]) : null;
|
||||
if (machineDefinition(bundle.entrypoint)) kinematics.offsets = readTrtGeometry(bundle).offsets;
|
||||
const trt = machineDefinition(bundle.entrypoint) ? readTrtConfiguration(bundle) : null;
|
||||
let homeLimits = null;
|
||||
if (!trt) {
|
||||
const parsed = parseIni({ workspace: new WorkspaceRuntime({ files: bundle.files }), entrypoint: bundle.entrypoint });
|
||||
const coordinates = parsed.sections.TRAJ?.COORDINATES?.replace(/\s/g, "").toUpperCase();
|
||||
if (coordinates && /^[XYZABCUVW]+$/.test(coordinates)) {
|
||||
const limits = {};
|
||||
for (const [index, axis] of [...coordinates].entries()) {
|
||||
const section = parsed.sections[`JOINT_${index}`] ?? parsed.sections[`AXIS_${axis}`];
|
||||
if (!section || section.MIN_LIMIT === undefined || section.MAX_LIMIT === undefined) continue;
|
||||
const range = [Number(section.MIN_LIMIT), Number(section.MAX_LIMIT)];
|
||||
if (!range.every(Number.isFinite) || range[0] >= range[1]) throw new Error(`configuration_joint_limits:${index}`);
|
||||
limits[axis.toLowerCase()] = range;
|
||||
}
|
||||
if (Object.keys(limits).length) homeLimits = { coordinates, joint_count: coordinates.length, ...limits };
|
||||
}
|
||||
}
|
||||
return {
|
||||
entrypoint: bundle.entrypoint,
|
||||
files: clone(bundle.files),
|
||||
@@ -288,9 +264,8 @@ 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] } } : {}),
|
||||
...(machineDefinition(bundle.entrypoint) ? { home_limits: { joint_count: 5, kinematics_type: machineDefinition(bundle.entrypoint).kinematics_type, coordinates: machineDefinition(bundle.entrypoint).coordinates, ...machineDefinition(bundle.entrypoint).joint_limits } } : {}),
|
||||
...(kinematics ? { kinematics } : {}),
|
||||
...(homeLimits ? { home_limits: homeLimits } : {}),
|
||||
...(trt ?? (kinematics ? { kinematics } : {})),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
71
web/workspace/simulation-resources.json
Normal file
71
web/workspace/simulation-resources.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"kinematics": {
|
||||
"linuxcnc/configs/sim/axis/corexy/corexy_by_kins.ini": {
|
||||
"type": "corexy",
|
||||
"path_mode": "kins"
|
||||
},
|
||||
"linuxcnc/configs/sim/axis/corexy/corexy_by_hal.ini": {
|
||||
"type": "corexy",
|
||||
"path_mode": "hal"
|
||||
},
|
||||
"linuxcnc/configs/sim/axis/ldelta.ini": {
|
||||
"type": "lineardelta",
|
||||
"path_mode": "kins"
|
||||
},
|
||||
"linuxcnc/configs/sim/axis/rdelta.ini": {
|
||||
"type": "rotarydelta",
|
||||
"path_mode": "kins"
|
||||
}
|
||||
},
|
||||
"runtime_fixtures": {
|
||||
"linuxcnc/configs/sim/axis/minimal_xyz.ini": {
|
||||
"generated_hal": "linuxcnc/configs/sim/axis/minimal_xyz_cmds.hal",
|
||||
"replace": "HALFILE = LIB:basic_sim.tcl",
|
||||
"with": "HALFILE = minimal_xyz_cmds.hal",
|
||||
"reason": "LinuxCNC basic_sim.tcl generated HAL command fixture"
|
||||
},
|
||||
"linuxcnc/configs/sim/axis/ja_tests/xz/xz.ini": {
|
||||
"generated_hal": "linuxcnc/configs/sim/axis/ja_tests/xz/xz_cmds.hal",
|
||||
"replace": "HALFILE = LIB:basic_sim.tcl",
|
||||
"with": "HALFILE = xz_cmds.hal",
|
||||
"reason": "LinuxCNC basic_sim.tcl generated XZ HAL command fixture"
|
||||
},
|
||||
"linuxcnc/configs/sim/axis/ja_tests/xz/xz_both.ini": {
|
||||
"generated_hal": "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_cmds.hal",
|
||||
"replace": "HALFILE = LIB:basic_sim.tcl",
|
||||
"with": "HALFILE = xz_both_cmds.hal",
|
||||
"reason": "LinuxCNC basic_sim.tcl generated XZ BOTH HAL command fixture"
|
||||
},
|
||||
"linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_auto.ini": {
|
||||
"generated_hal": "linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_auto_cmds.hal",
|
||||
"replace": "HALFILE = LIB:basic_sim.tcl",
|
||||
"with": "HALFILE = xz_both_auto_cmds.hal",
|
||||
"reason": "LinuxCNC basic_sim.tcl generated XZ BOTH AUTO HAL command fixture"
|
||||
},
|
||||
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini": {
|
||||
"generated_hal": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt-headless_cmds.hal",
|
||||
"replace": "HALFILE = LIB:basic_sim.tcl",
|
||||
"with": "HALFILE = xyzac-trt-headless_cmds.hal",
|
||||
"reason": "LinuxCNC basic_sim.tcl generated XYZAC HAL command fixture"
|
||||
},
|
||||
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini": {
|
||||
"generated_hal": "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt_cmds.hal",
|
||||
"replace": "HALFILE = LIB:basic_sim.tcl",
|
||||
"with": "HALFILE = xyzbc-trt_cmds.hal",
|
||||
"reason": "LinuxCNC basic_sim.tcl generated XYZBC HAL command fixture"
|
||||
}
|
||||
},
|
||||
"preloaded_hal_components": [
|
||||
"corexykins",
|
||||
"lineardeltakins",
|
||||
"motmod",
|
||||
"pid",
|
||||
"rotarydeltakins",
|
||||
"stepgen",
|
||||
"trivkins",
|
||||
"xyzac-trt-kins",
|
||||
"xyzbc-trt-kins",
|
||||
"tpmod",
|
||||
"homemod"
|
||||
]
|
||||
}
|
||||
74
web/workspace/trt-configuration.mjs
Normal file
74
web/workspace/trt-configuration.mjs
Normal file
@@ -0,0 +1,74 @@
|
||||
import { parseIni } from "./ini-parser.mjs";
|
||||
import { WorkspaceRuntime } from "./runtime.mjs";
|
||||
import { machineDefinition } from "./machine-definitions.mjs";
|
||||
import { readTrtGeometry } from "./trt-geometry.mjs";
|
||||
import { WEB_SETTINGS } from "./web-settings.mjs";
|
||||
|
||||
const NUMBER = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
|
||||
// ABI capacity constraints from sim_io.h and sim_device_io.h, not machine defaults.
|
||||
const IO_CAPACITY = Object.freeze({ spindle_count: 8, tool_capacity: 1001, encoder_count: 8 });
|
||||
export function readTrtConfiguration(configuration) {
|
||||
const machine = machineDefinition(configuration.entrypoint);
|
||||
if (!machine) throw new Error("trt_configuration_machine_missing");
|
||||
const parsed = parseIni({ workspace: new WorkspaceRuntime({ files: configuration.files }), entrypoint: configuration.entrypoint });
|
||||
const sections = parsed.sections;
|
||||
const number = (section, key, { positive = false, fallback } = {}) => {
|
||||
const raw = sections[section]?.[key];
|
||||
if (raw === undefined && fallback !== undefined) return fallback;
|
||||
if (typeof raw !== "string" || !NUMBER.test(raw) || !Number.isFinite(Number(raw)) || positive && Number(raw) <= 0) throw new Error(`trt_configuration_number:[${section}]${key}`);
|
||||
return Number(raw);
|
||||
};
|
||||
const geometry = readTrtGeometry(configuration);
|
||||
const coordinates = sections.TRAJ.COORDINATES.replace(/\s/g, "").toUpperCase();
|
||||
const axes = [...coordinates.toLowerCase()];
|
||||
const jointCount = number("KINS", "JOINTS", { positive: true });
|
||||
if (jointCount !== axes.length) throw new Error("trt_configuration_joint_count");
|
||||
if (sections.TRAJ.LINEAR_UNITS !== "mm" || sections.TRAJ.ANGULAR_UNITS !== "deg") throw new Error("trt_configuration_units_unsupported");
|
||||
if (!/\bsparm=identityfirst\b/.test(sections.KINS.KINEMATICS)) throw new Error("trt_configuration_requires_identityfirst");
|
||||
const limits = (section) => {
|
||||
const range = [number(section, "MIN_LIMIT"), number(section, "MAX_LIMIT")];
|
||||
if (range[0] >= range[1]) throw new Error(`trt_configuration_limits:${section}`);
|
||||
return range;
|
||||
};
|
||||
const jointLimits = {}, axisLimits = {}, velocity = {}, acceleration = {};
|
||||
for (const [joint, axis] of axes.entries()) {
|
||||
jointLimits[axis] = limits(`JOINT_${joint}`);
|
||||
axisLimits[axis] = limits(`AXIS_${axis.toUpperCase()}`);
|
||||
velocity[axis] = number(`AXIS_${axis.toUpperCase()}`, "MAX_VELOCITY", { positive: true });
|
||||
acceleration[axis] = number(`AXIS_${axis.toUpperCase()}`, "MAX_ACCELERATION", { positive: true });
|
||||
number(`JOINT_${joint}`, "MAX_VELOCITY", { positive: true });
|
||||
number(`JOINT_${joint}`, "MAX_ACCELERATION", { positive: true });
|
||||
for (const key of ["HOME", "HOME_OFFSET", "HOME_FINAL_VEL", "HOME_SEARCH_VEL", "HOME_LATCH_VEL", "HOME_SEQUENCE"]) if (sections[`JOINT_${joint}`][key] !== undefined) number(`JOINT_${joint}`, key);
|
||||
}
|
||||
const servo = number("EMCMOT", "SERVO_PERIOD", { positive: true }) / 1e9;
|
||||
const task = number("TASK", "CYCLE_TIME", { positive: true });
|
||||
const ticks = task / servo;
|
||||
if (!Number.isSafeInteger(Math.round(ticks)) || ticks < 1 || Math.abs(ticks - Math.round(ticks)) > 1e-9) throw new Error("trt_configuration_task_servo_ratio");
|
||||
if (servo > 60 || task > 10) throw new Error("trt_configuration_cycle_capacity");
|
||||
const maxFeed = number("DISPLAY", "MAX_FEED_OVERRIDE", { positive: true });
|
||||
if (maxFeed < 1) throw new Error("trt_configuration_feed_override");
|
||||
const blendKeys = { enable: "ARC_BLEND_ENABLE", fallback_enable: "ARC_BLEND_FALLBACK_ENABLE", optimization_depth: "ARC_BLEND_OPTIMIZATION_DEPTH", gap_cycles: "ARC_BLEND_GAP_CYCLES", ramp_frequency: "ARC_BLEND_RAMP_FREQ", tangent_kink_ratio: "ARC_BLEND_TANGENT_KINK_RATIO" };
|
||||
const blend = Object.fromEntries(Object.entries(blendKeys).map(([key, iniKey]) => [key, number("TRAJ", iniKey, { fallback: WEB_SETTINGS.runtime.planner.blend[key] })]));
|
||||
if (![0, 1].includes(blend.enable) || ![0, 1].includes(blend.fallback_enable) || !Number.isSafeInteger(blend.optimization_depth) || blend.optimization_depth < 1 || !Number.isSafeInteger(blend.gap_cycles) || blend.gap_cycles < 0 || blend.ramp_frequency <= 0 || blend.tangent_kink_ratio <= 0) throw new Error("trt_configuration_blend");
|
||||
const io = { ...Object.fromEntries(Object.entries(WEB_SETTINGS.runtime.io).filter(([key]) => key !== "cycle_time").map(([key, fallback]) => [key, number("WEB_RUNTIME", key.toUpperCase(), { positive: key !== "spindle_tolerance", fallback })])), cycle_time: task };
|
||||
if (io.spindle_tolerance < 0) throw new Error("trt_configuration_spindle_tolerance");
|
||||
for (const [key, maximum] of Object.entries(IO_CAPACITY)) if (!Number.isSafeInteger(io[key]) || io[key] < 1 || io[key] > maximum) throw new Error(`trt_configuration_io_capacity:${key}`);
|
||||
const defaultAcceleration = number("TRAJ", "DEFAULT_LINEAR_ACCELERATION", { positive: true });
|
||||
const maxAcceleration = number("TRAJ", "MAX_LINEAR_ACCELERATION", { positive: true });
|
||||
return {
|
||||
home_limits: { joint_count: jointCount, kinematics_type: machine.kinematics_type, coordinates, ...jointLimits },
|
||||
kinematics: {
|
||||
type: machine.id, path_mode: "wasm", coordinates, joint_count: jointCount, offsets: geometry.offsets,
|
||||
ini_hash: parsed.state_hash,
|
||||
servo_cycle_time: servo, cycle_time: servo, task_cycle_time: task,
|
||||
max_linear_velocity: number("TRAJ", "MAX_LINEAR_VELOCITY", { positive: true }),
|
||||
max_acceleration: Math.min(defaultAcceleration, maxAcceleration),
|
||||
max_linear_acceleration: maxAcceleration,
|
||||
axis_limits: axisLimits, axis_velocity_limits: velocity, axis_acceleration_limits: acceleration,
|
||||
max_feed_override: maxFeed,
|
||||
blend,
|
||||
io,
|
||||
switchkins: { 0: "identity", 1: coordinates.toLowerCase(), 2: "userk" },
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export function readTrtGeometry({ entrypoint, files }) {
|
||||
const machine = machineDefinition(entrypoint);
|
||||
if (!machine) fail("trt_geometry_machine", entrypoint);
|
||||
const parsed = parseIni({ workspace: new WorkspaceRuntime({ files }), entrypoint });
|
||||
if (parsed.sections.TRAJ?.COORDINATES?.toUpperCase() !== machine.coordinates || parsed.sections.KINS?.KINEMATICS?.split(/\s+/)[0] !== `${machine.id}-kins`) fail("trt_geometry_machine_mismatch", entrypoint);
|
||||
if (parsed.sections.TRAJ?.COORDINATES?.replace(/\s/g, "").toUpperCase() !== machine.coordinates || parsed.sections.KINS?.KINEMATICS?.split(/\s+/)[0] !== `${machine.id}-kins`) fail("trt_geometry_machine_mismatch", entrypoint);
|
||||
const commands = parsed.entries.filter((e) => e.section.toUpperCase() === "HAL" && e.key.toUpperCase() === "HALCMD")
|
||||
.map((entry) => ({ entry, record: parseHalLine(entry.value, entry.line, parsed.sections) }));
|
||||
const unsupported = machine.coordinates === "XYZAC" ? "x_offset" : "y_offset";
|
||||
|
||||
180
web/workspace/web-settings.json
Normal file
180
web/workspace/web-settings.json
Normal file
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"assets": {
|
||||
"axis_images": "linuxcnc/share/axis/images"
|
||||
},
|
||||
"ui": {
|
||||
"poll_ms": 50,
|
||||
"jog_cycles": 1,
|
||||
"run_cycles": 5,
|
||||
"preview_progress_ms": 100,
|
||||
"jog_increments": [
|
||||
"0.1000",
|
||||
"0.0100",
|
||||
"0.0010",
|
||||
"0.0001"
|
||||
],
|
||||
"initial_view": "p",
|
||||
"zoom": 1,
|
||||
"zoom_min": 0.5,
|
||||
"zoom_max": 2,
|
||||
"zoom_step": 0.2,
|
||||
"layers": {
|
||||
"grid": true,
|
||||
"tool": true,
|
||||
"path": true,
|
||||
"live": true,
|
||||
"limits": false,
|
||||
"velocity": false,
|
||||
"offsets": false,
|
||||
"extents": false
|
||||
},
|
||||
"motion_scale_min": 0.06,
|
||||
"override_debounce_ms": 80
|
||||
},
|
||||
"runtime": {
|
||||
"planner": {
|
||||
"cycle_time": 0.001,
|
||||
"velocity_limit": 35,
|
||||
"acceleration_limit": 300,
|
||||
"max_feed_override": 2,
|
||||
"compatibility_jerk_limit": 1000000000,
|
||||
"blend": {
|
||||
"enable": 1,
|
||||
"fallback_enable": 0,
|
||||
"optimization_depth": 50,
|
||||
"gap_cycles": 4,
|
||||
"ramp_frequency": 100,
|
||||
"tangent_kink_ratio": 0.1
|
||||
}
|
||||
},
|
||||
"io": {
|
||||
"spindle_count": 2,
|
||||
"tool_capacity": 1001,
|
||||
"cycle_time": 0.01,
|
||||
"spindle_acceleration": 600,
|
||||
"spindle_tolerance": 5,
|
||||
"encoder_count": 2
|
||||
},
|
||||
"compatibility": {
|
||||
"step_fraction": 0.25,
|
||||
"max_position": 1000000000.0,
|
||||
"max_speed": 1000000.0,
|
||||
"cycle_time": 0.001,
|
||||
"tripod_base": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
10,
|
||||
0
|
||||
],
|
||||
[
|
||||
5,
|
||||
7.071
|
||||
]
|
||||
],
|
||||
"linear_delta": {
|
||||
"radius": 130.25,
|
||||
"rod": 269
|
||||
},
|
||||
"rotary_delta": {
|
||||
"platform": 10,
|
||||
"thigh": 10,
|
||||
"shin": 14,
|
||||
"foot": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewer": {
|
||||
"mm_scale": 0.01,
|
||||
"pixel_ratio_limit": 2,
|
||||
"camera": {
|
||||
"fov": 36,
|
||||
"near": 0.02,
|
||||
"far": 150,
|
||||
"min_distance": 0.25,
|
||||
"max_distance": 65,
|
||||
"damping": 0.12,
|
||||
"fit_margin": 1.12,
|
||||
"pan_fraction": 0.035,
|
||||
"zoom_in": 0.8,
|
||||
"zoom_out": 1.25,
|
||||
"rotate_degrees": 15
|
||||
},
|
||||
"colors": {
|
||||
"background": "#252b2c",
|
||||
"path": "#ffff00",
|
||||
"live": "#ff9a55",
|
||||
"rapid": "#5c9d9a",
|
||||
"limits": "#dd7566",
|
||||
"extents": "#e6c56b"
|
||||
},
|
||||
"models": {
|
||||
"XYZAC": {
|
||||
"datum_z_mm": 200
|
||||
},
|
||||
"XYZBC": {
|
||||
"datum_z_mm": 150
|
||||
}
|
||||
},
|
||||
"materials": {
|
||||
"enamel": {
|
||||
"color": "#d9e1e1",
|
||||
"roughness": 0.32,
|
||||
"metalness": 0.25
|
||||
},
|
||||
"teal": {
|
||||
"color": "#167f79",
|
||||
"roughness": 0.3,
|
||||
"metalness": 0.3
|
||||
},
|
||||
"dark": {
|
||||
"color": "#303638",
|
||||
"roughness": 0.42,
|
||||
"metalness": 0.35
|
||||
},
|
||||
"steel": {
|
||||
"color": "#b2bdc4",
|
||||
"roughness": 0.27,
|
||||
"metalness": 0.85
|
||||
},
|
||||
"polished": {
|
||||
"color": "#e2e8ec",
|
||||
"roughness": 0.19,
|
||||
"metalness": 0.9
|
||||
},
|
||||
"recess": {
|
||||
"color": "#111718",
|
||||
"roughness": 0.66,
|
||||
"metalness": 0.2
|
||||
},
|
||||
"rubber": {
|
||||
"color": "#222729",
|
||||
"roughness": 0.88
|
||||
},
|
||||
"carbide": {
|
||||
"color": "#b9aa79",
|
||||
"roughness": 0.29,
|
||||
"metalness": 0.8
|
||||
},
|
||||
"warning": {
|
||||
"color": "#e7bd48",
|
||||
"roughness": 0.48,
|
||||
"metalness": 0.12
|
||||
},
|
||||
"lamp": {
|
||||
"color": "#e2f8ff",
|
||||
"emissive": "#d4f2ff",
|
||||
"emissiveIntensity": 1.5
|
||||
}
|
||||
},
|
||||
"toolpath_colors": {
|
||||
"background": "#050a0e",
|
||||
"completed": "#d8dfe3",
|
||||
"rapid": "#a85d5d",
|
||||
"active": "#00ffff",
|
||||
"live": "#ff6b4a"
|
||||
}
|
||||
}
|
||||
}
|
||||
34
web/workspace/web-settings.mjs
Normal file
34
web/workspace/web-settings.mjs
Normal file
@@ -0,0 +1,34 @@
|
||||
import values from "./web-settings.json" with { type: "json" };
|
||||
|
||||
function freeze(value) {
|
||||
if (value && typeof value === "object") { Object.values(value).forEach(freeze); Object.freeze(value); }
|
||||
return value;
|
||||
}
|
||||
function finitePositive(value, name) {
|
||||
if (!Number.isFinite(value) || value <= 0) throw new Error(`web_settings_invalid:${name}`);
|
||||
}
|
||||
for (const [name, value] of Object.entries(values.runtime.planner)) if (name !== "blend") finitePositive(value, `planner.${name}`);
|
||||
for (const [name, value] of Object.entries(values.runtime.io)) {
|
||||
if (name === "spindle_tolerance") {
|
||||
if (!Number.isFinite(value) || value < 0) throw new Error("web_settings_invalid:io.spindle_tolerance");
|
||||
} else finitePositive(value, `io.${name}`);
|
||||
}
|
||||
for (const [name, value] of Object.entries(values.viewer.camera)) finitePositive(value, `camera.${name}`);
|
||||
finitePositive(values.viewer.mm_scale, "mm_scale");
|
||||
finitePositive(values.viewer.pixel_ratio_limit, "pixel_ratio_limit");
|
||||
for (const key of ["poll_ms", "jog_cycles", "run_cycles", "preview_progress_ms", "zoom", "zoom_min", "zoom_max", "zoom_step"]) finitePositive(values.ui[key], key);
|
||||
finitePositive(values.ui.override_debounce_ms, "override_debounce_ms");
|
||||
if (!(values.ui.motion_scale_min > 0 && values.ui.motion_scale_min <= 1)) throw new Error("web_settings_invalid:motion_scale_min");
|
||||
if (!values.ui.jog_increments.every((value) => Number.isFinite(Number(value)) && Number(value) > 0)) throw new Error("web_settings_invalid:jog_increments");
|
||||
if (values.ui.zoom_min > values.ui.zoom_max || values.ui.zoom < values.ui.zoom_min || values.ui.zoom > values.ui.zoom_max) throw new Error("web_settings_invalid:zoom_range");
|
||||
if (!["x", "y", "y2", "z", "z2", "p"].includes(values.ui.initial_view)) throw new Error("web_settings_invalid:initial_view");
|
||||
for (const key of ["jog_cycles", "run_cycles"]) if (!Number.isSafeInteger(values.ui[key])) throw new Error(`web_settings_invalid:${key}`);
|
||||
if (values.viewer.camera.near >= values.viewer.camera.far || values.viewer.camera.min_distance >= values.viewer.camera.max_distance) throw new Error("web_settings_invalid:camera_range");
|
||||
for (const model of Object.values(values.viewer.models)) finitePositive(model.datum_z_mm, "model_datum");
|
||||
if (values.runtime.planner.max_feed_override < 1) throw new Error("web_settings_invalid:feed_limit");
|
||||
for (const key of ["spindle_count", "tool_capacity", "encoder_count"]) if (!Number.isSafeInteger(values.runtime.io[key])) throw new Error(`web_settings_invalid:io.${key}`);
|
||||
export const WEB_SETTINGS = freeze(values);
|
||||
|
||||
export function axisImagePath(name, base = ".") {
|
||||
return `${base.replace(/\/$/, "")}/${WEB_SETTINGS.assets.axis_images}/${name}`;
|
||||
}
|
||||
Reference in New Issue
Block a user