UNPKG

fullcontrol-js

Version:

FullControl TypeScript rewrite - API parity mirror of Python library (G-code generation & geometry)

1,298 lines (1,263 loc) 87.6 kB
var __defProp = Object.defineProperty; var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/core/base-model.ts var BaseModelPlus = class { // Accept partial init object; assign only known keys if enforceKeys provided constructor(init, enforceKeys) { if (init) { for (const k of Object.keys(init)) { if (!enforceKeys || enforceKeys.includes(k)) { this[k] = init[k]; } else { throw new Error(`attribute "${k}" not allowed for class ${this.constructor.name}`); } } } } updateFrom(source) { if (!source) return; for (const [k, v] of Object.entries(source)) { if (v === void 0) continue; this[k] = v; } } // Python parity alias update_from(source) { this.updateFrom(source); } copy() { const clone = Object.create(this.constructor.prototype); for (const [k, v] of Object.entries(this)) clone[k] = structuredClone(v); return clone; } }; // src/util/format.ts function formatPrecision6(val) { return val.toPrecision(6).replace(/\.?0+$/, ""); } function formatCoordinate(val) { return val.toFixed(6).replace(/0+$/, "").replace(/\.$/, ""); } function formatExtrusion(val) { return val.toFixed(6).replace(/0+$/, "").replace(/\.$/, ""); } function formatFeedrate(val) { return val.toFixed(1).replace(/\.0+$/, "").replace(/\.$/, ""); } // src/models/point.ts var _Point = class _Point extends BaseModelPlus { constructor(init) { super(init); } toJSON() { return { x: this.x, y: this.y, z: this.z, color: this.color, extrude: this.extrude, speed: this.speed }; } static fromJSON(data) { return new _Point(data); } XYZ_gcode(prev) { let s = ""; if (this.x != null && this.x !== prev.x) s += `X${formatCoordinate(this.x)} `; if (this.y != null && this.y !== prev.y) s += `Y${formatCoordinate(this.y)} `; if (this.z != null && this.z !== prev.z) s += `Z${formatCoordinate(this.z)} `; return s === "" ? void 0 : s; } gcode(state) { const XYZ = this.XYZ_gcode(state.point); if (XYZ == null) return void 0; const isFirst = state._first_movement_done !== true; const extruding = !!state.extruder?.on && !isFirst; const G = isFirst ? "G0 " : extruding || state.extruder?.travel_format === "G1_E0" ? "G1 " : "G0 "; const F = state.printer?.f_gcode(state) || ""; let E = ""; if (!isFirst && state.extruder) { if (extruding) E = state.extruder.e_gcode(this, state); else if (state.extruder?.travel_format === "G1_E0") E = state.extruder.e_gcode(this, state); } const line = `${G}${F}${XYZ}${E}`.trim(); if (state.printer) state.printer.speed_changed = false; state.point.updateFrom(this); if (isFirst) state._first_movement_done = true; return line; } }; // optional per-move speed override _Point.typeName = "Point"; var Point = _Point; // src/models/vector.ts var _Vector = class _Vector { constructor(init) { Object.assign(this, init); } toJSON() { return { x: this.x, y: this.y, z: this.z }; } static fromJSON(data) { return new _Vector(data); } }; _Vector.typeName = "Vector"; var Vector = _Vector; // src/models/extrusion.ts var _ExtrusionGeometry = class _ExtrusionGeometry extends BaseModelPlus { constructor(init) { super(init); } update_area() { if (this.area_model === "rectangle" && this.width != null && this.height != null) this.area = this.width * this.height; else if (this.area_model === "stadium" && this.width != null && this.height != null) this.area = (this.width - this.height) * this.height + Math.PI * (this.height / 2) ** 2; else if (this.area_model === "circle" && this.diameter != null) this.area = Math.PI * (this.diameter / 2) ** 2; } toJSON() { return { area_model: this.area_model, width: this.width, height: this.height, diameter: this.diameter, area: this.area }; } static fromJSON(d) { return new _ExtrusionGeometry(d); } gcode(state) { state.extrusion_geometry.updateFrom(this); if (this.width != null || this.height != null || this.diameter != null || this.area_model != null) { try { state.extrusion_geometry.update_area(); } catch { } } return void 0; } }; _ExtrusionGeometry.typeName = "ExtrusionGeometry"; var ExtrusionGeometry = _ExtrusionGeometry; var _StationaryExtrusion = class _StationaryExtrusion extends BaseModelPlus { constructor(init) { super(init); } toJSON() { return { volume: this.volume, speed: this.speed }; } static fromJSON(d) { return new _StationaryExtrusion(d); } gcode(state) { if (state.printer) state.printer.speed_changed = true; const eVal = state.extruder.get_and_update_volume(this.volume) * state.extruder.volume_to_e; if (state.extruder && state.extruder.on !== true) state.extruder.on = true; return `G1 F${this.speed} E${formatPrecision6(eVal)}`; } }; _StationaryExtrusion.typeName = "StationaryExtrusion"; var StationaryExtrusion = _StationaryExtrusion; var _Extruder = class _Extruder extends BaseModelPlus { constructor(init) { super(init); } update_e_ratio() { if (this.units === "mm3") this.volume_to_e = 1; else if (this.units === "mm" && this.dia_feed != null) this.volume_to_e = 1 / (Math.PI * (this.dia_feed / 2) ** 2); } get_and_update_volume(volume) { if (this.total_volume == null) this.total_volume = 0; if (this.total_volume_ref == null) this.total_volume_ref = 0; this.total_volume += volume; this.total_volume = Math.round(this.total_volume * 1e12) / 1e12; const ret = this.total_volume - this.total_volume_ref; if (this.relative_gcode) this.total_volume_ref = this.total_volume; return ret; } toJSON() { return { on: this.on, units: this.units, dia_feed: this.dia_feed, relative_gcode: this.relative_gcode }; } static fromJSON(d) { return new _Extruder(d); } e_gcode(point1, state) { const distance_forgiving = (p1, p2) => { const dx = p1.x == null || p2.x == null ? 0 : p1.x - p2.x; const dy = p1.y == null || p2.y == null ? 0 : p1.y - p2.y; const dz = p1.z == null || p2.z == null ? 0 : p1.z - p2.z; return Math.sqrt(dx * dx + dy * dy + dz * dz); }; if (this.on) { const length = distance_forgiving(point1, state.point); if (length === 0) return ""; const area = state.extrusion_geometry?.area || 0; const ratio = this.volume_to_e || 1; const val = this.get_and_update_volume(length * area) * ratio; return `E${formatExtrusion(val)}`; } else { if (this.travel_format === "G1_E0") { const ratio = this.volume_to_e || 1; const val = this.get_and_update_volume(0) * ratio; return `E${formatExtrusion(val)}`; } return ""; } } gcode(state) { state.extruder.updateFrom(this); if (this.on != null && state.printer) state.printer.speed_changed = true; if (this.units != null || this.dia_feed != null) state.extruder.update_e_ratio(); if (this.relative_gcode != null) { state.extruder.total_volume_ref = state.extruder.total_volume; return state.extruder.relative_gcode ? "M83 ; relative extrusion" : "M82 ; absolute extrusion\nG92 E0 ; reset extrusion position to zero"; } return void 0; } }; _Extruder.typeName = "Extruder"; var Extruder = _Extruder; var _Retraction = class _Retraction extends BaseModelPlus { constructor(init) { super(init); } toJSON() { return { length: this.length, speed: this.speed }; } static fromJSON(d) { return new _Retraction(d); } gcode(state) { const cmdMap = state.printer?.command_list; if (cmdMap && cmdMap.retract) return void 0; const len = this.length ?? state.extruder.retraction_length; if (len == null || len === 0) return void 0; const speed = this.speed ?? state.extruder.retraction_speed ?? 1800; if (state.printer) state.printer.speed_changed = true; const ratio = state.extruder.volume_to_e || 1; let eDelta; if (state.extruder.units === "mm3") { if (state.extruder.dia_feed) { const area = Math.PI * (state.extruder.dia_feed / 2) ** 2; eDelta = len * area * ratio; } else { return void 0; } } else { eDelta = len * ratio; } state.extruder.get_and_update_volume(-(eDelta / ratio)); const eVal = state.extruder.total_volume - state.extruder.total_volume_ref; if (state.extruder.relative_gcode) { const rel = -eDelta; return `G1 F${speed} E${formatExtrusion(rel)}`; } else { return `G1 F${speed} E${formatExtrusion(eVal * ratio)}`; } } }; // mm/min feedrate for retraction move _Retraction.typeName = "Retraction"; var Retraction = _Retraction; var _Unretraction = class _Unretraction extends BaseModelPlus { constructor(init) { super(init); } toJSON() { return { length: this.length, speed: this.speed }; } static fromJSON(d) { return new _Unretraction(d); } gcode(state) { const cmdMap = state.printer?.command_list; if (cmdMap && cmdMap.unretract) return void 0; const len = this.length ?? state.extruder.retraction_length; if (len == null || len === 0) return void 0; const speed = this.speed ?? state.extruder.retraction_speed ?? 1800; if (state.printer) state.printer.speed_changed = true; const ratio = state.extruder.volume_to_e || 1; let eDelta; if (state.extruder.units === "mm3") { if (state.extruder.dia_feed) { const area = Math.PI * (state.extruder.dia_feed / 2) ** 2; eDelta = len * area * ratio; } else return void 0; } else { eDelta = len * ratio; } state.extruder.get_and_update_volume(eDelta / ratio); const eVal = state.extruder.total_volume - state.extruder.total_volume_ref; if (state.extruder.relative_gcode) { return `G1 F${speed} E${formatExtrusion(eDelta)}`; } else { return `G1 F${speed} E${formatExtrusion(eVal * ratio)}`; } } }; _Unretraction.typeName = "Unretraction"; var Unretraction = _Unretraction; // src/models/printer.ts var _Printer = class _Printer extends BaseModelPlus { constructor(init) { super(init); } f_gcode(state) { if (this.speed_changed) { const speed = state.extruder?.on ? this.print_speed : this.travel_speed; if (speed != null) return `F${formatFeedrate(speed)} `; } return ""; } gcode(state) { state.printer.update_from(this); if (this.print_speed != null || this.travel_speed != null) state.printer.speed_changed = true; if (this.new_command) state.printer.command_list = { ...state.printer.command_list || {}, ...this.new_command }; return void 0; } toJSON() { return { print_speed: this.print_speed, travel_speed: this.travel_speed }; } static fromJSON(d) { return new _Printer(d); } }; _Printer.typeName = "Printer"; var Printer = _Printer; // src/models/auxiliary.ts var Buildplate = class extends BaseModelPlus { constructor(init) { super(init); } gcode() { if (this.temp == null) return ""; const code = this.wait ? "M190" : "M140"; return `${code} S${this.temp}`; } }; Buildplate.typeName = "Buildplate"; var Hotend = class extends BaseModelPlus { constructor(init) { super(init); } gcode() { if (this.temp == null) return ""; const code = this.wait ? "M109" : "M104"; const tool = this.tool != null ? ` T${this.tool}` : ""; return `${code}${tool} S${this.temp}`; } }; Hotend.typeName = "Hotend"; var Fan = class extends BaseModelPlus { // future multi-fan constructor(init) { super(init); this.speed_percent = 0; } gcode() { const s = Math.round(Math.max(0, Math.min(100, this.speed_percent)) * 255 / 100); return this.speed_percent > 0 ? `M106 S${s}` : "M107"; } }; Fan.typeName = "Fan"; // src/models/commands.ts var PrinterCommand = class extends BaseModelPlus { constructor(init) { super(init); } gcode(state) { if (this.id && state.printer.command_list) return state.printer.command_list[this.id]; } }; PrinterCommand.typeName = "PrinterCommand"; var ManualGcode = class extends BaseModelPlus { constructor(init) { super(init); } gcode() { return this.text; } }; ManualGcode.typeName = "ManualGcode"; var GcodeComment = class extends BaseModelPlus { constructor(init) { super(init); } gcode(state) { if (this.end_of_previous_line_text && state.gcode.length > 0) state.gcode[state.gcode.length - 1] += " ; " + this.end_of_previous_line_text; if (this.text) return "; " + this.text; } }; GcodeComment.typeName = "GcodeComment"; // src/models/controls.ts var GcodeControls = class extends BaseModelPlus { constructor(init) { super(init); this.include_date = true; this.show_banner = true; this.show_tips = true; this.silent = false; } initialize() { if (!this.printer_name) { this.printer_name = "generic"; console.warn("warning: printer is not set - defaulting to 'generic'"); } } }; GcodeControls.typeName = "GcodeControls"; var PlotControls = class extends BaseModelPlus { constructor(init) { super(init); this.color_type = "z_gradient"; this.tube_type = "flow"; this.tube_sides = 4; this.zoom = 1; this.hide_annotations = false; this.hide_travel = false; this.hide_axes = false; this.neat_for_publishing = false; this.raw_data = false; this.printer_name = "generic"; } initialize() { if (!this.raw_data) { if (!this.style) { this.style = "tube"; console.warn("warning: plot style is not set - defaulting to 'tube'"); } if (this.style === "tube" && this.line_width != null) { console.warn("warning: line_width set but style=tube; it is ignored for extruding lines"); } if (this.line_width == null) this.line_width = 2; } } }; PlotControls.typeName = "PlotControls"; // src/models/annotations.ts var PlotAnnotation = class extends BaseModelPlus { constructor(init) { super(init); } visualize(state, plot_data, _plot_controls) { if (!this.point) this.point = new Point({ x: state.point.x, y: state.point.y, z: state.point.z }); if (plot_data?.add_annotation) plot_data.add_annotation(this); } }; PlotAnnotation.typeName = "PlotAnnotation"; // src/util/extra.ts function flatten(steps) { return steps.flatMap((s) => Array.isArray(s) ? s : [s]); } function linspace(start, end, number_of_points) { if (number_of_points < 2) return [start]; const out = []; const step = (end - start) / (number_of_points - 1); for (let i = 0; i < number_of_points; i++) out.push(start + step * i); return out; } function points_only(steps, track_xyz = true) { const pts = steps.filter((s) => s instanceof Point); if (!track_xyz) return pts; for (let i = 0; i < pts.length - 1; i++) { const next = pts[i + 1].copy(); const cur = pts[i]; if (cur.x != null && next.x == null) next.x = cur.x; if (cur.y != null && next.y == null) next.y = cur.y; if (cur.z != null && next.z == null) next.z = cur.z; pts[i + 1] = next; } while (pts.length && (pts[0].x == null || pts[0].y == null || pts[0].z == null)) pts.shift(); return pts; } function relative_point(reference, x_offset, y_offset, z_offset) { let pt; if (reference instanceof Point) pt = reference; else if (Array.isArray(reference)) { for (let i = reference.length - 1; i >= 0; i--) if (reference[i] instanceof Point) { pt = reference[i]; break; } } if (!pt) throw new Error("The reference object must be a Point or list containing at least one point"); if (pt.x == null || pt.y == null || pt.z == null) throw new Error(`The reference point must have all x,y,z defined (x=${pt.x}, y=${pt.y}, z=${pt.z})`); return new Point({ x: pt.x + x_offset, y: pt.y + y_offset, z: pt.z + z_offset }); } function first_point(steps, fully_defined = true) { for (const s of steps) if (s instanceof Point) { if (!fully_defined || s.x != null && s.y != null && s.z != null) return s; } throw new Error(fully_defined ? "No point found in steps with all of x y z defined" : "No point found in steps"); } function last_point(steps, fully_defined = true) { for (let i = steps.length - 1; i >= 0; i--) { const s = steps[i]; if (s instanceof Point) { if (!fully_defined || s.x != null && s.y != null && s.z != null) return s; } } throw new Error(fully_defined ? "No point found in steps with all of x y z defined" : "No point found in steps"); } function export_design(steps, filename) { const serialized = steps.map((s) => ({ type: s.constructor.typeName || s.constructor.name, data: { ...s } })); const json = JSON.stringify(serialized, null, 2); if (filename) { if (typeof window === "undefined") { import("fs").then((fs2) => fs2.writeFileSync(filename + ".json", json)); } } return json; } function import_design(registry, jsonOrFilename) { let jsonStr = jsonOrFilename; if (jsonOrFilename.endsWith(".json")) { if (typeof window !== "undefined") throw new Error("File system import not available in browser context"); const fs2 = __require("fs"); jsonStr = fs2.readFileSync(jsonOrFilename, "utf-8"); } const arr = JSON.parse(jsonStr); return arr.map((o) => { const cls = registry[o.type]; if (!cls) throw new Error(`Unknown design type '${o.type}'`); return cls.fromJSON ? cls.fromJSON(o.data) : new cls(o.data); }); } function build_default_registry() { const reg = {}; const add = (cls) => { if (cls && cls.typeName) reg[cls.typeName] = cls; }; [ Point, Vector, Extruder, ExtrusionGeometry, StationaryExtrusion, Retraction, Unretraction, Printer, Fan, Hotend, Buildplate, PrinterCommand, ManualGcode, GcodeComment, GcodeControls, PlotControls, PlotAnnotation ].forEach(add); return reg; } // src/util/check.ts function check(steps) { if (!Array.isArray(steps)) { console.warn("design must be a 1D list of instances"); return; } const types = new Set(steps.map((s) => Array.isArray(s) ? "list" : s?.constructor?.name)); let results = ""; if (types.has("list")) { results += "warning - list contains nested lists; use flatten() to convert to 1D\n"; } results += "step types " + JSON.stringify([...types]); console.log("check results:\n" + results); } function fix(steps, result_type, controls) { const hasNested = steps.some((s) => Array.isArray(s)); if (hasNested) { console.warn("warning - design includes nested lists; flattening automatically"); steps = flatten(steps); } const p0 = first_point(steps, false); if (p0.x == null || p0.y == null || p0.z == null) { console.warn(`warning - first point should define x,y,z; filling missing with 0`); p0.x = p0.x ?? 0; p0.y = p0.y ?? 0; p0.z = p0.z ?? 0; } if (result_type === "plot" && controls?.color_type === "manual" && p0.color == null) { throw new Error("for PlotControls(color_type='manual') first point must have a color"); } return steps; } function check_points(geometry, checkType) { if (checkType === "polar_xy") { const checkPoint = (pt) => { if (pt.x == null || pt.y == null) throw new Error("polar transformations require points with x and y defined"); }; if (geometry instanceof Point) checkPoint(geometry); else for (const g of geometry) if (g instanceof Point) checkPoint(g); } } // src/geometry/polar.ts function polar_to_point(centre, radius, angle) { return new Point({ x: (centre.x ?? 0) + radius * Math.cos(angle), y: (centre.y ?? 0) + radius * Math.sin(angle), z: centre.z }); } function point_to_polar(target_point, origin_point) { check_points([target_point, origin_point], "polar_xy"); const r = Math.hypot(target_point.x - origin_point.x, target_point.y - origin_point.y); const angle = Math.atan2(target_point.y - origin_point.y, target_point.x - origin_point.x); return { radius: r, angle: (angle % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI) }; } function polar_to_vector(length, angle) { return new Vector({ x: length * Math.cos(angle), y: length * Math.sin(angle) }); } // src/geometry/midpoint.ts function midpoint(p1, p2) { return new Point({ x: avg(p1.x, p2.x), y: avg(p1.y, p2.y), z: avg(p1.z, p2.z) }); } function avg(a, b) { return a != null && b != null ? (a + b) / 2 : void 0; } function interpolated_point(p1, p2, f) { const interp = (a, b) => a != null || b != null ? (a ?? b) + f * ((b ?? a) - (a ?? b)) : void 0; return new Point({ x: interp(p1.x, p2.x), y: interp(p1.y, p2.y), z: interp(p1.z, p2.z) }); } function centreXY_3pt(p1, p2, p3) { const D = 2 * (p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y)); if (D === 0) throw new Error("The points are collinear, no unique circle"); const x_centre = ((p1.x ** 2 + p1.y ** 2) * (p2.y - p3.y) + (p2.x ** 2 + p2.y ** 2) * (p3.y - p1.y) + (p3.x ** 2 + p3.y ** 2) * (p1.y - p2.y)) / D; const y_centre = ((p1.x ** 2 + p1.y ** 2) * (p3.x - p2.x) + (p2.x ** 2 + p2.y ** 2) * (p1.x - p3.x) + (p3.x ** 2 + p3.y ** 2) * (p2.x - p1.x)) / D; return new Point({ x: x_centre, y: y_centre, z: p1.z }); } // src/geometry/measure.ts function distance(p1, p2) { return Math.sqrt(((p1.x ?? 0) - (p2.x ?? 0)) ** 2 + ((p1.y ?? 0) - (p2.y ?? 0)) ** 2 + ((p1.z ?? 0) - (p2.z ?? 0)) ** 2); } function angleXY_between_3_points(start, mid, end) { return point_to_polar(end, mid).angle - point_to_polar(start, mid).angle; } function path_length(points) { let len = 0; for (let i = 0; i < points.length - 1; i++) len += distance(points[i], points[i + 1]); return len; } // src/geometry/move.ts function move(geometry, vector, copy = false, copy_quantity = 2) { return copy ? copy_geometry(geometry, vector, copy_quantity) : move_geometry(geometry, vector); } function move_geometry(geometry, vector) { const move_point = (p) => { const n = p.copy(); if (n.x != null && vector.x != null) n.x += vector.x; if (n.y != null && vector.y != null) n.y += vector.y; if (n.z != null && vector.z != null) n.z += vector.z; return n; }; if (geometry instanceof Point) return move_point(geometry); return geometry.map((e) => e instanceof Point ? move_point(e) : e); } function copy_geometry(geometry, vector, quantity) { const out = []; for (let i = 0; i < quantity; i++) { const v = new Vector({ x: vector.x != null ? vector.x * i : void 0, y: vector.y != null ? vector.y * i : void 0, z: vector.z != null ? vector.z * i : void 0 }); const g = move_geometry(geometry, v); if (g instanceof Point) out.push(g); else out.push(...g); } return out; } // src/geometry/move_polar.ts function move_polar(geometry, centre, radius, angle, copy = false, copy_quantity = 2) { check_points(geometry, "polar_xy"); return copy ? copy_geometry_polar(geometry, centre, radius, angle, copy_quantity) : move_geometry_polar(geometry, centre, radius, angle); } function move_geometry_polar(geometry, centre, radius, angle) { const move_point = (p) => { const pol = point_to_polar(p, centre); const np = polar_to_point(centre, pol.radius + radius, pol.angle + angle); const clone = p.copy(); clone.x = np.x; clone.y = np.y; return clone; }; if (geometry instanceof Point) return move_point(geometry); return geometry.map((e) => e instanceof Point ? move_point(e) : e); } function copy_geometry_polar(geometry, centre, radius, angle, quantity) { const out = []; for (let i = 0; i < quantity; i++) { const rnow = radius * i; const anow = angle * i; const g = move_geometry_polar(geometry, centre, rnow, anow); if (g instanceof Point) out.push(g); else out.push(...g); } return out; } // src/geometry/reflect.ts function reflectXY_mc(p, m_reflect, c_reflect) { const m_reflect_normal = -1 / m_reflect; const c_reflect_normal = (p.y ?? 0) - m_reflect_normal * (p.x ?? 0); const x = (c_reflect_normal - c_reflect) / (m_reflect - m_reflect_normal); const y = (c_reflect_normal - m_reflect_normal / m_reflect * c_reflect) / (1 - m_reflect_normal / m_reflect); return new Point({ x: (p.x ?? 0) + 2 * (x - (p.x ?? 0)), y: (p.y ?? 0) + 2 * (y - (p.y ?? 0)), z: p.z }); } function reflectXY(p, p1, p2) { if (p2.x === p1.x) return new Point({ x: (p.x ?? 0) + 2 * (p1.x - (p.x ?? 0)), y: p.y, z: p.z }); if (p2.y === p1.y) return new Point({ x: p.x, y: (p.y ?? 0) + 2 * (p1.y - (p.y ?? 0)), z: p.z }); const m = (p2.y - p1.y) / (p2.x - p1.x); const c = p1.y - m * p1.x; return reflectXY_mc(p, m, c); } // src/geometry/reflect_polar.ts function reflectXYpolar(p, preflect, angle_reflect) { return reflectXY(p, preflect, polar_to_point(preflect, 1, angle_reflect)); } // src/geometry/segmentation.ts function segmented_line(p1, p2, segments) { const xs = linspace(p1.x, p2.x, segments + 1); const ys = linspace(p1.y, p2.y, segments + 1); const zs = linspace(p1.z, p2.z, segments + 1); return xs.map((_, i) => new Point({ x: xs[i], y: ys[i], z: zs[i] })); } function segmented_path(points, segments) { const lengths = points.slice(0, -1).map((_, i) => distance(points[i], points[i + 1])); const cumulative = [0]; for (const l of lengths) cumulative.push(cumulative[cumulative.length - 1] + l); const seg_length = cumulative[cumulative.length - 1] / segments; const out = [points[0]]; let path_section_now = 1; for (let s = 1; s < segments; s++) { const target = seg_length * s; while (target > cumulative[path_section_now]) path_section_now++; const interpolation_length = target - cumulative[path_section_now - 1]; const fraction = interpolation_length / distance(points[path_section_now - 1], points[path_section_now]); out.push(interpolated_point(points[path_section_now - 1], points[path_section_now], fraction)); } out.push(points[points.length - 1]); return out; } // src/geometry/ramping.ts function ramp_xyz(list, x_change = 0, y_change = 0, z_change = 0) { const xs = linspace(0, x_change, list.length); const ys = linspace(0, y_change, list.length); const zs = linspace(0, z_change, list.length); for (let i = 0; i < list.length; i++) list[i] = move(list[i], new Vector({ x: xs[i], y: ys[i], z: zs[i] })); return list; } function ramp_polar(list, centre, radius_change = 0, angle_change = 0) { const rs = linspace(0, radius_change, list.length); const as = linspace(0, angle_change, list.length); for (let i = 0; i < list.length; i++) list[i] = move_polar(list[i], centre, rs[i], as[i]); return list; } // src/geometry/arcs.ts function arcXY(centre, radius, start_angle, arc_angle, segments = 100) { return linspace(start_angle, start_angle + arc_angle, segments + 1).map((a) => polar_to_point(centre, radius, a)); } function variable_arcXY(centre, start_radius, start_angle, arc_angle, segments = 100, radius_change = 0, z_change = 0) { let arc = arcXY(centre, start_radius, start_angle, arc_angle, segments); arc = ramp_xyz(arc, 0, 0, z_change); return ramp_polar(arc, centre, radius_change, 0); } function elliptical_arcXY(centre, a, b, start_angle, arc_angle, segments = 100) { const t = linspace(start_angle, start_angle + arc_angle, segments + 1); return t.map((tt) => new Point({ x: a * Math.cos(tt) + centre.x, y: b * Math.sin(tt) + centre.y, z: centre.z })); } function arcXY_3pt(p1, p2, p3, segments = 100) { const centre = centreXY_3pt(p1, p2, p3); const radius = Math.hypot(p1.x - centre.x, p1.y - centre.y); const start_angle = Math.atan2(p1.y - centre.y, p1.x - centre.x); const mid_angle = Math.atan2(p2.y - centre.y, p2.x - centre.x); const end_angle = Math.atan2(p3.y - centre.y, p3.x - centre.x); const twoPi = Math.PI * 2; const norm = (a) => { while (a < 0) a += twoPi; while (a >= twoPi) a -= twoPi; return a; }; const sa = norm(start_angle), ma = norm(mid_angle), ea = norm(end_angle); const ccw = ma > sa && ma < ea || sa > ea && (ma > sa || ma < ea); const arc_angle = ccw ? ea - sa : -(twoPi - (ea - sa)); return arcXY(centre, radius, sa, arc_angle, segments); } // src/geometry/shapes.ts function rectangleXY(start, x_size, y_size, cw = false) { const p1 = new Point({ x: start.x + x_size * (cw ? 0 : 1), y: start.y + y_size * (cw ? 1 : 0), z: start.z }); const p2 = new Point({ x: start.x + x_size, y: start.y + y_size, z: start.z }); const p3 = new Point({ x: start.x + x_size * (cw ? 1 : 0), y: start.y + y_size * (cw ? 0 : 1), z: start.z }); return [start.copy(), p1, p2, p3, start.copy()]; } function circleXY(centre, radius, start_angle, segments = 100, cw = false) { return arcXY(centre, radius, start_angle, Math.PI * 2 * (1 - 2 * Number(cw)), segments); } function circleXY_3pt(p1, p2, p3, start_angle, start_at_first_point, segments = 100, cw = false) { const centre = centreXY_3pt(p1, p2, p3); const radius = Math.hypot(p1.x - centre.x, p1.y - centre.y); if (start_angle != null && start_at_first_point != null) throw new Error("start_angle and start_at_first_point cannot both be set"); if (start_angle == null) { if (start_at_first_point == null) throw new Error("neither start_angle nor start_at_first_point set"); start_angle = Math.atan2(p1.y - centre.y, p1.x - centre.x); } return arcXY(centre, radius, start_angle, Math.PI * 2 * (1 - 2 * Number(cw)), segments); } function ellipseXY(centre, a, b, start_angle, segments = 100, cw = false) { return elliptical_arcXY(centre, a, b, start_angle, Math.PI * 2 * (1 - 2 * Number(cw)), segments); } function polygonXY(centre, enclosing_radius, start_angle, sides, cw = false) { return arcXY(centre, enclosing_radius, start_angle, Math.PI * 2 * (1 - 2 * Number(cw)), sides); } function spiralXY(centre, start_radius, end_radius, start_angle, n_turns, segments, cw = false) { return variable_arcXY(centre, start_radius, start_angle, n_turns * Math.PI * 2 * (1 - 2 * Number(cw)), segments, end_radius - start_radius, 0); } function helixZ(centre, start_radius, end_radius, start_angle, n_turns, pitch_z, segments, cw = false) { return variable_arcXY(centre, start_radius, start_angle, n_turns * Math.PI * 2 * (1 - 2 * Number(cw)), segments, end_radius - start_radius, pitch_z * n_turns); } // src/geometry/waves.ts function squarewaveXYpolar(start, direction_polar, amplitude, line_spacing, periods, extra_half_period = false, extra_end_line = false) { const steps = [start.copy()]; for (let i = 0; i < periods; i++) { steps.push(polar_to_point(steps[steps.length - 1], amplitude, direction_polar + Math.PI / 2)); steps.push(polar_to_point(steps[steps.length - 1], line_spacing, direction_polar)); steps.push(polar_to_point(steps[steps.length - 1], amplitude, direction_polar - Math.PI / 2)); if (i !== periods - 1) steps.push(polar_to_point(steps[steps.length - 1], line_spacing, direction_polar)); } if (extra_half_period) { steps.push(polar_to_point(steps[steps.length - 1], line_spacing, direction_polar)); steps.push(polar_to_point(steps[steps.length - 1], amplitude, direction_polar + Math.PI / 2)); } if (extra_end_line) steps.push(polar_to_point(steps[steps.length - 1], line_spacing, direction_polar)); return steps; } function squarewaveXY(start, direction_vector, amplitude, line_spacing, periods, extra_half_period = false, extra_end_line = false) { const vx = direction_vector.x ?? 0, vy = direction_vector.y ?? 0; const direction_polar = Math.atan2(vy, vx); return squarewaveXYpolar(start, direction_polar, amplitude, line_spacing, periods, extra_half_period, extra_end_line); } function trianglewaveXYpolar(start, direction_polar, amplitude, tip_separation, periods, extra_half_period = false) { const steps = [start.copy()]; for (let i = 0; i < periods; i++) { let temp = polar_to_point(steps[steps.length - 1], amplitude, direction_polar + Math.PI / 2); steps.push(polar_to_point(temp, tip_separation / 2, direction_polar)); temp = polar_to_point(steps[steps.length - 1], amplitude, direction_polar - Math.PI / 2); steps.push(polar_to_point(temp, tip_separation / 2, direction_polar)); } if (extra_half_period) { const temp = polar_to_point(steps[steps.length - 1], amplitude, direction_polar + Math.PI / 2); steps.push(polar_to_point(temp, tip_separation / 2, direction_polar)); } return steps; } function sinewaveXYpolar(start, direction_polar, amplitude, period_length, periods, segments_per_period = 16, extra_half_period = false, phase_shift = 0) { const steps = []; const totalSegments = periods * segments_per_period + (extra_half_period ? Math.floor(0.5 * segments_per_period) : 0); for (let i = 0; i <= totalSegments; i++) { const axis_distance = i * period_length / segments_per_period; const amp_now = amplitude * (0.5 - 0.5 * Math.cos(i / segments_per_period * Math.PI * 2 + phase_shift)); steps.push(move(start, new Vector({ x: axis_distance, y: amp_now, z: 0 }))); } return move_polar(steps, start, 0, direction_polar); } // src/geometry/travel_to.ts function travel_to(geometry) { let point; if (geometry instanceof Point) point = geometry; else if (Array.isArray(geometry)) point = first_point(geometry); else throw new Error("travel_to expects a Point or array of steps containing at least one Point"); return [new Extruder({ on: false }), point, new Extruder({ on: true })]; } // src/gcode/primer/index.ts function clonePoint(p) { return new Point({ x: p.x, y: p.y, z: p.z, extrude: p.extrude }); } function buildPrimer(name, endPoint, options) { if (!options?.enablePrimer || name === "no_primer") return []; switch (name) { case "travel": return [new Extruder({ on: false }), clonePoint(endPoint), new Extruder({ on: true })]; case "front_lines_then_y": return frontLinesThen("y", endPoint); case "front_lines_then_x": return frontLinesThen("x", endPoint); case "front_lines_then_xy": return frontLinesThen("xy", endPoint); case "x": return axisPrime("x", endPoint); case "y": return axisPrime("y", endPoint); default: return []; } } function addBoxSeq(endPoint) { return [new Point({ x: 110 }), new Point({ y: 14 }), new Point({ x: 10 }), new Point({ y: 16 })]; } function frontLinesThen(mode, endPoint) { const seq = []; seq.push(new ManualGcode({ text: ";-----\n; START OF PRIMER PROCEDURE\n;-----" })); seq.push(new Extruder({ on: false })); seq.push(new Point({ x: 10, y: 12, z: endPoint.z })); seq.push(new Extruder({ on: true })); seq.push(...addBoxSeq(endPoint)); if (mode === "y") { seq.push(new Point({ x: endPoint.x })); seq.push(new Point({ y: endPoint.y })); } else if (mode === "x") { seq.push(new Point({ y: endPoint.y })); seq.push(new Point({ x: endPoint.x })); } else { seq.push(new Point({ x: endPoint.x, y: endPoint.y })); } seq.push(new ManualGcode({ text: ";-----\n; END OF PRIMER PROCEDURE\n;-----\n" })); return seq; } function axisPrime(first, endPoint) { const seq = []; seq.push(new ManualGcode({ text: ";-----\n; START OF PRIMER PROCEDURE\n;-----" })); seq.push(new Extruder({ on: false })); seq.push(new Point({ x: 10, y: 12, z: endPoint.z })); seq.push(new Extruder({ on: true })); if (first === "x") { seq.push(new Point({ x: endPoint.x })); seq.push(new Point({ y: endPoint.y })); } else { seq.push(new Point({ y: endPoint.y })); seq.push(new Point({ x: endPoint.x })); } seq.push(new ManualGcode({ text: ";-----\n; END OF PRIMER PROCEDURE\n;-----\n" })); return seq; } // src/devices/community/singletool/generic.ts var generic_exports = {}; __export(generic_exports, { set_up: () => set_up }); // src/devices/community/singletool/base_settings.ts var default_initial_settings = { print_speed: 1e3, travel_speed: 8e3, area_model: "rectangle", extrusion_width: 0.4, extrusion_height: 0.2, nozzle_temp: 210, bed_temp: 40, enclosure_temp: 0, tool_number: 0, fan_percent: 100, print_speed_percent: 100, material_flow_percent: 100, e_units: "mm", // 'mm' | 'mm3' relative_e: true, manual_e_ratio: null, dia_feed: 1.75, travel_format: "G0", primer: "front_lines_then_y", printer_command_list: { home: "G28 ; home axes", retract: "G10 ; retract", unretract: "G11 ; unretract", absolute_coords: "G90 ; absolute coordinates", relative_coords: "G91 ; relative coordinates", units_mm: "G21 ; set units to millimeters" } }; // src/devices/community/singletool/generic.ts function set_up(user_overrides) { const printer_overrides = { primer: "travel" }; let initialization_data = { ...default_initial_settings, ...printer_overrides }; initialization_data = { ...initialization_data, ...user_overrides }; const starting_procedure_steps = []; starting_procedure_steps.push(new ManualGcode({ text: "; Time to print!!!!!\n; GCode created with FullControl - tell us what you're printing!\n; info@fullcontrol.xyz or tag FullControlXYZ on Twitter/Instagram/LinkedIn/Reddit/TikTok" })); starting_procedure_steps.push(new Extruder({ relative_gcode: initialization_data.relative_e })); if ("bed_temp" in user_overrides) starting_procedure_steps.push(new Buildplate({ temp: initialization_data.bed_temp, wait: false })); if ("nozzle_temp" in user_overrides) starting_procedure_steps.push(new Hotend({ temp: initialization_data.nozzle_temp, wait: false })); if ("bed_temp" in user_overrides) starting_procedure_steps.push(new Buildplate({ temp: initialization_data.bed_temp, wait: true })); if ("nozzle_temp" in user_overrides) starting_procedure_steps.push(new Hotend({ temp: initialization_data.nozzle_temp, wait: true })); if ("fan_percent" in user_overrides) starting_procedure_steps.push(new Fan({ speed_percent: initialization_data.fan_percent })); if ("print_speed_percent" in user_overrides) starting_procedure_steps.push(new ManualGcode({ text: "M220 S" + initialization_data.print_speed_percent + " ; set speed factor override percentage" })); if ("material_flow_percent" in user_overrides) starting_procedure_steps.push(new ManualGcode({ text: "M221 S" + initialization_data.material_flow_percent + " ; set extrude factor override percentage" })); const ending_procedure_steps = []; initialization_data.starting_procedure_steps = starting_procedure_steps; initialization_data.ending_procedure_steps = ending_procedure_steps; return initialization_data; } // src/pipeline/state.ts var State = class { constructor(steps = [], options) { this.gcodeLines = []; this.points = []; this.extruders = []; this.annotations = []; // Python-style single tracking instances this.point = new Point(); const flat = flatten(steps); const printerName = options?.printer_name || "generic"; let initData; initData = set_up(options?.initialization_data || {}); const starting = initData.starting_procedure_steps || []; const ending = initData.ending_procedure_steps || []; const primerName = initData.primer || "no_primer"; let firstPoint = void 0; for (const s of flat) { if (s instanceof Point) { firstPoint = s; break; } if (Array.isArray(s)) { const p = s.find((x) => x instanceof Point); if (p) { firstPoint = p; break; } } } let primerSteps = []; if (firstPoint && primerName && primerName !== "no_primer") { primerSteps = buildPrimer(primerName, firstPoint, { enablePrimer: true }); } this.steps = [...starting, ...primerSteps, ...flat, ...ending]; if (!this.printer) { this.printer = new Printer({ print_speed: initData.print_speed, travel_speed: initData.travel_speed, speed_changed: true, command_list: initData.printer_command_list }); } if (!this.extruders.length) { this.extruder = new Extruder({ units: initData.e_units, dia_feed: initData.dia_feed, total_volume: 0, total_volume_ref: 0, relative_gcode: initData.relative_e, travel_format: initData.travel_format === "G1_E0" ? "G1_E0" : "none", on: false }); this.extruder.update_e_ratio(); if (initData.manual_e_ratio != null) this.extruder.volume_to_e = initData.manual_e_ratio; this.extruders.push(this.extruder); } else { this.extruder = this.extruders[0]; } const hasGeom = this.steps.some((s) => s instanceof ExtrusionGeometry); if (!hasGeom) { const g = new ExtrusionGeometry({ area_model: initData.area_model, width: initData.extrusion_width, height: initData.extrusion_height }); this.steps.unshift(g); this.extrusion_geometry = g; try { g.update_area(); } catch { } } else { const gstep = this.steps.find((s) => s instanceof ExtrusionGeometry); if (gstep) { this.extrusion_geometry = gstep; try { gstep.update_area(); } catch { } } } } addPoint(p) { this.points.push(p); } addGcode(line) { if (line.includes("\n")) { for (const l of line.split(/\r?\n/)) if (l.trim().length) this.gcodeLines.push(l); } else { this.gcodeLines.push(line); } } register(step) { if (step && typeof step === "object") { if (step.type === "Extruder" || step.constructor?.name === "Extruder") this.extruders.push(step); if (step.type === "Printer" || step.constructor?.name === "Printer") this.printer = step; if (step.constructor?.name === "PlotAnnotation") this.annotations.push(step); } } }; // src/pipeline/gcode.ts function generate_gcode(state, controls) { const gstate = { printer: state.printer || new Printer(), gcode: state.gcodeLines }; if (state.extrusion_geometry && state.extrusion_geometry.area == null) { try { state.extrusion_geometry.update_area(); } catch { } } let firstMovementEmitted = false; const pendingModeAfterFirstMove = []; let seenFirstMode = false; let queuedPrinterUpdate = null; for (const step of state.steps) { if (Array.isArray(step) && step.length && step[0] instanceof Point) { for (const pt of step) { state.addPoint(pt); const line = pt.gcode(state); if (line) { state.addGcode(line); if (!firstMovementEmitted) { firstMovementEmitted = true; if (pendingModeAfterFirstMove.length) { for (const m of pendingModeAfterFirstMove) state.addGcode(m); pendingModeAfterFirstMove.length = 0; } } } } continue; } if (step instanceof Point) { state.addPoint(step); const line = step.gcode(state); if (line) { state.addGcode(line); if (!firstMovementEmitted) { firstMovementEmitted = true; if (pendingModeAfterFirstMove.length) { for (const m of pendingModeAfterFirstMove) state.addGcode(m); pendingModeAfterFirstMove.length = 0; } if (queuedPrinterUpdate) { queuedPrinterUpdate.gcode(state); queuedPrinterUpdate = null; } } } continue; } if (step instanceof ExtrusionGeometry) { step.gcode(state); continue; } if (step instanceof StationaryExtrusion) { const line = step.gcode(state); if (line) state.addGcode(line); continue; } if (step instanceof Retraction) { const line = step.gcode(state); if (line) state.addGcode(line); continue; } if (step instanceof Unretraction) { const line = step.gcode(state); if (line) state.addGcode(line); continue; } if (step instanceof Extruder) { const prevOn = state.extruder.on; const line = step.gcode(state); const becameOn = step.on === true && prevOn !== true; if (line) { const lines = line.split("\n"); if (!firstMovementEmitted) { if (!seenFirstMode) { for (const l of lines) state.addGcode(l); seenFirstMode = true; } else { pendingModeAfterFirstMove.push(...lines); } } else { for (const l of lines) state.addGcode(l); } } if (becameOn && !firstMovementEmitted) { state.extruder.on = true; if (state.printer) state.printer.speed_changed = true; } else if (becameOn) { if (state.printer) state.printer.speed_changed = true; } if (step.on === false && prevOn === true) { if (state.printer) state.printer.speed_changed = true; } continue; } if (step instanceof Printer) { if (!firstMovementEmitted) queuedPrinterUpdate = step; else step.gcode(state); continue; } if (step instanceof ManualGcode) { const out = step.gcode(); if (out) state.addGcode(out); continue; } if (step instanceof GcodeComment) { const out = step.gcode(gstate); if (out) state.addGcode(out); continue; } if (step instanceof PrinterCommand) { let out = step.gcode(gstate); if (state.printer?.command_list && step.id) { const cmd = state.printer.command_list[step.id]; if (cmd) out = cmd; } if (out) state.addGcode(out); continue; } } if (!firstMovementEmitted && pendingModeAfterFirstMove.length) { for (const m of pendingModeAfterFirstMove) state.addGcode(m); } return state.gcodeLines.join("\n"); } // src/pipeline/visualize.ts function build_plot_data(state) { const points = state.points.map((p) => ({ x: p.x, y: p.y, z: p.z, color: p.color })); return { points, annotations: state.annotations.slice() }; } // src/pipeline/transform.ts function transform(steps, result_type = "gcode", controls = {}) { const gcodeControls = new GcodeControls(controls); gcodeControls.initialize(); const fixed = fix(steps, result_type, gcodeControls); const state = new State(fixed, { initialization_data: gcodeControls.initialization_data, printer_name: gcodeControls.printer_name }); state.controls = gcodeControls; for (const s of state.steps) state.register(s); const gcode = generate_gcode(state, gcodeControls); const plot = build_plot_data(state); return { gcode, plot, state }; } // src/devices/community/singletool/custom.ts var custom_exports = {}; __export(custom_exports, { set_up: () => set_up2 }); function set_up2(user_overrides) { const printer_overrides = { primer: "no_primer", relative_e: false }; let initialization_data = { ...default_initial_settings, ...printer_overrides }; initialization_data = { ...initialization_data, ...user_overrides }; const starting_procedure_steps = []; starting_procedure_steps.push(new ManualGcode({ text: "; Time to print!!!!!\n; GCode created with FullControl - tell us what you're printing!\n; info@fullcontrol.xyz or tag FullControlXYZ on Twitter/Instagram/LinkedIn/Reddit/TikTok" })); if ("relative_e" in user_overrides) starting_procedure_steps.push(new Extruder({ relative_gcode: initialization_data.relative_e })); if ("bed_temp" in user_overrides) starting_procedure_steps.push(new Buildplate({ temp: initialization_data.bed_temp, wait: false })); if ("nozzle_temp" in user_overrides) starting_procedure_steps.push(new Hotend({ temp: initialization_data.nozzle_temp, wait: false })); if ("bed_temp" in user_overrides) starting_procedure_steps.push(new Buildplate({ temp: initialization_data.bed_temp, wait: true })); if ("nozzle_temp" in user_overrides) starting_procedure_steps.push(new Hotend({ temp: initialization_data.nozzle_temp, wait: true })); if ("fan_percent" in user_overrides) starting_procedure_steps.push(new Fan({ speed_percent: initialization_data.fan_percent })); if ("print_speed_percent" in user_overrides) starting_procedure_steps.push(new ManualGcode({ text: "M220 S" + initialization_data.print_speed_percent + " ; set speed factor override percentage" })); if ("material_flow_percent" in user_overrides) starting_procedure_steps.push(new ManualGcode({ text: "M221 S" + initialization_data.material_flow_percent + " ; set extrude factor override percentage" })); const ending_procedure_steps = []; initialization_data.starting_procedure_steps = starting_procedure_steps; initialization_data.ending_procedure_steps = ending_procedure_steps; return initialization_data; } // src/devices/community/singletool/prusa_mk4.ts var prusa_mk4_exports = {}; __export(prusa_mk4_exports, { set_up: () => set_up3 }); function set_up3(user_overrides) { const printer_overrides = { nozzle_probe_temp: 170 }; let initialization_data = { ...default_initial_settings, ...printer_overrides }; initialization_data = { ...initialization_data, ...user_overrides }; const s = []; s.push(new ManualGcode({ text: "; Time to print!!!!!\n; GCode created with FullControl - tell us what you're printing!\n; info@fullcontrol.xyz or tag FullControlXYZ on Twitter/Instagram/LinkedIn/Reddit/TikTok " })); s.push(new Buildplate({ temp: initialization_data.bed_temp, wait: false })); s.push(new Hotend({ temp: initialization_data.nozzle_probe_temp, wait: false })); s.push(new Buildplate({ temp: initialization_data.bed_temp, wait: true })); s.push(new Hotend({ temp: initialization_data.nozzle_probe_temp, wait: true })); s.push(new PrinterCommand({ id: "