Improve bridge detection algorithm. #2477

This commit is contained in:
Alessandro Ranellucci 2017-04-03 20:49:45 +02:00
parent 4926e2c76c
commit 91e7ef7a66
9 changed files with 116 additions and 70 deletions

View File

@ -1,4 +1,4 @@
use Test::More tests => 16; use Test::More tests => 18;
use strict; use strict;
use warnings; use warnings;
@ -82,13 +82,11 @@ use Slic3r::Test;
ok check_angle($lower, $bridge, 45, undef, $bridge->area/2), 'correct bridge angle for square overhang with L-shaped anchors'; ok check_angle($lower, $bridge, 45, undef, $bridge->area/2), 'correct bridge angle for square overhang with L-shaped anchors';
} }
if (0) { {
# GH #2477: # GH #2477: This test case failed when we computed coverage by summing length of centerlines
# This rectangle-shaped bridge is actually unsupported (i.e. the potential anchors are # instead of summing their covered area.
# a bit far away from the contour of the bridge area) because perimeters are reducing
# its area.
my $bridge = Slic3r::ExPolygon->new( my $bridge = Slic3r::ExPolygon->new(
Slic3r::Polygon->new([30023195,14023195],[1776805,14023195],[1776805,1776805],[30023195,1776805]), Slic3r::Polygon->new([30299990,14299990],[1500010,14299990],[1500010,1500010],[30299990,1500010]),
); );
my $lower = [ my $lower = [
Slic3r::ExPolygon->new( Slic3r::ExPolygon->new(

View File

@ -3,6 +3,8 @@
#include "Geometry.hpp" #include "Geometry.hpp"
#include <algorithm> #include <algorithm>
#include "SVG.hpp"
namespace Slic3r { namespace Slic3r {
BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices, BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices,
@ -32,7 +34,7 @@ BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonColle
svg.draw(this->expolygon); svg.draw(this->expolygon);
svg.draw(this->lower_slices, "red"); svg.draw(this->lower_slices, "red");
svg.draw(this->_anchors, "yellow"); svg.draw(this->_anchors, "yellow");
//svg.draw(this->_edges, "black", scale_(0.2)); svg.draw(this->_edges, "black", scale_(0.2));
svg.Close(); svg.Close();
} }
#endif #endif
@ -63,14 +65,9 @@ BridgeDetector::detect_angle()
angles.push_back(i * this->resolution); angles.push_back(i * this->resolution);
// we also test angles of each bridge contour // we also test angles of each bridge contour
{ for (const Polygon &p : (Polygons)this->expolygon)
Polygons pp = this->expolygon; for (const Line &line : p.lines())
for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { angles.push_back(line.direction());
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 /* we also test angles of each open supporting edge
(this finds the optimal angle for C-shaped supports) */ (this finds the optimal angle for C-shaped supports) */
@ -97,7 +94,7 @@ BridgeDetector::detect_angle()
candidates.push_back(BridgeDirection(angle)); candidates.push_back(BridgeDirection(angle));
} }
const double line_increment = this->extrusion_width; const coord_t line_increment = this->extrusion_width;
bool have_coverage = false; bool have_coverage = false;
for (BridgeDirection &candidate : candidates) { for (BridgeDirection &candidate : candidates) {
Polygons my_clip_area = clip_area; Polygons my_clip_area = clip_area;
@ -120,30 +117,24 @@ BridgeDetector::detect_angle()
const Lines clipped_lines = intersection_ln(lines, my_clip_area); const Lines clipped_lines = intersection_ln(lines, my_clip_area);
std::vector<double> lengths;
double total_length = 0;
for (const Line &line : clipped_lines) { for (const Line &line : clipped_lines) {
// skip any line not having both endpoints within anchors // skip any line not having both endpoints within anchors
if (!Slic3r::Geometry::contains(my_anchors, line.a) if (!Slic3r::Geometry::contains(my_anchors, line.a)
|| !Slic3r::Geometry::contains(my_anchors, line.b)) || !Slic3r::Geometry::contains(my_anchors, line.b))
continue; continue;
const double len = line.length(); candidate.max_length = std::max(candidate.max_length, line.length());
lengths.push_back(len); // Calculate coverage as actual covered area, because length of centerlines
total_length += len; // is not accurate enough when such lines are slightly skewed and not parallel
// to the sides; calculating area will compute them as triangles.
// TODO: use a faster algorithm for computing covered area by using a sweep line
// instead of intersecting many lines.
candidate.coverage += Slic3r::Geometry::area(intersection(
my_clip_area,
offset((Polyline)line, +this->extrusion_width/2)
));
} }
if (total_length) have_coverage = true; if (candidate.coverage > 0) have_coverage = true;
// sum length of bridged lines
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
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 no direction produced coverage, then there's no bridge direction
@ -187,21 +178,21 @@ BridgeDetector::coverage(double angle) const
// Compute trapezoids according to a vertical orientation // Compute trapezoids according to a vertical orientation
Polygons trapezoids; Polygons trapezoids;
for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) for (const ExPolygon &e : grown)
it->get_trapezoids2(&trapezoids, PI/2.0); e.get_trapezoids2(&trapezoids, PI/2.0);
// get anchors, convert them to Polygons and rotate them too // get anchors, convert them to Polygons and rotate them too
Polygons anchors; Polygons anchors;
for (ExPolygons::const_iterator anchor = this->_anchors.begin(); anchor != this->_anchors.end(); ++anchor) { for (const ExPolygon &anchor : this->_anchors) {
Polygons pp = *anchor; Polygons pp = anchor;
for (Polygons::iterator p = pp.begin(); p != pp.end(); ++p) for (Polygon &p : pp)
p->rotate(PI/2.0 - angle, Point(0,0)); p.rotate(PI/2.0 - angle, Point(0,0));
anchors.insert(anchors.end(), pp.begin(), pp.end()); append_to(anchors, pp);
} }
Polygons covered; Polygons covered;
for (Polygons::const_iterator trapezoid = trapezoids.begin(); trapezoid != trapezoids.end(); ++trapezoid) { for (const Polygon &trapezoid : trapezoids) {
Lines supported = intersection_ln(trapezoid->lines(), anchors); Lines supported = intersection_ln(trapezoid.lines(), anchors);
// not nice, we need a more robust non-numeric check // not nice, we need a more robust non-numeric check
for (size_t i = 0; i < supported.size(); ++i) { for (size_t i = 0; i < supported.size(); ++i) {
@ -211,7 +202,7 @@ BridgeDetector::coverage(double angle) const
} }
} }
if (supported.size() >= 2) covered.push_back(*trapezoid); if (supported.size() >= 2) covered.push_back(trapezoid);
} }
// merge trapezoids and rotate them back // merge trapezoids and rotate them back
@ -251,10 +242,8 @@ BridgeDetector::unsupported_edges(double angle) const
// get bridge edges (both contour and holes) // get bridge edges (both contour and holes)
Polylines bridge_edges; Polylines bridge_edges;
{ for (const Polygon &p : (Polygons)this->expolygon)
Polygons pp = this->expolygon; bridge_edges.push_back(p.split_at_first_point());
bridge_edges.insert(bridge_edges.end(), pp.begin(), pp.end()); // this uses split_at_first_point()
}
// get unsupported edges // get unsupported edges
Polylines _unsupported = diff_pl( Polylines _unsupported = diff_pl(
@ -269,13 +258,10 @@ BridgeDetector::unsupported_edges(double angle) const
direction might still benefit from anchors if long enough) direction might still benefit from anchors if long enough)
double angle_tolerance = PI / 180.0 * 5.0; */ double angle_tolerance = PI / 180.0 * 5.0; */
Polylines unsupported; Polylines unsupported;
for (Polylines::const_iterator polyline = _unsupported.begin(); polyline != _unsupported.end(); ++polyline) { for (const Polyline &polyline : _unsupported)
Lines lines = polyline->lines(); for (const Line &line : polyline.lines())
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { if (!Slic3r::Geometry::directions_parallel(line.direction(), angle))
if (!Slic3r::Geometry::directions_parallel(line->direction(), angle)) unsupported.push_back(line);
unsupported.push_back(*line);
}
}
return unsupported; return unsupported;
/* /*

View File

@ -15,7 +15,7 @@ public:
// Lower slices, all regions. // Lower slices, all regions.
ExPolygonCollection lower_slices; ExPolygonCollection lower_slices;
// Scaled extrusion width of the infill. // Scaled extrusion width of the infill.
double extrusion_width; coord_t extrusion_width;
// Angle resolution for the brute force search of the best bridging angle. // Angle resolution for the brute force search of the best bridging angle.
double resolution; double resolution;
// The final optimal angle. // The final optimal angle.

View File

@ -296,13 +296,27 @@ template<class T>
bool bool
contains(const std::vector<T> &vector, const Point &point) contains(const std::vector<T> &vector, const Point &point)
{ {
for (typename std::vector<T>::const_iterator it = vector.begin(); it != vector.end(); ++it) { for (const T &it : vector)
if (it->contains(point)) return true; if (it.contains(point))
} return true;
return false; return false;
} }
template bool contains(const Polygons &vector, const Point &point);
template bool contains(const ExPolygons &vector, const Point &point); template bool contains(const ExPolygons &vector, const Point &point);
template<class T>
double
area(const std::vector<T> &vector)
{
double area = 0;
for (const T &it : vector)
area += it.area();
return area;
}
template double area(const Polygons &vector);
double double
rad2deg(double angle) rad2deg(double angle)
{ {

View File

@ -20,6 +20,7 @@ void chained_path(const Points &points, std::vector<Points::size_type> &retval);
template<class T> void chained_path_items(Points &points, T &items, T &retval); template<class T> void chained_path_items(Points &points, T &items, T &retval);
bool directions_parallel(double angle1, double angle2, double max_diff = 0); bool directions_parallel(double angle1, double angle2, double max_diff = 0);
template<class T> bool contains(const std::vector<T> &vector, const Point &point); template<class T> bool contains(const std::vector<T> &vector, const Point &point);
template<class T> double area(const std::vector<T> &vector);
double rad2deg(double angle); double rad2deg(double angle);
double rad2deg_dir(double angle); double rad2deg_dir(double angle);
double deg2rad(double angle); double deg2rad(double angle);

View File

@ -1,10 +1,16 @@
#include "Layer.hpp" #include "Layer.hpp"
#include "BridgeDetector.hpp" #include "BridgeDetector.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "PerimeterGenerator.hpp" #include "PerimeterGenerator.hpp"
#include "Print.hpp" #include "Print.hpp"
#include "Surface.hpp" #include "Surface.hpp"
#include "SVG.hpp"
namespace Slic3r { namespace Slic3r {
Flow Flow
@ -65,19 +71,50 @@ LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection*
g.process(); g.process();
} }
// This function reads lower_layer->slices and writes this->bridged and this->fill_surfaces, // This function reads layer->slices andlower_layer->slices
// so it's thread-safe. // and writes this->bridged and this->fill_surfaces, so it's thread-safe.
void void
LayerRegion::process_external_surfaces() LayerRegion::process_external_surfaces()
{ {
const Surfaces &surfaces = this->fill_surfaces.surfaces; Surfaces &surfaces = this->fill_surfaces.surfaces;
for (size_t j = 0; j < surfaces.size(); ++j) {
Surface &surface = surfaces[j];
if (this->layer()->lower_layer != NULL && surface.is_bridge()) {
// If this bridge has one or more holes that are internal surfaces
// (thus not visible from the outside), like a slab sustained by
// pillars, include them in the bridge in order to have better and
// more continuous bridging.
Polygons &holes = surface.expolygon.holes;
for (int i = 0; i < holes.size(); ++i) {
// reverse the hole and consider it a polygon
Polygon h = holes[i];
h.reverse();
// Is this hole fully contained in the layer slices?
if (diff(h, this->layer()->slices).empty()) {
// remove any other surface contained in this hole
for (int k = 0; k < surfaces.size(); ++k) {
if (k == j) continue;
if (h.contains(surfaces[k].expolygon.contour.first_point())) {
surfaces.erase(surfaces.begin() + k);
--k;
}
}
holes.erase(holes.begin() + i);
--i;
}
}
}
}
SurfaceCollection bottom; SurfaceCollection bottom;
Polygons removed_holes;
for (const Surface &surface : surfaces) { for (const Surface &surface : surfaces) {
if (!surface.is_bottom()) continue; if (!surface.is_bottom()) continue;
const ExPolygons grown = offset_ex(surface.expolygon, +SCALED_EXTERNAL_INFILL_MARGIN);
/* detect bridge direction before merging grown surfaces otherwise adjacent bridges /* detect bridge direction before merging grown surfaces otherwise adjacent bridges
would get merged into a single one while they need different directions 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 also, supply the original expolygon instead of the grown one, because in case
@ -104,6 +141,7 @@ LayerRegion::process_external_surfaces()
} }
} }
const ExPolygons grown = offset_ex(surface.expolygon, +SCALED_EXTERNAL_INFILL_MARGIN);
Surface templ = surface; Surface templ = surface;
templ.bridge_angle = angle; templ.bridge_angle = angle;
bottom.append(grown, templ); bottom.append(grown, templ);

View File

@ -92,10 +92,9 @@ SVG::draw(const ExPolygon &expolygon, std::string fill)
this->fill = fill; this->fill = fill;
std::string d; std::string d;
Polygons pp = expolygon; for (const Polygon &p : (Polygons)expolygon)
for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { d += this->get_path_d(p, true) + " ";
d += this->get_path_d(*p, true) + " ";
}
this->path(d, true); this->path(d, true);
} }
@ -105,11 +104,18 @@ SVG::draw(const ExPolygonCollection &coll, std::string fill)
this->draw(coll.expolygons, fill); this->draw(coll.expolygons, fill);
} }
void
SVG::draw(const SurfaceCollection &coll, std::string fill)
{
for (const Surface &s : coll.surfaces)
this->draw(s.expolygon, fill);
}
void void
SVG::draw(const ExPolygons &expolygons, std::string fill) SVG::draw(const ExPolygons &expolygons, std::string fill)
{ {
for (ExPolygons::const_iterator it = expolygons.begin(); it != expolygons.end(); ++it) for (const ExPolygon &e : expolygons)
this->draw(*it, fill); this->draw(e, fill);
} }
void void

View File

@ -5,6 +5,7 @@
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include "ExPolygonCollection.hpp" #include "ExPolygonCollection.hpp"
#include "Line.hpp" #include "Line.hpp"
#include "SurfaceCollection.hpp"
#include "TriangleMesh.hpp" #include "TriangleMesh.hpp"
namespace Slic3r { namespace Slic3r {
@ -26,6 +27,7 @@ class SVG
void draw(const ExPolygon &expolygon, std::string fill = "grey"); void draw(const ExPolygon &expolygon, std::string fill = "grey");
void draw(const ExPolygons &expolygons, std::string fill = "grey"); void draw(const ExPolygons &expolygons, std::string fill = "grey");
void draw(const ExPolygonCollection &coll, std::string fill = "grey"); void draw(const ExPolygonCollection &coll, std::string fill = "grey");
void draw(const SurfaceCollection &coll, std::string fill = "grey");
void draw(const Polygon &polygon, std::string fill = "grey"); void draw(const Polygon &polygon, std::string fill = "grey");
void draw(const Polygons &polygons, std::string fill = "grey"); void draw(const Polygons &polygons, std::string fill = "grey");
void draw(const Polyline &polyline, std::string stroke = "black", coord_t stroke_width = 0); void draw(const Polyline &polyline, std::string stroke = "black", coord_t stroke_width = 0);

View File

@ -41,6 +41,7 @@ class SurfaceCollection
bool empty() const { return this->surfaces.empty(); }; bool empty() const { return this->surfaces.empty(); };
size_t size() const { return this->surfaces.size(); }; size_t size() const { return this->surfaces.size(); };
void clear() { this->surfaces.clear(); }; void clear() { this->surfaces.clear(); };
void erase(size_t i) { this->surfaces.erase(this->surfaces.begin() + i); };
}; };
} }