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?.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"; 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; }