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
This commit is contained in:
bubnikv 2016-11-08 09:59:25 +01:00
parent 5a81731577
commit 22ca927f12
7 changed files with 433 additions and 443 deletions

View File

@ -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

View File

@ -5,37 +5,48 @@
namespace Slic3r {
class BridgeDirectionComparator {
public:
std::map<double,double> 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();
}
// 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);
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 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<BridgeDirection> candidates;
{
std::vector<double> 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<double> BridgeDetector::bridge_direction_candidates() const
{
// we test angles according to configured resolution
std::vector<double> 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
@ -108,90 +200,7 @@ BridgeDetector::detect_angle()
if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution))
angles.pop_back();
BridgeDirectionComparator bdcomp(this->extrusion_width);
std::map<double,double> dir_avg_length;
double line_increment = this->extrusion_width;
bool have_coverage = false;
for (std::vector<double>::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<double> 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<double>::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
@ -200,56 +209,46 @@ 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));
// 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
intersection(_coverage, this->expolygon, coverage);
// 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) {
@ -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,
);
@ -285,28 +284,20 @@ 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()
}
Polygons grown_lower = offset(this->lower_slices.expolygons, float(this->spacing));
// 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) {
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,

View File

@ -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<double> bridge_direction_candidates() const;
// Open lines representing the supporting edges.
Polylines _edges;
// Closed polygons representing the supporting areas.
ExPolygons _anchors;
ExPolygons _anchor_regions;
};
}

View File

@ -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;
}

View File

@ -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<char> is_solid(groups.size(), false);
std::vector<float> fw(groups.size(), 0.f);
std::vector<int> pattern(groups.size(), -1);
std::vector<SurfaceGroupAttrib> 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<ExtrusionEntityCollection*>(out.entities.back()) != NULL);
#else
ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection());
out.entities.push_back(&collection);
collection.entities.push_back((*thin_fill)->clone());
#endif
}
}

View File

@ -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<BoundingBox> 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<Polygons> bridges_grown;
// Bounding boxes of bridges_grown.
std::vector<BoundingBox> 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<size_t> 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<size_t> 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());
}
}
}
}
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);
}
}
/* 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);
}
}
// intersect the grown surfaces with the actual fill boundaries
SurfaceCollection new_surfaces;
// 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!
{
// 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<SurfacesPtr> groups;
tb.group(&groups);
for (std::vector<SurfacesPtr>::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);
}
// 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));
}
}
/* 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<SurfacesPtr> groups;
other.group(&groups);
for (std::vector<SurfacesPtr>::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);
}
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);
}
this->fill_surfaces = new_surfaces;
// 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);
}
this->fill_surfaces.surfaces = STDMOVE(new_surfaces);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final");

View File

@ -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
%}
};