From 21f830bf24a5df028e1dcf4a392a2aa2e663d94a Mon Sep 17 00:00:00 2001 From: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:23:41 -0300 Subject: [PATCH] Ensure spiral lift inside print area (avoid collision) (#13634) * Ensure spiral lift positive quadrant * check for printable area * clamp area * simpler version * Adjust printable area bounds with safety safety margin Added safety margin to printable area bounds calculations. * increase safety margin * Refactor safety margin calculations in GCodeWriter * New Logic Co-Authored-By: Ian Bassi <12130714+ianalexis@users.noreply.github.com> --------- Co-authored-by: Ian Bassi <12130714+ianalexis@users.noreply.github.com> --- src/libslic3r/GCodeWriter.cpp | 103 ++++++++++++++++++++++++++++++++-- src/libslic3r/GCodeWriter.hpp | 13 +++++ 2 files changed, 111 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 26db1c61e5..e3d1c30362 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -2,6 +2,8 @@ #include "CustomGCode.hpp" #include "I18N.hpp" #include "PrintConfig.hpp" +#include "ClipperUtils.hpp" +#include "Line.hpp" #include #include #include @@ -99,9 +101,87 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) m_max_jerk_z = LIMITS(machine_max_jerk_z); m_max_jerk_e = LIMITS(machine_max_jerk_e); m_resolution = print_config.resolution.value; - #undef LIMITS #undef LIMITS_UINT + // Orca: capture the printable area(s) so a spiral lift can be skipped when its + // circle would leave the boundary and collide with the print limits. Full polygons + // are stored (not a bounding box) so the check stays correct for non-rectangular + // beds, and per-extruder areas are kept so printers with different boundaries per + // extruder use the right limit for whichever extruder is active. + auto to_scaled_polygon = [](const Pointfs &pts) { + Polygon poly; + poly.points.reserve(pts.size()); + for (const Vec2d &p : pts) + poly.points.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); + poly.make_counter_clockwise(); + return poly; + }; + + m_bed_printable_area.points.clear(); + m_extruder_printable_areas.clear(); + + if (print_config.printable_area.values.size() >= 3) + m_bed_printable_area = to_scaled_polygon(print_config.printable_area.values); + + const std::vector &extruder_areas = print_config.extruder_printable_area.values; + if (!extruder_areas.empty()) { + m_extruder_printable_areas.resize(extruder_areas.size()); + for (size_t i = 0; i < extruder_areas.size(); ++i) { + if (extruder_areas[i].size() < 3) { + // No dedicated area for this extruder: it can reach the whole bed. + m_extruder_printable_areas[i] = m_bed_printable_area; + continue; + } + Polygon extruder_poly = to_scaled_polygon(extruder_areas[i]); + if (m_bed_printable_area.points.size() < 3) { + m_extruder_printable_areas[i] = std::move(extruder_poly); + continue; + } + // The reachable area is the extruder area clipped to the bed. Bed shapes are + // convex in practice, so keep the largest resulting contour. + Polygons clipped = intersection(extruder_poly, m_bed_printable_area); + const Polygon *largest = nullptr; + double best_area = 0.; + for (const Polygon &p : clipped) { + double a = std::abs(p.area()); + if (a > best_area) { best_area = a; largest = &p; } + } + m_extruder_printable_areas[i] = largest ? *largest : std::move(extruder_poly); + } + } +} + +const Polygon *GCodeWriter::active_printable_area() const +{ + if (const Extruder *e = this->filament()) { + size_t id = e->extruder_id(); + if (id < m_extruder_printable_areas.size() && m_extruder_printable_areas[id].points.size() >= 3) + return &m_extruder_printable_areas[id]; + } + if (m_bed_printable_area.points.size() >= 3) + return &m_bed_printable_area; + return nullptr; +} + +bool GCodeWriter::spiral_lift_fits_printable_area(const Vec2d ¢er, double radius) const +{ + const Polygon *area = this->active_printable_area(); + if (area == nullptr) + return true; // Boundary unknown: don't restrict (preserve previous behavior). + + const Point c = Point::new_scale(center.x(), center.y()); + const double r_scaled = scale_(radius); + const double r2 = r_scaled * r_scaled; + + // The spiral traces a full circle of `radius` around `center`, so the center must lie + // inside the printable area and every edge must be at least `radius` away from it. + if (!area->contains(c)) + return false; + const Points &pts = area->points; + for (size_t i = 0, n = pts.size(); i < n; ++i) + if (Line::distance_to_squared(c, pts[i], pts[(i + 1) % n]) < r2) + return false; + return true; } void GCodeWriter::set_extruders(std::vector extruder_ids) @@ -731,14 +811,19 @@ std::string GCodeWriter::eager_lift(const LiftType type) { } // BBS: spiral lift only safe with known position - // TODO: check the arc will move within bed area if (type == LiftType::SpiralLift && this->is_current_position_clear()) { double radius = target_lift / (2 * PI * atan(filament()->travel_slope())); // static spiral alignment when no move in x,y plane. - // spiral centra is a radius distance to the right (y=0) + // spiral centra is a radius distance to the right (y=0) Vec2d ij_offset = { radius, 0 }; - if (target_lift > 0) { + // Orca: keep the spiral inside the active extruder's printable area, otherwise + // fall back to a normal lift to avoid colliding with the print boundary. m_pos + // includes the plate offset, so remove it to match the printable area coordinates. + const Vec2d spiral_center = { m_pos.x() - m_x_offset + ij_offset.x(), m_pos.y() - m_y_offset + ij_offset.y() }; + if (target_lift > 0 && this->spiral_lift_fits_printable_area(spiral_center, radius)) { lift_move = this->_spiral_travel_to_z(m_pos(2) + target_lift, ij_offset, "spiral lift Z"); + } else if (target_lift > 0) { + lift_move = _travel_to_z(m_pos(2) + target_lift, "normal lift Z"); } } //BBS: if position is unknown use normal lift @@ -793,7 +878,15 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co double radius = delta(2) / (2 * PI * atan(this->filament()->travel_slope())); Vec2d ij_offset = radius * delta_no_z.normalized(); ij_offset = { -ij_offset(1), ij_offset(0) }; - slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z"); + // Orca: only perform the spiral lift if its full circle stays inside the + // printable area of the active extruder, otherwise fall back to a normal + // lift to avoid colliding with the print boundary. `source` is already in + // bed coordinates (plate offset removed), matching the printable area. + const Vec2d spiral_center = { source.x() + ij_offset.x(), source.y() + ij_offset.y() }; + if (this->spiral_lift_fits_printable_area(spiral_center, radius)) + slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z"); + else + slop_move = _travel_to_z(target.z(), "normal lift Z"); } //BBS: SlopeLift else if (m_to_lift_type == LiftType::SlopeLift && diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 2e7ea2176c..e904f3f35b 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -6,6 +6,7 @@ #include #include "Extruder.hpp" #include "Point.hpp" +#include "Polygon.hpp" #include "PrintConfig.hpp" #include "GCode/CoolingBuffer.hpp" @@ -181,6 +182,14 @@ public: // Orca: slicing resolution in mm double m_resolution = 0.01; + // Orca: printable area polygons (scaled, bed coordinates) used to keep spiral lifts + // from colliding with the print boundary. m_extruder_printable_areas holds the + // per-extruder reachable area (intersected with the bed) when a printer defines + // different boundaries per extruder; m_bed_printable_area is the global fallback. + // Storing full polygons (rather than a bounding box) keeps the check correct for + // non-rectangular beds such as delta/circular printers. + Polygon m_bed_printable_area; + std::vector m_extruder_printable_areas; std::string m_gcode_label_objects_start; std::string m_gcode_label_objects_end; @@ -197,6 +206,10 @@ public: std::string _travel_to_z(double z, const std::string &comment); std::string _spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment); + // Orca: printable area of the active extruder (per-extruder when configured, otherwise the bed). Null when unknown. + const Polygon *active_printable_area() const; + // Orca: true if a full spiral-lift circle (center in bed coordinates, mm) fits inside the active printable area. + bool spiral_lift_fits_printable_area(const Vec2d ¢er, double radius) const; std::string _retract(double length, double restart_extra, const std::string &comment); std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);