diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 4314e4fa6..214a2a634 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -8,7 +8,7 @@ use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull); use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2 - intersection_pl offset2_ex diff_pl); + intersection_pl offset2_ex diff_pl diff_ex); use Slic3r::Surface ':types'; has 'print_config' => (is => 'rw', required => 1); @@ -782,7 +782,7 @@ sub generate_toolpaths { my $base_flow = $_flow; # find centerline of the external loop/extrusions - my $to_infill = offset2_ex($base, +scaled_epsilon, -(scaled_epsilon + $_flow->scaled_width/2)); + my $to_infill = offset2($base, +scaled_epsilon, -(scaled_epsilon + $_flow->scaled_width/2)); my @paths = (); @@ -796,6 +796,17 @@ sub generate_toolpaths { # use the proper spacing for first layer as we don't need to align # its pattern to the other layers $filler->set_min_spacing($base_flow->spacing); + + # subtract brim so that it goes around the object fully (and support gets its own brim) + if ($self->print_config->brim_width > 0) { + my $d = +scale $self->print_config->brim_width*2; + $to_infill = diff_ex( + $to_infill, + offset($object->get_layer(0)->slices->polygons, $d), + ); + } else { + $to_infill = union_ex($to_infill); + } } else { # draw a perimeter all around support infill # TODO: use brim ordering algorithm @@ -806,10 +817,10 @@ sub generate_toolpaths { mm3_per_mm => $mm3_per_mm, width => $_flow->width, height => $layer->height, - ), map @$_, @$to_infill; + ), @$to_infill; # TODO: use offset2_ex() - $to_infill = offset_ex([ map @$_, @$to_infill ], -$_flow->scaled_spacing); + $to_infill = offset_ex($to_infill, -$_flow->scaled_spacing); } my $mm3_per_mm = $base_flow->mm3_per_mm; diff --git a/t/fill.t b/t/fill.t index 710e60536..765cfd479 100644 --- a/t/fill.t +++ b/t/fill.t @@ -22,7 +22,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $print = Slic3r::Print->new; my $surface_width = 250; - my $distance = Slic3r::Filler::adjust_solid_spacing($surface_width, 47); + my $distance = Slic3r::Flow::solid_spacing($surface_width, 47); is $distance, 50, 'adjusted solid distance'; is $surface_width % $distance, 0, 'adjusted solid distance'; } diff --git a/xs/src/libslic3r/BridgeDetector.cpp b/xs/src/libslic3r/BridgeDetector.cpp index f9061e405..228f9a4e6 100644 --- a/xs/src/libslic3r/BridgeDetector.cpp +++ b/xs/src/libslic3r/BridgeDetector.cpp @@ -5,24 +5,6 @@ namespace Slic3r { -class BridgeDirectionComparator { - public: - std::map dir_coverage; // angle => score - - BridgeDirectionComparator(double _extrusion_width) - : extrusion_width(_extrusion_width) - {}; - - // the best direction is the one causing most lines to be bridged (thus most coverage) - bool operator() (double a, double b) { - // Initial sort by coverage only - comparator must obey strict weak ordering - return (this->dir_coverage[a] > this->dir_coverage[b]); - }; - - private: - double extrusion_width; -}; - BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width) : expolygon(_expolygon), lower_slices(_lower_slices), extrusion_width(_extrusion_width), @@ -59,6 +41,8 @@ BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonColle bool BridgeDetector::detect_angle() { + // Do nothing if the bridging region is completely in the air + // and there are no anchors available at the layer below. if (this->_edges.empty() || this->_anchors.empty()) return false; /* Outset the bridge expolygon by half the amount we used for detecting anchors; @@ -70,60 +54,65 @@ BridgeDetector::detect_angle() bridge in several directions and then sum the length of lines having both endpoints within anchors */ - // we test angles according to configured resolution - std::vector angles; - for (int i = 0; i <= PI/this->resolution; ++i) - angles.push_back(i * this->resolution); - - // we also test angles of each bridge contour + // generate the list of candidate angles + std::vector candidates; { - Polygons pp = this->expolygon; - for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { - Lines lines = p->lines(); - for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) - angles.push_back(line->direction()); + // we test angles according to configured resolution + std::vector angles; + for (int i = 0; i <= PI/this->resolution; ++i) + angles.push_back(i * this->resolution); + + // we also test angles of each bridge contour + { + Polygons pp = this->expolygon; + for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { + Lines lines = p->lines(); + for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) + angles.push_back(line->direction()); + } } - } - /* we also test angles of each open supporting edge - (this finds the optimal angle for C-shaped supports) */ - for (Polylines::const_iterator edge = this->_edges.begin(); edge != this->_edges.end(); ++edge) { - if (edge->first_point().coincides_with(edge->last_point())) continue; - angles.push_back(Line(edge->first_point(), edge->last_point()).direction()); - } - - // remove duplicates - double min_resolution = PI/180.0; // 1 degree - std::sort(angles.begin(), angles.end()); - for (size_t i = 1; i < angles.size(); ++i) { - if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) { - angles.erase(angles.begin() + i); - --i; + /* we also test angles of each open supporting edge + (this finds the optimal angle for C-shaped supports) */ + for (Polylines::const_iterator edge = this->_edges.begin(); edge != this->_edges.end(); ++edge) { + if (edge->first_point().coincides_with(edge->last_point())) continue; + angles.push_back(Line(edge->first_point(), edge->last_point()).direction()); } - } - /* compare first value with last one and remove the greatest one (PI) - in case they are parallel (PI, 0) */ - if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution)) - angles.pop_back(); - BridgeDirectionComparator bdcomp(this->extrusion_width); - std::map dir_avg_length; + // remove duplicates + double min_resolution = PI/180.0; // 1 degree + std::sort(angles.begin(), angles.end()); + for (size_t i = 1; i < angles.size(); ++i) { + if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) { + angles.erase(angles.begin() + i); + --i; + } + } + /* compare first value with last one and remove the greatest one (PI) + in case they are parallel (PI, 0) */ + if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution)) + angles.pop_back(); + + for (auto angle : angles) + candidates.push_back(BridgeDirection(angle)); + } + double line_increment = this->extrusion_width; bool have_coverage = false; - for (std::vector::const_iterator angle = angles.begin(); angle != angles.end(); ++angle) { + for (BridgeDirection &candidate : candidates) { Polygons my_clip_area = clip_area; ExPolygons my_anchors = this->_anchors; // rotate everything - the center point doesn't matter - for (Polygons::iterator it = my_clip_area.begin(); it != my_clip_area.end(); ++it) - it->rotate(-*angle, Point(0,0)); - for (ExPolygons::iterator it = my_anchors.begin(); it != my_anchors.end(); ++it) - it->rotate(-*angle, Point(0,0)); + for (Polygon &p : my_clip_area) + p.rotate(-candidate.angle, Point(0,0)); + for (ExPolygon &e : my_anchors) + e.rotate(-candidate.angle, Point(0,0)); // generate lines in this direction BoundingBox bb; - for (ExPolygons::const_iterator it = my_anchors.begin(); it != my_anchors.end(); ++it) - bb.merge((Points)*it); + for (const ExPolygon &e : my_anchors) + bb.merge((Points)e); Lines lines; for (coord_t y = bb.min.y; y <= bb.max.y; y += line_increment) @@ -143,44 +132,39 @@ BridgeDetector::detect_angle() std::vector lengths; double total_length = 0; - for (Lines::const_iterator line = clipped_lines.begin(); line != clipped_lines.end(); ++line) { - double len = line->length(); + for (const Line &line : clipped_lines) { + const double len = line.length(); lengths.push_back(len); total_length += len; } if (total_length) have_coverage = true; // sum length of bridged lines - bdcomp.dir_coverage[*angle] = total_length; + candidate.coverage = total_length; /* The following produces more correct results in some cases and more broken in others. TODO: investigate, as it looks more reliable than line clipping. */ // $directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0; // max length of bridged lines - dir_avg_length[*angle] = !lengths.empty() - ? *std::max_element(lengths.begin(), lengths.end()) - : 0; + if (!lengths.empty()) + candidate.max_length = *std::max_element(lengths.begin(), lengths.end()); } // if no direction produced coverage, then there's no bridge direction if (!have_coverage) return false; // sort directions by coverage - most coverage first - std::sort(angles.begin(), angles.end(), bdcomp); - this->angle = angles.front(); + std::sort(candidates.begin(), candidates.end()); // if any other direction is within extrusion width of coverage, prefer it if shorter // TODO: There are two options here - within width of the angle with most coverage, or within width of the currently perferred? - double most_coverage_angle = this->angle; - for (std::vector::const_iterator angle = angles.begin() + 1; - angle != angles.end() && bdcomp.dir_coverage[most_coverage_angle] - bdcomp.dir_coverage[*angle] < this->extrusion_width; - ++angle - ) { - if (dir_avg_length[*angle] < dir_avg_length[this->angle]) { - this->angle = *angle; - } - } + size_t i_best = 0; + for (size_t i = 1; i < candidates.size() && candidates[i_best].coverage - candidates[i].coverage < this->extrusion_width; ++ i) + if (candidates[i].max_length < candidates[i_best].max_length) + i_best = i; + + this->angle = candidates[i_best].angle; if (this->angle >= PI) this->angle -= PI; diff --git a/xs/src/libslic3r/BridgeDetector.hpp b/xs/src/libslic3r/BridgeDetector.hpp index bbbd26df9..3ba054f5d 100644 --- a/xs/src/libslic3r/BridgeDetector.hpp +++ b/xs/src/libslic3r/BridgeDetector.hpp @@ -31,6 +31,19 @@ private: Polylines _edges; // Closed polygons representing the supporting areas. ExPolygons _anchors; + + class BridgeDirection { + public: + BridgeDirection(double a = -1.) : angle(a), coverage(0.), max_length(0.) {} + // the best direction is the one causing most lines to be bridged (thus most coverage) + bool operator<(const BridgeDirection &other) const { + // Initial sort by coverage only - comparator must obey strict weak ordering + return this->coverage > other.coverage; + }; + double angle; + double coverage; + double max_length; + }; }; } diff --git a/xs/src/libslic3r/Fill/Fill.cpp b/xs/src/libslic3r/Fill/Fill.cpp index c88a32514..39c9107b9 100644 --- a/xs/src/libslic3r/Fill/Fill.cpp +++ b/xs/src/libslic3r/Fill/Fill.cpp @@ -1,3 +1,6 @@ +#define DEBUG +#undef NDEBUG +#include #include #include @@ -69,36 +72,6 @@ Fill::fill_surface(const Surface &surface) return polylines_out; } -// Calculate a new spacing to fill width with possibly integer number of lines, -// the first and last line being centered at the interval ends. -// This function possibly increases the spacing, never decreases, -// and for a narrow width the increase in spacing may become severe, -// therefore the adjustment is limited to 20% increase. -coord_t -Fill::adjust_solid_spacing(const coord_t width, const coord_t distance) -{ - assert(width >= 0); - assert(distance > 0); - const int number_of_intervals = floor(width / distance); - if (number_of_intervals == 0) return distance; - - coord_t distance_new = (width / number_of_intervals); - - const coordf_t factor = coordf_t(distance_new) / coordf_t(distance); - assert(factor > 1. - 1e-5); - - // How much could the extrusion width be increased? By 20%. - // Because of this limit, this method is not idempotent: each run - // will increment distance by 20%. - const coordf_t factor_max = 1.2; - if (factor > factor_max) - distance_new = floor((double)distance * factor_max + 0.5); - - assert((distance_new * number_of_intervals) <= width); - - return distance_new; -} - // Returns orientation of the infill and the reference point of the infill pattern. // For a normal print, the reference point is the center of a bounding box of the STL. Fill::direction_t diff --git a/xs/src/libslic3r/Fill/Fill.hpp b/xs/src/libslic3r/Fill/Fill.hpp index abb9e1c65..a72d5528c 100644 --- a/xs/src/libslic3r/Fill/Fill.hpp +++ b/xs/src/libslic3r/Fill/Fill.hpp @@ -64,7 +64,6 @@ public: public: static Fill* new_from_type(const InfillPattern type); static Fill* new_from_type(const std::string &type); - static coord_t adjust_solid_spacing(const coord_t width, const coord_t distance); virtual Fill* clone() const = 0; virtual ~Fill() {}; diff --git a/xs/src/libslic3r/Fill/FillConcentric.cpp b/xs/src/libslic3r/Fill/FillConcentric.cpp index 24d8f318a..992dfaa3b 100644 --- a/xs/src/libslic3r/Fill/FillConcentric.cpp +++ b/xs/src/libslic3r/Fill/FillConcentric.cpp @@ -1,5 +1,6 @@ #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" +#include "../Flow.hpp" #include "../Surface.hpp" #include "FillConcentric.hpp" @@ -20,7 +21,7 @@ FillConcentric::_fill_surface_single( if (this->density > 0.9999f && !this->dont_adjust) { BoundingBox bounding_box = expolygon.contour.bounding_box(); - distance = this->adjust_solid_spacing(bounding_box.size().x, distance); + distance = Flow::solid_spacing(bounding_box.size().x, distance); this->_spacing = unscale(distance); } diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp index e02c3a298..bfee321ed 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp @@ -6,6 +6,7 @@ #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" +#include "../Flow.hpp" #include "../PolylineCollection.hpp" #include "../Surface.hpp" #include @@ -48,7 +49,7 @@ FillRectilinear::_fill_single_direction(ExPolygon expolygon, // define flow spacing according to requested density if (this->density > 0.9999f && !this->dont_adjust) { - line_spacing = this->adjust_solid_spacing(bounding_box.size().x, line_spacing); + line_spacing = Flow::solid_spacing(bounding_box.size().x, line_spacing); this->_spacing = unscale(line_spacing); } else { // extend bounding box so that our pattern will be aligned with other layers diff --git a/xs/src/libslic3r/Flow.cpp b/xs/src/libslic3r/Flow.cpp index 6131534e7..42caf9f1a 100644 --- a/xs/src/libslic3r/Flow.cpp +++ b/xs/src/libslic3r/Flow.cpp @@ -50,6 +50,11 @@ Flow::spacing() const { return this->width - OVERLAP_FACTOR * (this->width - min_flow_spacing); } +void +Flow::set_spacing(float spacing) { + this->width = Flow::_width_from_spacing(spacing, this->nozzle_diameter, this->height, this->bridge); +} + /* This method returns the centerline spacing between an extrusion using this flow and another one using another flow. this->spacing(other) shall return the same value as other.spacing(*this) */ @@ -115,4 +120,37 @@ Flow::_width_from_spacing(float spacing, float nozzle_diameter, float height, bo return spacing + OVERLAP_FACTOR * height * (1 - PI/4.0); } +// Calculate a new spacing to fill width with possibly integer number of lines, +// the first and last line being centered at the interval ends. +// This function possibly increases the spacing, never decreases, +// and for a narrow width the increase in spacing may become severe, +// therefore the adjustment is limited to 20% increase. +template +T +Flow::solid_spacing(const T total_width, const T spacing) +{ + assert(total_width >= 0); + assert(spacing > 0); + const int number_of_intervals = floor(total_width / spacing); + if (number_of_intervals == 0) return spacing; + + T spacing_new = (total_width / number_of_intervals); + + const double factor = (double)spacing_new / (double)spacing; + assert(factor > 1. - 1e-5); + + // How much could the extrusion width be increased? By 20%. + // Because of this limit, this method is not idempotent: each run + // will increment spacing by 20%. + const double factor_max = 1.2; + if (factor > factor_max) + spacing_new = floor((double)spacing * factor_max + 0.5); + + assert((spacing_new * number_of_intervals) <= total_width); + + return spacing_new; +} +template coord_t Flow::solid_spacing(const coord_t total_width, const coord_t spacing); +template coordf_t Flow::solid_spacing(const coordf_t total_width, const coordf_t spacing); + } diff --git a/xs/src/libslic3r/Flow.hpp b/xs/src/libslic3r/Flow.hpp index fdfcac695..7149df6b4 100644 --- a/xs/src/libslic3r/Flow.hpp +++ b/xs/src/libslic3r/Flow.hpp @@ -30,6 +30,13 @@ class Flow : width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {}; float spacing() const; float spacing(const Flow &other) const; + void set_spacing(float spacing); + void set_solid_spacing(const coord_t total_width) { + this->set_spacing(Flow::solid_spacing(total_width, this->scaled_spacing())); + }; + void set_solid_spacing(const coordf_t total_width) { + this->set_spacing(Flow::solid_spacing(total_width, (coordf_t)this->spacing())); + }; double mm3_per_mm() const; coord_t scaled_width() const { return scale_(this->width); @@ -43,12 +50,12 @@ class Flow static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio); static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); + template static T solid_spacing(const T total_width, const T spacing); private: static float _bridge_width(float nozzle_diameter, float bridge_flow_ratio); static float _auto_width(FlowRole role, float nozzle_diameter, float height); static float _width_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); - static float _spacing(float width, float nozzle_diameter, float height, float bridge_flow_ratio); }; } diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 36ce27a8a..deb1d3135 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -163,6 +163,10 @@ Print::invalidate_state_by_config(const PrintConfigBase &config) || opt_key == "min_skirt_length" || opt_key == "ooze_prevention") { steps.insert(psSkirt); + } else if (opt_key == "brim_width") { + steps.insert(psBrim); + steps.insert(psSkirt); + osteps.insert(posSupportMaterial); } else if (opt_key == "brim_width" || opt_key == "interior_brim_width" || opt_key == "brim_connections_width") { @@ -759,13 +763,18 @@ Print::brim_flow() const extruders and take the one with, say, the smallest index. The same logic should be applied to the code that selects the extruder during G-code generation as well. */ - return Flow::new_from_config_width( + Flow flow = Flow::new_from_config_width( frPerimeter, width, this->config.nozzle_diameter.get_at(this->regions.front()->config.perimeter_extruder-1), this->skirt_first_layer_height(), 0 ); + + // Adjust extrusion width in order to fill the total brim width with an integer number of lines. + flow.set_solid_spacing(this->config.brim_width.value); + + return flow; } Flow @@ -844,8 +853,8 @@ Print::_make_brim() // perimeters because here we're offsetting outwards) append_to(loops, offset2( islands, - flow.scaled_width() + flow.scaled_spacing() * (i - 1.0 + 0.5), - flow.scaled_spacing() * -1.0, + flow.scaled_width() + flow.scaled_spacing() * (i - 1.5 + 0.5), + flow.scaled_spacing() * -0.5, 100000, ClipperLib::jtSquare )); diff --git a/xs/xsp/Filler.xsp b/xs/xsp/Filler.xsp index 0c5c08b6c..3208ac29a 100644 --- a/xs/xsp/Filler.xsp +++ b/xs/xsp/Filler.xsp @@ -78,8 +78,3 @@ new_from_type(CLASS, type) %} }; - -%package{Slic3r::Filler}; - -coord_t adjust_solid_spacing(coord_t width, coord_t distance) - %code{% RETVAL = Fill::adjust_solid_spacing(width, distance); %}; diff --git a/xs/xsp/Flow.xsp b/xs/xsp/Flow.xsp index d09f0b351..05cc828b9 100644 --- a/xs/xsp/Flow.xsp +++ b/xs/xsp/Flow.xsp @@ -81,3 +81,6 @@ _constant() %} +coord_t solid_spacing(coord_t width, coord_t distance) + %code{% RETVAL = Flow::solid_spacing(width, distance); %}; +