77 lines
4.5 KiB
JavaScript
77 lines
4.5 KiB
JavaScript
import { parseIni } from "./ini-parser.mjs";
|
|
import { parseHalLine } from "./hal-configuration.mjs";
|
|
import { WorkspaceRuntime } from "./runtime.mjs";
|
|
import { machineDefinition } from "./machine-definitions.mjs";
|
|
import { fnv1a64 } from "../worker/replay.mjs";
|
|
|
|
export const TRT_GEOMETRY_KEYS = Object.freeze(["x_rot_point", "y_rot_point", "z_rot_point", "x_offset", "y_offset", "z_offset"]);
|
|
const numeric = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
function fail(code, detail) { throw Object.assign(new Error(`${code}: ${detail}`), { code }); }
|
|
|
|
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);
|
|
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";
|
|
const offsets = { tool_offset: 0 };
|
|
const sources = {};
|
|
for (const key of TRT_GEOMETRY_KEYS) {
|
|
const pin = `${machine.id}-kins.${key.replaceAll("_", "-")}`;
|
|
const nets = commands.filter(({ record: r }) => r?.command === "net" && r.tokens.includes(pin));
|
|
const direct = commands.filter(({ record: r }) => r?.command === "setp" && r.tokens[0] === pin);
|
|
if (nets.length > 1 || nets.length && direct.length) fail("trt_geometry_ambiguous", pin);
|
|
const assignments = nets.length ? commands.filter(({ record: r }) => r?.command === "sets" && r.tokens[0] === nets[0].record.tokens[0]) : direct;
|
|
if (assignments.length > 1) fail("trt_geometry_duplicate", pin);
|
|
if (!assignments.length) {
|
|
if (key !== unsupported) fail("trt_geometry_missing", pin);
|
|
offsets[key] = 0;
|
|
continue;
|
|
}
|
|
const { entry, record } = assignments[0];
|
|
const value = record.tokens[1];
|
|
if (record.tokens.length !== 2 || !numeric.test(value) || !Number.isFinite(Number(value))) fail("trt_geometry_number", `${pin} @ ${entry.source}:${entry.line}`);
|
|
if (key === unsupported && Number(value) !== 0) fail("trt_geometry_unsupported", pin);
|
|
offsets[key] = Number(value);
|
|
// Follow INI references so editing updates the original scalar definition.
|
|
const rawValue = entry.value.trim().split(/\s+/).at(-1);
|
|
const reference = /^\[([^\]]+)\](.+)$/.exec(rawValue);
|
|
let source = entry;
|
|
if (reference) {
|
|
const definitions = parsed.entries.filter((e) => e.section === reference[1] && e.key === reference[2]);
|
|
if (definitions.length !== 1) fail("trt_geometry_reference", rawValue);
|
|
source = definitions[0];
|
|
}
|
|
sources[key] = { path: source.source, line: source.line, value: reference ? source.value : rawValue };
|
|
if (Object.entries(sources).some(([other, location]) => other !== key && location.path === source.source && location.line === source.line)) fail("trt_geometry_shared_definition", key);
|
|
}
|
|
return { coordinates: machine.coordinates, offsets, sources, unsupported, state_hash: fnv1a64({ entrypoint, offsets, sources }) };
|
|
}
|
|
|
|
export function writeTrtGeometry(configuration, values) {
|
|
const geometry = readTrtGeometry(configuration);
|
|
const files = { ...configuration.files };
|
|
for (const key of TRT_GEOMETRY_KEYS) {
|
|
const raw = String(values[key] ?? geometry.offsets[key]).trim();
|
|
if (!numeric.test(raw) || !Number.isFinite(Number(raw))) fail("trt_geometry_number", key);
|
|
if (key === geometry.unsupported && Number(raw) !== 0) fail("trt_geometry_unsupported", key);
|
|
const source = geometry.sources[key];
|
|
if (!source) continue;
|
|
const lines = files[source.path].split("\n");
|
|
const line = lines[source.line - 1];
|
|
const comment = line.search(/[;#]/);
|
|
const end = comment < 0 ? line.length : comment;
|
|
const body = line.slice(0, end);
|
|
const index = body.lastIndexOf(source.value);
|
|
if (index < 0) fail("trt_geometry_source_changed", key);
|
|
lines[source.line - 1] = body.slice(0, index) + String(Number(raw)) + body.slice(index + source.value.length) + line.slice(end);
|
|
files[source.path] = lines.join("\n");
|
|
}
|
|
const result = { ...configuration, files };
|
|
if ("state_hash" in result) result.state_hash = fnv1a64({ entrypoint: configuration.entrypoint, files });
|
|
readTrtGeometry(result);
|
|
return result;
|
|
}
|