From 22ca927f124572b08546ab3aca7b085561a0d9cb Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 8 Nov 2016 09:59:25 +0100 Subject: [PATCH] Reworked the bridge detector to allow searching a single bridging direction over multiple regions. This allows a single bridge to be drawn over holes, which are too close to each other to allow for separate bridges. Fixes Bridging-Angle not optimal https://github.com/prusa3d/Slic3r/issues/12 Re-allowed adaptive infill line width for solid infills. The adaptive infill line width works in some circumstances, see Issue #15, but the original implementation often changed the line width too aggressively. The current implementation limits the line width change to 20%. Fixes Gaps between infill and perimeter leads to errors in laydown on following layer https://github.com/prusa3d/Slic3r/issues/15 --- t/bridges.t | 2 +- xs/src/libslic3r/BridgeDetector.cpp | 391 +++++++++++------------ xs/src/libslic3r/BridgeDetector.hpp | 42 ++- xs/src/libslic3r/ExPolygonCollection.cpp | 4 +- xs/src/libslic3r/Fill/Fill.cpp | 75 +++-- xs/src/libslic3r/LayerRegion.cpp | 352 +++++++++----------- xs/xsp/BridgeDetector.xsp | 10 + 7 files changed, 433 insertions(+), 443 deletions(-) diff --git a/t/bridges.t b/t/bridges.t index 432d5e7a9..582863a33 100644 --- a/t/bridges.t +++ b/t/bridges.t @@ -73,7 +73,7 @@ use Slic3r::Test; ); my $lower = [ Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([10,10],[10,20],[20,20],[20,30],[0,30],[0,10]), + Slic3r::Polygon->new_scale([10,10],[10,20],[20,20],[30,30],[0,30],[0,0]), ), ]; $_->translate(scale 20, scale 20) for $bridge, @$lower; # avoid negative coordinates for easier SVG preview diff --git a/xs/src/libslic3r/BridgeDetector.cpp b/xs/src/libslic3r/BridgeDetector.cpp index ec954e86c..d5250d03b 100644 --- a/xs/src/libslic3r/BridgeDetector.cpp +++ b/xs/src/libslic3r/BridgeDetector.cpp @@ -5,37 +5,48 @@ 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), - resolution(PI/36.0), angle(-1) +BridgeDetector::BridgeDetector( + ExPolygon _expolygon, + const ExPolygonCollection &_lower_slices, + coord_t _spacing) : + // The original infill polygon, not inflated. + expolygons(expolygons_owned), + // All surfaces of the object supporting this region. + lower_slices(_lower_slices), + spacing(_spacing) { - /* outset our bridge by an arbitrary amout; we'll use this outer margin - for detecting anchors */ - Polygons grown; - offset((Polygons)this->expolygon, &grown, this->extrusion_width); + this->expolygons_owned.push_back(std::move(_expolygon)); + initialize(); +} + +BridgeDetector::BridgeDetector( + const ExPolygons &_expolygons, + const ExPolygonCollection &_lower_slices, + coord_t _spacing) : + // The original infill polygon, not inflated. + expolygons(_expolygons), + // All surfaces of the object supporting this region. + lower_slices(_lower_slices), + spacing(_spacing) +{ + initialize(); +} + +void BridgeDetector::initialize() +{ + // 5 degrees stepping + this->resolution = PI/36.0; + // output angle not known + this->angle = -1.; + + // Outset our bridge by an arbitrary amout; we'll use this outer margin for detecting anchors. + Polygons grown = offset(this->expolygons, float(this->spacing)); - // detect what edges lie on lower slices by turning bridge contour and holes - // into polylines and then clipping them with each lower slice's contour - intersection(grown, this->lower_slices.contours(), &this->_edges); + // Detect possible anchoring edges of this bridging region. + // Detect what edges lie on lower slices by turning bridge contour and holes + // into polylines and then clipping them with each lower slice's contour. + // Currently _edges are only used to set a candidate direction of the bridge (see bridge_direction_candidates()). + intersection(to_polylines(grown), this->lower_slices.contours(), &this->_edges); #ifdef SLIC3R_DEBUG printf(" bridge has " PRINTF_ZU " support(s)\n", this->_edges.size()); @@ -43,7 +54,7 @@ BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonColle // detect anchors as intersection between our bridge expolygon and the lower slices // safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some edges - intersection(grown, this->lower_slices, &this->_anchors, true); + this->_anchor_regions = intersection_ex(grown, to_polygons(this->lower_slices.expolygons), true); /* if (0) { @@ -60,18 +71,103 @@ BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonColle bool BridgeDetector::detect_angle() { - if (this->_edges.empty() || this->_anchors.empty()) return false; + if (this->_edges.empty() || this->_anchor_regions.empty()) + // The bridging region is completely in the air, there are no anchors available at the layer below. + return false; + + std::vector candidates; + { + std::vector angles = bridge_direction_candidates(); + candidates.reserve(angles.size()); + for (size_t i = 0; i < angles.size(); ++ i) + candidates.push_back(BridgeDirection(angles[i])); + } /* Outset the bridge expolygon by half the amount we used for detecting anchors; we'll use this one to clip our test lines and be sure that their endpoints are inside the anchors and not on their contours leading to false negatives. */ - Polygons clip_area; - offset((const Slic3r::Polygons)this->expolygon, &clip_area, +this->extrusion_width/2); + Polygons clip_area = offset(this->expolygons, 0.5f * float(this->spacing)); /* we'll now try several directions using a rudimentary visibility check: bridge in several directions and then sum the length of lines having both endpoints within anchors */ + + bool have_coverage = false; + for (size_t i_angle = 0; i_angle < candidates.size(); ++ i_angle) + { + const double angle = candidates[i_angle].angle; + + Lines lines; + { + // Get an oriented bounding box around _anchor_regions. + BoundingBox bbox = get_extents_rotated(this->_anchor_regions, - angle); + // Cover the region with line segments. + lines.reserve((bbox.max.y - bbox.min.y + this->spacing) / this->spacing); + double s = sin(angle); + double c = cos(angle); + //FIXME Vojtech: The lines shall be spaced half the line width from the edge, but then + // some of the test cases fail. Need to adjust the test cases then? +// for (coord_t y = bbox.min.y + this->spacing / 2; y <= bbox.max.y; y += this->spacing) + for (coord_t y = bbox.min.y; y <= bbox.max.y; y += this->spacing) + lines.push_back(Line( + Point((coord_t)round(c * bbox.min.x - s * y), (coord_t)round(c * y + s * bbox.min.x)), + Point((coord_t)round(c * bbox.max.x - s * y), (coord_t)round(c * y + s * bbox.max.x)))); + } + + double total_length = 0; + double max_length = 0; + { + Lines clipped_lines = intersection(lines, clip_area); + for (size_t i = 0; i < clipped_lines.size(); ++i) { + const Line &line = clipped_lines[i]; + if (expolygons_contain(this->_anchor_regions, line.a) && expolygons_contain(this->_anchor_regions, line.b)) { + // This line could be anchored. + double len = line.length(); + total_length += len; + max_length = std::max(max_length, len); + } + } + } + if (total_length == 0.) + continue; + + have_coverage = true; + // Sum length of bridged lines. + candidates[i_angle].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 + candidates[i_angle].max_length = max_length; + } + + // 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(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? + size_t i_best = 0; + for (size_t i = 1; i < candidates.size() && candidates[i_best].coverage - candidates[i].coverage < this->spacing; ++ 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; + + #ifdef SLIC3R_DEBUG + printf(" Optimal infill angle is %d degrees\n", (int)Slic3r::Geometry::rad2deg(this->angle)); + #endif + + return true; +} + +std::vector BridgeDetector::bridge_direction_candidates() const +{ // we test angles according to configured resolution std::vector angles; for (int i = 0; i <= PI/this->resolution; ++i) @@ -79,20 +175,16 @@ BridgeDetector::detect_angle() // 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()); - } + Lines lines = to_lines(this->expolygons); + 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()); - } + for (Polylines::const_iterator edge = this->_edges.begin(); edge != this->_edges.end(); ++edge) + if (! edge->first_point().coincides_with(edge->last_point())) + angles.push_back(Line(edge->first_point(), edge->last_point()).direction()); // remove duplicates double min_resolution = PI/180.0; // 1 degree @@ -107,91 +199,8 @@ BridgeDetector::detect_angle() 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; - double line_increment = this->extrusion_width; - bool have_coverage = false; - for (std::vector::const_iterator angle = angles.begin(); angle != angles.end(); ++angle) { - 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)); - - // generate lines in this direction - BoundingBox bb; - for (ExPolygons::const_iterator it = my_anchors.begin(); it != my_anchors.end(); ++it) - bb.merge((Points)*it); - - Lines lines; - for (coord_t y = bb.min.y; y <= bb.max.y; y += line_increment) - lines.push_back(Line(Point(bb.min.x, y), Point(bb.max.x, y))); - - Lines clipped_lines; - intersection(lines, my_clip_area, &clipped_lines); - - // remove any line not having both endpoints within anchors - for (size_t i = 0; i < clipped_lines.size(); ++i) { - Line &line = clipped_lines[i]; - if (!Slic3r::Geometry::contains(my_anchors, line.a) - || !Slic3r::Geometry::contains(my_anchors, line.b)) { - clipped_lines.erase(clipped_lines.begin() + i); - --i; - } - } - - std::vector lengths; - double total_length = 0; - for (Lines::const_iterator line = clipped_lines.begin(); line != clipped_lines.end(); ++line) { - 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; - - /* 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 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(); - - // 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; - } - } - - if (this->angle >= PI) this->angle -= PI; - - #ifdef SLIC3R_DEBUG - printf(" Optimal infill angle is %d degrees\n", (int)Slic3r::Geometry::rad2deg(this->angle)); - #endif - - return true; + + return angles; } void @@ -199,58 +208,48 @@ BridgeDetector::coverage(double angle, Polygons* coverage) const { if (angle == -1) angle = this->angle; if (angle == -1) return; - - // Clone our expolygon and rotate it so that we work with vertical lines. - ExPolygon expolygon = this->expolygon; - expolygon.rotate(PI/2.0 - angle, Point(0,0)); - - /* Outset the bridge expolygon by half the amount we used for detecting anchors; - we'll use this one to generate our trapezoids and be sure that their vertices - are inside the anchors and not on their contours leading to false negatives. */ - ExPolygons grown; - offset(expolygon, &grown, this->extrusion_width/2.0); - - // Compute trapezoids according to a vertical orientation - Polygons trapezoids; - for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) - it->get_trapezoids2(&trapezoids, PI/2.0); - - // get anchors, convert them to Polygons and rotate them too - Polygons anchors; - for (ExPolygons::const_iterator anchor = this->_anchors.begin(); anchor != this->_anchors.end(); ++anchor) { - Polygons pp = *anchor; - for (Polygons::iterator p = pp.begin(); p != pp.end(); ++p) - p->rotate(PI/2.0 - angle, Point(0,0)); - anchors.insert(anchors.end(), pp.begin(), pp.end()); - } + + // Get anchors, convert them to Polygons and rotate them. + Polygons anchors = to_polygons(this->_anchor_regions); + polygons_rotate(anchors, PI/2.0 - angle); Polygons covered; - for (Polygons::const_iterator trapezoid = trapezoids.begin(); trapezoid != trapezoids.end(); ++trapezoid) { - Lines lines = trapezoid->lines(); - Lines supported; - intersection(lines, anchors, &supported); + for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) + { + // Clone our expolygon and rotate it so that we work with vertical lines. + ExPolygon expolygon = *it_expoly; + expolygon.rotate(PI/2.0 - angle); - // not nice, we need a more robust non-numeric check - for (size_t i = 0; i < supported.size(); ++i) { - if (supported[i].length() < this->extrusion_width) { - supported.erase(supported.begin() + i); - i--; - } + /* Outset the bridge expolygon by half the amount we used for detecting anchors; + we'll use this one to generate our trapezoids and be sure that their vertices + are inside the anchors and not on their contours leading to false negatives. */ + ExPolygons grown = offset_ex(expolygon, 0.5f * float(this->spacing)); + + // Compute trapezoids according to a vertical orientation + Polygons trapezoids; + for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) + it->get_trapezoids2(&trapezoids, PI/2.0); + + for (Polygons::iterator trapezoid = trapezoids.begin(); trapezoid != trapezoids.end(); ++trapezoid) { + Lines supported = intersection(trapezoid->lines(), anchors); + size_t n_supported = 0; + // not nice, we need a more robust non-numeric check + for (size_t i = 0; i < supported.size(); ++i) + if (supported[i].length() >= this->spacing) + ++ n_supported; + if (n_supported >= 2) + covered.push_back(STDMOVE(*trapezoid)); } - - if (supported.size() >= 2) covered.push_back(*trapezoid); } - - // merge trapezoids and rotate them back - Polygons _coverage; - union_(covered, &_coverage); - for (Polygons::iterator p = _coverage.begin(); p != _coverage.end(); ++p) - p->rotate(-(PI/2.0 - angle), Point(0,0)); - - // intersect trapezoids with actual bridge area to remove extra margins - // and append it to result - intersection(_coverage, this->expolygon, coverage); - + + // Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids + // instead of exact overlaps. + covered = union_(covered); + + // Intersect trapezoids with actual bridge area to remove extra margins and append it to result. + polygons_rotate(covered, -(PI/2.0 - angle)); + intersection(covered, to_polygons(this->expolygons), coverage); + /* if (0) { my @lines = map @{$_->lines}, @$trapezoids; @@ -260,7 +259,7 @@ BridgeDetector::coverage(double angle, Polygons* coverage) const Slic3r::SVG::output( "coverage_" . rad2deg($angle) . ".svg", expolygons => [$self->expolygon], - green_expolygons => $self->_anchors, + green_expolygons => $self->_anchor_regions, red_expolygons => $coverage, lines => \@lines, ); @@ -284,29 +283,21 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const { if (angle == -1) angle = this->angle; if (angle == -1) return; - - // get bridge edges (both contour and holes) - Polylines bridge_edges; - { - Polygons pp = this->expolygon; - bridge_edges.insert(bridge_edges.end(), pp.begin(), pp.end()); // this uses split_at_first_point() - } - - // get unsupported edges - Polygons grown_lower; - offset(this->lower_slices, &grown_lower, +this->extrusion_width); - Polylines _unsupported; - diff(bridge_edges, grown_lower, &_unsupported); - - /* Split into individual segments and filter out edges parallel to the bridging angle - TODO: angle tolerance should probably be based on segment length and flow width, - so that we build supports whenever there's a chance that at least one or two bridge - extrusions would be anchored within such length (i.e. a slightly non-parallel bridging - direction might still benefit from anchors if long enough) - double angle_tolerance = PI / 180.0 * 5.0; */ - for (Polylines::const_iterator polyline = _unsupported.begin(); polyline != _unsupported.end(); ++polyline) { - Lines lines = polyline->lines(); - for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { + + Polygons grown_lower = offset(this->lower_slices.expolygons, float(this->spacing)); + + for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) { + // get unsupported bridge edges (both contour and holes) + Polylines unuspported_polylines; + diff(to_polylines(*it_expoly), grown_lower, &unuspported_polylines); + Lines unsupported_lines = to_lines(unuspported_polylines); + /* Split into individual segments and filter out edges parallel to the bridging angle + TODO: angle tolerance should probably be based on segment length and flow width, + so that we build supports whenever there's a chance that at least one or two bridge + extrusions would be anchored within such length (i.e. a slightly non-parallel bridging + direction might still benefit from anchors if long enough) + double angle_tolerance = PI / 180.0 * 5.0; */ + for (Lines::const_iterator line = unsupported_lines.begin(); line != unsupported_lines.end(); ++line) { if (!Slic3r::Geometry::directions_parallel(line->direction(), angle)) unsupported->push_back(*line); } @@ -318,7 +309,7 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const Slic3r::SVG::output( "unsupported_" . rad2deg($angle) . ".svg", expolygons => [$self->expolygon], - green_expolygons => $self->_anchors, + green_expolygons => $self->_anchor_regions, red_expolygons => union_ex($grown_lower), no_arrows => 1, polylines => \@bridge_edges, diff --git a/xs/src/libslic3r/BridgeDetector.hpp b/xs/src/libslic3r/BridgeDetector.hpp index 668646889..825ce10b6 100644 --- a/xs/src/libslic3r/BridgeDetector.hpp +++ b/xs/src/libslic3r/BridgeDetector.hpp @@ -8,20 +8,29 @@ namespace Slic3r { +// The bridge detector optimizes a direction of bridges over a region or a set of regions. +// A bridge direction is considered optimal, if the length of the lines strang over the region is maximal. +// This is optimal if the bridge is supported in a single direction only, but +// it may not likely be optimal, if the bridge region is supported from all sides. Then an optimal +// solution would find a direction with shortest bridges. +// The bridge orientation is measured CCW from the X axis. class BridgeDetector { public: - // The non-grown hole. - ExPolygon expolygon; + // The non-grown holes. + const ExPolygons &expolygons; + // In case the caller gaves us the input polygons by a value, make a copy. + ExPolygons expolygons_owned; // Lower slices, all regions. - ExPolygonCollection lower_slices; + const ExPolygonCollection &lower_slices; // Scaled extrusion width of the infill. - double extrusion_width; + coord_t spacing; // Angle resolution for the brute force search of the best bridging angle. - double resolution; + double resolution; // The final optimal angle. - double angle; + double angle; - BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); + BridgeDetector(ExPolygon _expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); + BridgeDetector(const ExPolygons &_expolygons, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); bool detect_angle(); void coverage(double angle, Polygons* coverage) const; Polygons coverage(double angle = -1) const; @@ -29,10 +38,27 @@ public: Polylines unsupported_edges(double angle = -1) const; private: + void initialize(); + + struct BridgeDirection { + 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; + }; + + // Get possible briging direction candidates. + std::vector bridge_direction_candidates() const; + // Open lines representing the supporting edges. Polylines _edges; // Closed polygons representing the supporting areas. - ExPolygons _anchors; + ExPolygons _anchor_regions; }; } diff --git a/xs/src/libslic3r/ExPolygonCollection.cpp b/xs/src/libslic3r/ExPolygonCollection.cpp index eb5cceb0e..e52498ecb 100644 --- a/xs/src/libslic3r/ExPolygonCollection.cpp +++ b/xs/src/libslic3r/ExPolygonCollection.cpp @@ -116,9 +116,9 @@ Polygons ExPolygonCollection::contours() const { Polygons contours; - for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { + contours.reserve(this->expolygons.size()); + for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) contours.push_back(it->contour); - } return contours; } diff --git a/xs/src/libslic3r/Fill/Fill.cpp b/xs/src/libslic3r/Fill/Fill.cpp index bcfed56f0..d840d44c0 100644 --- a/xs/src/libslic3r/Fill/Fill.cpp +++ b/xs/src/libslic3r/Fill/Fill.cpp @@ -13,6 +13,17 @@ namespace Slic3r { +struct SurfaceGroupAttrib +{ + SurfaceGroupAttrib() : is_solid(false), fw(0.f), pattern(-1) {} + bool operator==(const SurfaceGroupAttrib &other) const + { return is_solid == other.is_solid && fw == other.fw && pattern == other.pattern; } + bool is_solid; + float fw; + // pattern is of type InfillPattern, -1 for an unset pattern. + int pattern; +}; + // Generate infills for Slic3r::Layer::Region. // The Slic3r::Layer::Region at this point of time may contain // surfaces of various types (internal/bridge/top/bottom/solid). @@ -34,11 +45,11 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // in case of bridge surfaces, the ones with defined angle will be attached to the ones // without any angle (shouldn't this logic be moved to process_external_surfaces()?) { - SurfacesPtr surfaces_with_bridge_angle; - surfaces_with_bridge_angle.reserve(layerm.fill_surfaces.surfaces.size()); + Polygons polygons_bridged; + polygons_bridged.reserve(layerm.fill_surfaces.surfaces.size()); for (Surfaces::iterator it = layerm.fill_surfaces.surfaces.begin(); it != layerm.fill_surfaces.surfaces.end(); ++ it) if (it->bridge_angle >= 0) - surfaces_with_bridge_angle.push_back(&(*it)); + polygons_append(polygons_bridged, *it); // group surfaces by distinct properties (equal surface_type, thickness, thickness_layers, bridge_angle) // group is of type Slic3r::SurfaceCollection @@ -50,33 +61,29 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) { // cache flow widths and patterns used for all solid groups // (we'll use them for comparing compatible groups) - std::vector is_solid(groups.size(), false); - std::vector fw(groups.size(), 0.f); - std::vector pattern(groups.size(), -1); + std::vector group_attrib(groups.size()); for (size_t i = 0; i < groups.size(); ++ i) { // we can only merge solid non-bridge surfaces, so discard // non-solid surfaces const Surface &surface = *groups[i].front(); if (surface.is_solid() && (!surface.is_bridge() || layerm.layer()->id() == 0)) { - is_solid[i] = true; - fw[i] = (surface.surface_type == stTop) ? top_solid_infill_flow.width : solid_infill_flow.width; - pattern[i] = surface.is_external() ? layerm.region()->config.external_fill_pattern.value : ipRectilinear; + group_attrib[i].is_solid = true; + group_attrib[i].fw = (surface.surface_type == stTop) ? top_solid_infill_flow.width : solid_infill_flow.width; + group_attrib[i].pattern = surface.is_external() ? layerm.region()->config.external_fill_pattern.value : ipRectilinear; } } - // loop through solid groups + // Loop through solid groups, find compatible groups and append them to this one. for (size_t i = 0; i < groups.size(); ++ i) { - if (is_solid[i]) { - // find compatible groups and append them to this one - for (size_t j = i + 1; j < groups.size(); ++ j) { - if (is_solid[j] && fw[i] == fw[j] && pattern[i] == pattern[j]) { - // groups are compatible, merge them - groups[i].insert(groups[i].end(), groups[j].begin(), groups[j].end()); - groups.erase(groups.begin() + j); - is_solid.erase(is_solid.begin() + j); - fw.erase(fw.begin() + j); - pattern.erase(pattern.begin() + j); - } - } + if (! group_attrib[i].is_solid) + continue; + for (size_t j = i + 1; j < groups.size();) { + if (group_attrib[i] == group_attrib[j]) { + // groups are compatible, merge them + groups[i].insert(groups[i].end(), groups[j].begin(), groups[j].end()); + groups.erase(groups.begin() + j); + group_attrib.erase(group_attrib.begin() + j); + } else + ++ j; } } } @@ -91,13 +98,12 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // Make a union of polygons defining the infiill regions of a group, use a safety offset. Polygons union_p = union_(to_polygons(*it_group), true); // Subtract surfaces having a defined bridge_angle from any other, use a safety offset. - if (! surfaces_with_bridge_angle.empty() && it_group->front()->bridge_angle < 0) - union_p = diff(union_p, to_polygons(surfaces_with_bridge_angle), true); + if (! polygons_bridged.empty() && ! is_bridge) + union_p = diff(union_p, polygons_bridged, true); // subtract any other surface already processed //FIXME Vojtech: Because the bridge surfaces came first, they are subtracted twice! - ExPolygons union_expolys = diff_ex(union_p, to_polygons(surfaces), true); - for (ExPolygons::const_iterator it_expoly = union_expolys.begin(); it_expoly != union_expolys.end(); ++ it_expoly) - surfaces.push_back(Surface(*it_group->front(), *it_expoly)); + // Using group.front() as a template. + surfaces_append(surfaces, diff_ex(union_p, to_polygons(surfaces), true), *group.front()); } } } @@ -154,7 +160,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) bool is_bridge = layerm.layer()->id() > 0 && surface.is_bridge(); if (surface.is_solid()) { - density = 100; + density = 100.; fill_pattern = (surface.is_external() && ! is_bridge) ? layerm.region()->config.external_fill_pattern.value : ipRectilinear; @@ -224,7 +230,8 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // apply half spacing using this flow's own spacing and generate infill FillParams params; params.density = 0.01 * density; - params.dont_adjust = true; +// params.dont_adjust = true; + params.dont_adjust = false; Polylines polylines = f->fill_surface(&surface, params); if (polylines.empty()) continue; @@ -248,12 +255,9 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // Only concentric fills are not sorted. collection.no_sort = f->no_sort(); for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) { - ExtrusionPath *path = new ExtrusionPath(role); + ExtrusionPath *path = new ExtrusionPath(role, flow.mm3_per_mm(), flow.width, flow.height); collection.entities.push_back(path); path->polyline.points.swap(it->points); - path->mm3_per_mm = flow.mm3_per_mm(); - path->width = flow.width, - path->height = flow.height; } } } @@ -264,14 +268,9 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. // Why the paths are unpacked? for (ExtrusionEntitiesPtr::iterator thin_fill = layerm.thin_fills.entities.begin(); thin_fill != layerm.thin_fills.entities.end(); ++ thin_fill) { - #if 0 - out.entities.push_back((*thin_fill)->clone()); - assert(dynamic_cast(out.entities.back()) != NULL); - #else ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); out.entities.push_back(&collection); collection.entities.push_back((*thin_fill)->clone()); - #endif } } diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index edddbee59..0f916c2bf 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -99,7 +99,7 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #if 0 - SurfaceCollection bottom; + Surfaces bottom; // For all stBottom && stBottomBridge surfaces: for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) { if (!surface->is_bottom()) continue; @@ -112,10 +112,12 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) of very thin (but still working) anchors, the grown expolygon would go beyond them */ double angle = -1; if (lower_layer != NULL) { + ExPolygons expolygons; + expolygons.push_back(surface->expolygon); BridgeDetector bd( - surface->expolygon, + expolygons, lower_layer->slices, - this->flow(frInfill, this->layer()->height, true).scaled_width() + this->flow(frInfill, true, this->layer()->height).scaled_width() ); #ifdef SLIC3R_DEBUG @@ -126,8 +128,7 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) angle = bd.angle; if (this->layer()->object()->config.support_material) { - Polygons coverage = bd.coverage(); - this->bridged.insert(this->bridged.end(), coverage.begin(), coverage.end()); + polygons_append(this->bridged, bd.coverage()); this->unsupported_bridge_edges.append(bd.unsupported_edges()); } } @@ -137,28 +138,30 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) Surface s = *surface; s.expolygon = *it; s.bridge_angle = angle; - bottom.surfaces.push_back(s); + bottom.push_back(s); } } #else // 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset // for better anchoring. - SurfaceCollection bottom; - SurfaceCollection bridges; - std::vector bridge_bboxes; + // Bottom surfaces, grown. + Surfaces bottom; + // Bridge surfaces, initialy not grown. + Surfaces bridges; + // Bridge expolygons, grown, to be tested for intersection with other bridge regions. + std::vector bridges_grown; + // Bounding boxes of bridges_grown. + std::vector bridge_bboxes; // For all stBottom && stBottomBridge surfaces: for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) { - if (!surface->is_bottom()) continue; - // Grown by 3mm. - ExPolygons grown = offset_ex(surface->expolygon, +margin); - for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) { - Surface s = *surface; - s.expolygon = *it; - if (surface->surface_type == stBottomBridge) { - bridges.surfaces.push_back(s); - bridge_bboxes.push_back(get_extents(s)); - } else - bottom.surfaces.push_back(s); + if (surface->surface_type == stBottom || lower_layer == NULL) { + // Grown by 3mm. + surfaces_append(bottom, offset_ex(surface->expolygon, float(margin)), *surface); + } else if (surface->surface_type == stBottomBridge) { + bridges.push_back(*surface); + // Grown by 3mm. + bridges_grown.push_back(offset(surface->expolygon, float(margin))); + bridge_bboxes.push_back(get_extents(bridges_grown.back())); } } @@ -169,202 +172,163 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) } #endif - // 2) Group the bridge surfaces by overlaps. - std::vector bridge_group(bridges.surfaces.size(), (size_t)-1); - size_t n_groups = 0; - for (size_t i = 0; i < bridges.surfaces.size(); ++ i) { - // A grup id for this bridge. - size_t group_id = (bridge_group[i] == -1) ? (n_groups ++) : bridge_group[i]; - bridge_group[i] = group_id; - // For all possibly overlaping bridges: - for (size_t j = i + 1; j < bridges.surfaces.size(); ++ j) { - if (! bridge_bboxes[i].overlap(bridge_bboxes[j])) - continue; - if (! bridges.surfaces[i].expolygon.overlaps(bridges.surfaces[j].expolygon)) - continue; - // The two bridge regions intersect. Give them the same group id. - if (bridge_group[j] != -1) { - // The j'th bridge has been merged with some other bridge before. - size_t group_id_new = bridge_group[j]; - for (size_t k = 0; k < j; ++ k) - if (bridge_group[k] == group_id) - bridge_group[k] = group_id_new; - group_id = group_id_new; - } - bridge_group[j] = group_id; - } - } - - // 3) Merge the groups with the same group id. + if (lower_layer != NULL) { - SurfaceCollection bridges_merged; - bridges_merged.surfaces.reserve(n_groups); - for (size_t group_id = 0; group_id < n_groups; ++ group_id) { - size_t n_bridges_merged = 0; - size_t idx_last = (size_t)-1; - for (size_t i = 0; i < bridges.surfaces.size(); ++ i) { - if (bridge_group[i] == group_id) { - ++ n_bridges_merged; - idx_last = i; + // 2) Group the bridge surfaces by overlaps. + std::vector bridge_group(bridges.size(), (size_t)-1); + size_t n_groups = 0; + for (size_t i = 0; i < bridges.size(); ++ i) { + // A grup id for this bridge. + size_t group_id = (bridge_group[i] == -1) ? (n_groups ++) : bridge_group[i]; + bridge_group[i] = group_id; + // For all possibly overlaping bridges: + for (size_t j = i + 1; j < bridges.size(); ++ j) { + if (! bridge_bboxes[i].overlap(bridge_bboxes[j])) + continue; + if (intersection(bridges_grown[i], bridges_grown[j], false).empty()) + continue; + // The two bridge regions intersect. Give them the same group id. + if (bridge_group[j] != -1) { + // The j'th bridge has been merged with some other bridge before. + size_t group_id_new = bridge_group[j]; + for (size_t k = i; k < j; ++ k) + if (bridge_group[k] == group_id) + bridge_group[k] = group_id_new; + group_id = group_id_new; } + bridge_group[j] = group_id; } - if (n_bridges_merged == 1) - bridges_merged.surfaces.push_back(bridges.surfaces[idx_last]); - else if (n_bridges_merged > 1) { - Slic3r::Polygons polygons; - for (size_t i = 0; i < bridges.surfaces.size(); ++ i) { + } + + // 3) Merge the groups with the same group id, detect bridges. + { + for (size_t group_id = 0; group_id < n_groups; ++ group_id) { + size_t n_bridges_merged = 0; + size_t idx_last = (size_t)-1; + for (size_t i = 0; i < bridges.size(); ++ i) { + if (bridge_group[i] == group_id) { + ++ n_bridges_merged; + idx_last = i; + } + } + if (n_bridges_merged == 0) + // This group has no regions assigned as these were moved into another group. + continue; + // Collect the initial ungrown regions and the grown polygons. + ExPolygons initial; + Polygons grown; + for (size_t i = 0; i < bridges.size(); ++ i) { if (bridge_group[i] != group_id) continue; - const Surface &surface = bridges.surfaces[i]; - polygons.push_back(surface.expolygon.contour); - for (size_t j = 0; j < surface.expolygon.holes.size(); ++ j) - polygons.push_back(surface.expolygon.holes[j]); + initial.push_back(STDMOVE(bridges[i].expolygon)); + polygons_append(grown, bridges_grown[i]); + } + // detect bridge direction before merging grown surfaces otherwise adjacent bridges + // would get merged into a single one while they need different directions + // also, supply the original expolygon instead of the grown one, because in case + // of very thin (but still working) anchors, the grown expolygon would go beyond them + BridgeDetector bd( + initial, + lower_layer->slices, + //FIXME parameters are not correct! + // flow(FlowRole role, bool bridge = false, double width = -1) const; + this->flow(frInfill, true, this->layer()->height).scaled_width() + ); + #ifdef SLIC3R_DEBUG + printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id()); + #endif + if (bd.detect_angle()) { + bridges[idx_last].bridge_angle = bd.angle; + if (this->layer()->object()->config.support_material) { + polygons_append(this->bridged, bd.coverage()); + this->unsupported_bridge_edges.append(bd.unsupported_edges()); + } } - ExPolygons expp; // without safety offset, artifacts are generated (GH #2494) - union_(polygons, &expp, true); - Surface &surface0 = bridges.surfaces[idx_last]; - surface0.expolygon.clear(); - for (size_t i = 0; i < expp.size(); ++ i) { - surface0.expolygon = expp[i]; - bridges_merged.surfaces.push_back(surface0); - } + surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]); } } - std::swap(bridges_merged, bridges); - } -#if 0 - { - static int iRun = 0; - bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun ++), true); + #if 0 + { + static int iRun = 0; + bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun ++), true); + } + #endif } #endif - // 4) Detect bridge directions. - if (lower_layer != NULL) { - for (size_t i = 0; i < bridges.surfaces.size(); ++ i) { - Surface &surface = bridges.surfaces[i]; - /* detect bridge direction before merging grown surfaces otherwise adjacent bridges - would get merged into a single one while they need different directions - also, supply the original expolygon instead of the grown one, because in case - of very thin (but still working) anchors, the grown expolygon would go beyond them */ - BridgeDetector bd( - surface.expolygon, - lower_layer->slices, - //FIXME parameters are not correct! - // flow(FlowRole role, bool bridge = false, double width = -1) const; - this->flow(frInfill, this->layer()->height, true).scaled_width() - ); - #ifdef SLIC3R_DEBUG - printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id()); - #endif - if (bd.detect_angle()) { - surface.bridge_angle = bd.angle; - if (this->layer()->object()->config.support_material) { - Polygons coverage = bd.coverage(); - this->bridged.insert(this->bridged.end(), coverage.begin(), coverage.end()); - this->unsupported_bridge_edges.append(bd.unsupported_edges()); - } - } + // Collect top surfaces and internal surfaces. + // Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill. + Surfaces top; + Surfaces internal; + Polygons fill_boundaries; + // This loop destroys the surfaces (aliasing this->fill_surfaces.surfaces) by moving into top/internal/fill_boundaries! + { + // bottom_polygons are used to trim inflated top surfaces. + const Polygons bottom_polygons = to_polygons(bottom); + fill_boundaries.reserve(number_polygons(surfaces)); + bool has_infill = this->region()->config.fill_density.value > 0.; + for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { + if (surface->surface_type == stTop) + // Collect the top surfaces, inflate them and trim them by the bottom surfaces. + // This gives the priority to bottom surfaces. + surfaces_append( + top, + STDMOVE(diff_ex(offset(surface->expolygon, float(margin)), bottom_polygons)), + *surface); // template + bool internal_surface = surface->surface_type != stTop && ! surface->is_bottom(); + if (has_infill || surface->surface_type != stInternal) { + if (internal_surface) + // Make a copy as the following line uses the move semantics. + internal.push_back(*surface); + polygons_append(fill_boundaries, STDMOVE(surface->expolygon)); + } else if (internal_surface) + internal.push_back(STDMOVE(*surface)); } } - bottom.surfaces.insert(bottom.surfaces.end(), bridges.surfaces.begin(), bridges.surfaces.end()); -#endif - SurfaceCollection top; - for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) { - if (surface->surface_type != stTop) continue; - - // give priority to bottom surfaces - ExPolygons grown = diff_ex( - offset(surface->expolygon, +margin), - (Polygons)bottom - ); - for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) { - Surface s = *surface; - s.expolygon = *it; - top.surfaces.push_back(s); + Surfaces new_surfaces; + + // Merge top and bottom in a single collection. + surfaces_append(top, STDMOVE(bottom)); + // Intersect the grown surfaces with the actual fill boundaries. + for (size_t i = 0; i < top.size(); ++ i) { + Surface &s1 = top[i]; + if (s1.empty()) + continue; + Polygons polys; + polygons_append(polys, STDMOVE(s1)); + for (size_t j = i + 1; j < top.size(); ++ j) { + Surface &s2 = top[j]; + if (! s2.empty() && surfaces_could_merge(s1, s2)) + polygons_append(polys, STDMOVE(s2)); } + surfaces_append( + new_surfaces, + STDMOVE(intersection_ex(polys, fill_boundaries, true)), + s1); } - /* if we're slicing with no infill, we can't extend external surfaces - over non-existent infill */ - SurfaceCollection fill_boundaries; - if (this->region()->config.fill_density.value > 0) { - fill_boundaries = SurfaceCollection(surfaces); - } else { - for (Surfaces::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it) { - if (it->surface_type != stInternal) - fill_boundaries.surfaces.push_back(*it); + // Subtract the new top surfaces from the other non-top surfaces and re-add them. + Polygons new_polygons = to_polygons(new_surfaces); + for (size_t i = 0; i < internal.size(); ++ i) { + Surface &s1 = internal[i]; + if (s1.empty()) + continue; + Polygons polys; + polygons_append(polys, STDMOVE(s1)); + for (size_t j = i + 1; j < internal.size(); ++ j) { + Surface &s2 = internal[j]; + if (! s2.empty() && surfaces_could_merge(s1, s2)) + polygons_append(polys, STDMOVE(s2)); } + ExPolygons new_expolys = diff_ex(polys, new_polygons); + polygons_append(new_polygons, to_polygons(new_expolys)); + surfaces_append(new_surfaces, STDMOVE(new_expolys), s1); } - // intersect the grown surfaces with the actual fill boundaries - SurfaceCollection new_surfaces; - { - // merge top and bottom in a single collection - SurfaceCollection tb = top; - tb.surfaces.insert(tb.surfaces.end(), bottom.surfaces.begin(), bottom.surfaces.end()); - - // group surfaces - std::vector groups; - tb.group(&groups); - - for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { - Polygons subject; - for (SurfacesPtr::const_iterator s = g->begin(); s != g->end(); ++s) { - Polygons pp = **s; - subject.insert(subject.end(), pp.begin(), pp.end()); - } - - ExPolygons expp = intersection_ex( - subject, - (Polygons)fill_boundaries, - true // to ensure adjacent expolygons are unified - ); - - for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) { - Surface s = *g->front(); - s.expolygon = *ex; - new_surfaces.surfaces.push_back(s); - } - } - } - - /* subtract the new top surfaces from the other non-top surfaces and re-add them */ - { - SurfaceCollection other; - for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) { - if (s->surface_type != stTop && !s->is_bottom()) - other.surfaces.push_back(*s); - } - - // group surfaces - std::vector groups; - other.group(&groups); - - for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { - Polygons subject; - for (SurfacesPtr::const_iterator s = g->begin(); s != g->end(); ++s) { - Polygons pp = **s; - subject.insert(subject.end(), pp.begin(), pp.end()); - } - - ExPolygons expp = diff_ex( - subject, - (Polygons)new_surfaces - ); - - for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) { - Surface s = *g->front(); - s.expolygon = *ex; - new_surfaces.surfaces.push_back(s); - } - } - } - - this->fill_surfaces = new_surfaces; + this->fill_surfaces.surfaces = STDMOVE(new_surfaces); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); diff --git a/xs/xsp/BridgeDetector.xsp b/xs/xsp/BridgeDetector.xsp index 9670bda3f..c7ac409df 100644 --- a/xs/xsp/BridgeDetector.xsp +++ b/xs/xsp/BridgeDetector.xsp @@ -29,5 +29,15 @@ BridgeDetector::new(expolygon, lower_slices, extrusion_width) OUTPUT: RETVAL +BridgeDetector* +BridgeDetector::new_expolygons(expolygons, lower_slices, extrusion_width) + ExPolygonCollection* expolygons; + ExPolygonCollection* lower_slices; + long extrusion_width; + CODE: + RETVAL = new BridgeDetector(expolygons->expolygons, *lower_slices, extrusion_width); + OUTPUT: + RETVAL + %} };