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" }, }, }; }