re-wrote PrintObject::detect_surfaces_type() to C++,
Fixed some cracks in the fill surfaces created by rounding all surfaces inside detect_surface_type(). Fixed https://github.com/prusa3d/Slic3r/issues/12 Bridging-Angle not optimal Extended the "Ensure veritcal wall thickness" mode (merged with the original discover_horizontal_shells function), but this a work in progress. Already Slic3r with "ensure vertical wall thickness" produces less spurious infills inside solids.
This commit is contained in:
parent
317e9131e8
commit
4460b5ce50
@ -669,167 +669,6 @@ sub _support_material {
|
||||
}
|
||||
}
|
||||
|
||||
# This function analyzes slices of a region (SurfaceCollection slices).
|
||||
# Each slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface.
|
||||
# Initially all slices are of type S_TYPE_INTERNAL.
|
||||
# Slices are compared against the top / bottom slices and regions and classified to the following groups:
|
||||
# S_TYPE_TOP - Part of a region, which is not covered by any upper layer. This surface will be filled with a top solid infill.
|
||||
# S_TYPE_BOTTOMBRIDGE - Part of a region, which is not fully supported, but it hangs in the air, or it hangs losely on a support or a raft.
|
||||
# S_TYPE_BOTTOM - Part of a region, which is not supported by the same region, but it is supported either by another region, or by a soluble interface layer.
|
||||
# S_TYPE_INTERNAL - Part of a region, which is supported by the same region type.
|
||||
# If a part of a region is of S_TYPE_BOTTOM and S_TYPE_TOP, the S_TYPE_BOTTOM wins.
|
||||
sub detect_surfaces_type {
|
||||
my $self = shift;
|
||||
Slic3r::debugf "Detecting solid surfaces...\n";
|
||||
|
||||
for my $region_id (0 .. ($self->print->region_count-1)) {
|
||||
for my $i (0 .. ($self->layer_count - 1)) {
|
||||
my $layerm = $self->get_layer($i)->regions->[$region_id];
|
||||
|
||||
# prepare a reusable subroutine to make surface differences
|
||||
my $difference = sub {
|
||||
my ($subject, $clip, $result_type) = @_;
|
||||
my $diff = diff(
|
||||
[ map @$_, @$subject ],
|
||||
[ map @$_, @$clip ],
|
||||
1,
|
||||
);
|
||||
|
||||
# collapse very narrow parts (using the safety offset in the diff is not enough)
|
||||
my $offset = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_width / 10;
|
||||
return map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type),
|
||||
@{ offset2_ex($diff, -$offset, +$offset) };
|
||||
};
|
||||
|
||||
# comparison happens against the *full* slices (considering all regions)
|
||||
# unless internal shells are requested
|
||||
my $upper_layer = $i < $self->layer_count - 1 ? $self->get_layer($i+1) : undef;
|
||||
my $lower_layer = $i > 0 ? $self->get_layer($i-1) : undef;
|
||||
|
||||
# find top surfaces (difference between current surfaces
|
||||
# of current layer and upper one)
|
||||
my @top = ();
|
||||
if ($upper_layer) {
|
||||
# Config value $self->config->interface_shells is true, if a support is separated from the object
|
||||
# by a soluble material (for example a PVA plastic).
|
||||
my $upper_slices = $self->config->interface_shells
|
||||
? [ map $_->expolygon, @{$upper_layer->regions->[$region_id]->slices} ]
|
||||
: $upper_layer->slices;
|
||||
|
||||
@top = $difference->(
|
||||
[ map $_->expolygon, @{$layerm->slices} ],
|
||||
$upper_slices,
|
||||
S_TYPE_TOP,
|
||||
);
|
||||
} else {
|
||||
# if no upper layer, all surfaces of this one are solid
|
||||
# we clone surfaces because we're going to clear the slices collection
|
||||
@top = map $_->clone, @{$layerm->slices};
|
||||
$_->surface_type(S_TYPE_TOP) for @top;
|
||||
}
|
||||
|
||||
# find bottom surfaces (difference between current surfaces
|
||||
# of current layer and lower one)
|
||||
my @bottom = ();
|
||||
if ($lower_layer) {
|
||||
# Any surface lying on the void is a true bottom bridge (an overhang)
|
||||
push @bottom, $difference->(
|
||||
[ map $_->expolygon, @{$layerm->slices} ],
|
||||
$lower_layer->slices,
|
||||
S_TYPE_BOTTOMBRIDGE,
|
||||
);
|
||||
|
||||
# If we have soluble support material, don't bridge. The overhang will be squished against a soluble layer separating
|
||||
# the support from the print.
|
||||
if ($self->config->support_material && $self->config->support_material_contact_distance == 0) {
|
||||
$_->surface_type(S_TYPE_BOTTOM) for @bottom;
|
||||
}
|
||||
|
||||
# if user requested internal shells, we need to identify surfaces
|
||||
# lying on other slices not belonging to this region
|
||||
if ($self->config->interface_shells) {
|
||||
# non-bridging bottom surfaces: any part of this layer lying
|
||||
# on something else, excluding those lying on our own region
|
||||
my $supported = intersection_ex(
|
||||
[ map @{$_->expolygon}, @{$layerm->slices} ],
|
||||
[ map @$_, @{$lower_layer->slices} ],
|
||||
);
|
||||
push @bottom, $difference->(
|
||||
$supported,
|
||||
[ map $_->expolygon, @{$lower_layer->regions->[$region_id]->slices} ],
|
||||
S_TYPE_BOTTOM,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
# if no lower layer, all surfaces of this one are solid
|
||||
# we clone surfaces because we're going to clear the slices collection
|
||||
@bottom = map $_->clone, @{$layerm->slices};
|
||||
|
||||
# if we have raft layers, consider bottom layer as a bridge
|
||||
# just like any other bottom surface lying on the void
|
||||
if ($self->config->raft_layers > 0 && $self->config->support_material_contact_distance > 0) {
|
||||
$_->surface_type(S_TYPE_BOTTOMBRIDGE) for @bottom;
|
||||
} else {
|
||||
$_->surface_type(S_TYPE_BOTTOM) for @bottom;
|
||||
}
|
||||
}
|
||||
|
||||
# now, if the object contained a thin membrane, we could have overlapping bottom
|
||||
# and top surfaces; let's do an intersection to discover them and consider them
|
||||
# as bottom surfaces (to allow for bridge detection)
|
||||
if (@top && @bottom) {
|
||||
my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]);
|
||||
Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->layer->id, scalar(@$overlapping)
|
||||
if $Slic3r::debug;
|
||||
@top = $difference->([map $_->expolygon, @top], $overlapping, S_TYPE_TOP);
|
||||
}
|
||||
|
||||
# find internal surfaces (difference between top/bottom surfaces and others)
|
||||
my @internal = $difference->(
|
||||
[ map $_->expolygon, @{$layerm->slices} ],
|
||||
[ map $_->expolygon, @top, @bottom ],
|
||||
S_TYPE_INTERNAL,
|
||||
);
|
||||
|
||||
# save surfaces to layer
|
||||
$layerm->slices->clear;
|
||||
$layerm->slices->append($_) for (@bottom, @top, @internal);
|
||||
|
||||
Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n",
|
||||
$layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug;
|
||||
|
||||
if ($SLIC3R_DEBUG_SLICE_PROCESSING) {
|
||||
$layerm->export_region_slices_to_svg_debug("detect_surfaces_type-final");
|
||||
}
|
||||
} # for each layer of a region
|
||||
|
||||
# clip surfaces to the fill boundaries
|
||||
foreach my $layer (@{$self->layers}) {
|
||||
my $layerm = $layer->regions->[$region_id];
|
||||
|
||||
# Note: this method should be idempotent, but fill_surfaces gets modified
|
||||
# in place. However we're now only using its boundaries (which are invariant)
|
||||
# so we're safe. This guarantees idempotence of prepare_infill() also in case
|
||||
# that combine_infill() turns some fill_surface into VOID surfaces.
|
||||
my $fill_boundaries = [ map $_->clone->p, @{$layerm->fill_surfaces} ];
|
||||
$layerm->fill_surfaces->clear;
|
||||
foreach my $surface (@{$layerm->slices}) {
|
||||
my $intersection = intersection_ex(
|
||||
[ $surface->p ],
|
||||
$fill_boundaries,
|
||||
);
|
||||
$layerm->fill_surfaces->append($_)
|
||||
for map Slic3r::Surface->new(expolygon => $_, surface_type => $surface->surface_type),
|
||||
@$intersection;
|
||||
}
|
||||
|
||||
if ($SLIC3R_DEBUG_SLICE_PROCESSING) {
|
||||
$layerm->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-final");
|
||||
}
|
||||
} # for each layer of a region
|
||||
} # for each $self->print->region_count
|
||||
}
|
||||
|
||||
# Idempotence of this method is guaranteed by the fact that we don't remove things from
|
||||
# fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries.
|
||||
sub clip_fill_surfaces {
|
||||
@ -954,6 +793,9 @@ sub discover_horizontal_shells {
|
||||
$_->surface_type($type) for @{$layerm->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)};
|
||||
}
|
||||
|
||||
# If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells().
|
||||
next if ($layerm->region->config->ensure_vertical_shell_thickness);
|
||||
|
||||
EXTERNAL: foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM, S_TYPE_BOTTOMBRIDGE) {
|
||||
# find slices of current type for current layer
|
||||
# use slices instead of fill_surfaces because they also include the perimeter area
|
||||
|
@ -75,12 +75,6 @@ Layer::clear_regions()
|
||||
this->regions.clear();
|
||||
}
|
||||
|
||||
LayerRegion*
|
||||
Layer::get_region(int idx)
|
||||
{
|
||||
return this->regions.at(idx);
|
||||
}
|
||||
|
||||
LayerRegion*
|
||||
Layer::add_region(PrintRegion* print_region)
|
||||
{
|
||||
|
@ -61,6 +61,7 @@ class LayerRegion
|
||||
|
||||
Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
|
||||
void merge_slices();
|
||||
void slices_to_fill_surfaces_clipped();
|
||||
void prepare_fill_surfaces();
|
||||
void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* perimeter_surfaces, SurfaceCollection* fill_surfaces);
|
||||
void process_external_surfaces(const Layer* lower_layer);
|
||||
@ -108,7 +109,8 @@ public:
|
||||
|
||||
|
||||
size_t region_count() const;
|
||||
LayerRegion* get_region(int idx);
|
||||
const LayerRegion* get_region(int idx) const { return this->regions.at(idx); }
|
||||
LayerRegion* get_region(int idx) { return this->regions.at(idx); }
|
||||
LayerRegion* add_region(PrintRegion* print_region);
|
||||
|
||||
void make_slices();
|
||||
|
@ -46,12 +46,25 @@ LayerRegion::merge_slices()
|
||||
{
|
||||
ExPolygons expp;
|
||||
// without safety offset, artifacts are generated (GH #2494)
|
||||
union_(this->slices, &expp, true);
|
||||
union_(to_polygons(STDMOVE(this->slices.surfaces)), &expp, true);
|
||||
this->slices.surfaces.clear();
|
||||
this->slices.surfaces.reserve(expp.size());
|
||||
surfaces_append(this->slices.surfaces, expp, stInternal);
|
||||
}
|
||||
|
||||
for (ExPolygons::const_iterator expoly = expp.begin(); expoly != expp.end(); ++expoly)
|
||||
this->slices.surfaces.push_back(Surface(stInternal, *expoly));
|
||||
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
|
||||
void LayerRegion::slices_to_fill_surfaces_clipped()
|
||||
{
|
||||
// Note: this method should be idempotent, but fill_surfaces gets modified
|
||||
// in place. However we're now only using its boundaries (which are invariant)
|
||||
// so we're safe. This guarantees idempotence of prepare_infill() also in case
|
||||
// that combine_infill() turns some fill_surface into VOID surfaces.
|
||||
Polygons fill_boundaries = to_polygons(STDMOVE(this->fill_surfaces));
|
||||
this->fill_surfaces.surfaces.clear();
|
||||
for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++ surface)
|
||||
surfaces_append(
|
||||
this->fill_surfaces.surfaces,
|
||||
intersection_ex(to_polygons(surface->expolygon), fill_boundaries),
|
||||
surface->surface_type);
|
||||
}
|
||||
|
||||
void
|
||||
@ -102,70 +115,46 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
||||
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
#if 0
|
||||
Surfaces bottom;
|
||||
// For all stBottom && stBottomBridge surfaces:
|
||||
for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) {
|
||||
if (!surface->is_bottom()) continue;
|
||||
|
||||
ExPolygons grown = offset_ex(surface->expolygon, +margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS);
|
||||
|
||||
/* 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 */
|
||||
double angle = -1;
|
||||
if (lower_layer != NULL) {
|
||||
ExPolygons expolygons;
|
||||
expolygons.push_back(surface->expolygon);
|
||||
BridgeDetector bd(
|
||||
expolygons,
|
||||
lower_layer->slices,
|
||||
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()) {
|
||||
angle = bd.angle;
|
||||
|
||||
if (this->layer()->object()->config.support_material) {
|
||||
polygons_append(this->bridged, bd.coverage());
|
||||
this->unsupported_bridge_edges.append(bd.unsupported_edges());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) {
|
||||
Surface s = *surface;
|
||||
s.expolygon = *it;
|
||||
s.bridge_angle = angle;
|
||||
bottom.push_back(s);
|
||||
}
|
||||
}
|
||||
#else
|
||||
// 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset
|
||||
// for better anchoring.
|
||||
// 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->surface_type == stBottom || lower_layer == NULL) {
|
||||
// Grown by 3mm.
|
||||
surfaces_append(bottom, offset_ex(surface->expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), *surface);
|
||||
} else if (surface->surface_type == stBottomBridge) {
|
||||
bridges.push_back(*surface);
|
||||
// Grown by 3mm.
|
||||
bridges_grown.push_back(offset(surface->expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS));
|
||||
bridge_bboxes.push_back(get_extents(bridges_grown.back()));
|
||||
// Top surfaces, grown.
|
||||
Surfaces top;
|
||||
// Internal surfaces, not grown.
|
||||
Surfaces internal;
|
||||
// Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed.
|
||||
Polygons fill_boundaries;
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
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, offset_ex(surface->expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), *surface);
|
||||
} else if (surface->surface_type == stBottom || (surface->surface_type == stBottomBridge && lower_layer == NULL)) {
|
||||
// Grown by 3mm.
|
||||
surfaces_append(bottom, offset_ex(surface->expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), *surface);
|
||||
} else if (surface->surface_type == stBottomBridge) {
|
||||
if (! surface->empty())
|
||||
bridges.push_back(*surface);
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,8 +165,56 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (lower_layer != NULL)
|
||||
if (bridges.empty())
|
||||
{
|
||||
fill_boundaries = union_(fill_boundaries, true);
|
||||
} else
|
||||
{
|
||||
// 1) Calculate the inflated bridge regions, each constrained to its island.
|
||||
ExPolygons fill_boundaries_ex = union_ex(fill_boundaries, true);
|
||||
std::vector<Polygons> bridges_grown;
|
||||
std::vector<BoundingBox> bridge_bboxes;
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex));
|
||||
svg.draw(fill_boundaries_ex);
|
||||
svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05));
|
||||
svg.Close();
|
||||
}
|
||||
|
||||
// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
{
|
||||
// Bridge expolygons, grown, to be tested for intersection with other bridge regions.
|
||||
std::vector<BoundingBox> fill_boundaries_ex_bboxes = get_extents_vector(fill_boundaries_ex);
|
||||
bridges_grown.reserve(bridges.size());
|
||||
bridge_bboxes.reserve(bridges.size());
|
||||
for (size_t i = 0; i < bridges.size(); ++ i) {
|
||||
// Find the island of this bridge.
|
||||
const Point pt = bridges[i].expolygon.contour.points.front();
|
||||
int idx_island = -1;
|
||||
for (int j = 0; j < int(fill_boundaries_ex.size()); ++ j)
|
||||
if (fill_boundaries_ex_bboxes[j].contains(pt) &&
|
||||
fill_boundaries_ex[j].contains(pt)) {
|
||||
idx_island = j;
|
||||
break;
|
||||
}
|
||||
// Grown by 3mm.
|
||||
Polygons polys = offset(bridges[i].expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS);
|
||||
if (idx_island == -1) {
|
||||
printf("Bridge did not fall into the source region!\r\n");
|
||||
} else {
|
||||
// Found an island, to which this bridge region belongs. Trim it,
|
||||
polys = intersection(polys, to_polygons(fill_boundaries_ex[idx_island]));
|
||||
}
|
||||
bridge_bboxes.push_back(get_extents(polys));
|
||||
bridges_grown.push_back(STDMOVE(polys));
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Group the bridge surfaces by overlaps.
|
||||
std::vector<size_t> bridge_group(bridges.size(), (size_t)-1);
|
||||
size_t n_groups = 0;
|
||||
@ -195,7 +232,7 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
||||
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)
|
||||
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;
|
||||
@ -251,6 +288,8 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
||||
// without safety offset, artifacts are generated (GH #2494)
|
||||
surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]);
|
||||
}
|
||||
|
||||
fill_boundaries = STDMOVE(to_polygons(fill_boundaries_ex));
|
||||
}
|
||||
|
||||
#if 0
|
||||
@ -260,58 +299,33 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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), EXTERNAL_SURFACES_OFFSET_PARAMETERS), 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));
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
{
|
||||
// Merge top and bottom in a single collection.
|
||||
surfaces_append(top, STDMOVE(bottom));
|
||||
// Intersect the grown surfaces with the actual fill boundaries.
|
||||
Polygons bottom_polygons = to_polygons(bottom);
|
||||
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));
|
||||
}
|
||||
if (s1.surface_type == stTop)
|
||||
// Trim the top surfaces by the bottom surfaces. This gives the priority to the bottom surfaces.
|
||||
polys = diff(polys, bottom_polygons);
|
||||
surfaces_append(
|
||||
new_surfaces,
|
||||
// Don't use a safety offset as fill_boundaries were already united using the safety offset.
|
||||
STDMOVE(intersection_ex(polys, fill_boundaries, false)),
|
||||
s1);
|
||||
}
|
||||
surfaces_append(
|
||||
new_surfaces,
|
||||
STDMOVE(intersection_ex(polys, fill_boundaries, true)),
|
||||
s1);
|
||||
}
|
||||
|
||||
// Subtract the new top surfaces from the other non-top surfaces and re-add them.
|
||||
@ -342,6 +356,11 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
||||
void
|
||||
LayerRegion::prepare_fill_surfaces()
|
||||
{
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial");
|
||||
export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
/* Note: in order to make the psPrepareInfill step idempotent, we should never
|
||||
alter fill_surfaces boundaries on which our idempotency relies since that's
|
||||
the only meaningful information returned by psPerimeters. */
|
||||
@ -376,8 +395,8 @@ LayerRegion::prepare_fill_surfaces()
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_slices_to_svg_debug("2_prepare_fill_surfaces");
|
||||
export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces");
|
||||
export_region_slices_to_svg_debug("2_prepare_fill_surfaces-final");
|
||||
export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-final");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,11 @@ PerimeterGenerator::process()
|
||||
// from the line width of the infill?
|
||||
coord_t distance = (i == 1) ? ext_pspacing2 : pspacing;
|
||||
|
||||
if (this->config->thin_walls) {
|
||||
|
||||
//FIXME Vojtech: Why there is a special case for the thin walls?
|
||||
// Gap fill is active all the time anyway and this is not the outer perimeter.
|
||||
// if (this->config->thin_walls) {
|
||||
if (false) {
|
||||
offsets = offset2(
|
||||
last,
|
||||
-(distance + min_spacing/2 - 1),
|
||||
|
@ -138,6 +138,7 @@ public:
|
||||
bool invalidate_all_steps();
|
||||
|
||||
bool has_support_material() const;
|
||||
void detect_surfaces_type();
|
||||
void process_external_surfaces();
|
||||
void discover_vertical_shells();
|
||||
void bridge_over_infill();
|
||||
|
@ -310,6 +310,162 @@ PrintObject::has_support_material() const
|
||||
|| this->config.support_material_enforce_layers > 0;
|
||||
}
|
||||
|
||||
// This function analyzes slices of a region (SurfaceCollection slices).
|
||||
// Each slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface.
|
||||
// Initially all slices are of type S_TYPE_INTERNAL.
|
||||
// Slices are compared against the top / bottom slices and regions and classified to the following groups:
|
||||
// S_TYPE_TOP - Part of a region, which is not covered by any upper layer. This surface will be filled with a top solid infill.
|
||||
// S_TYPE_BOTTOMBRIDGE - Part of a region, which is not fully supported, but it hangs in the air, or it hangs losely on a support or a raft.
|
||||
// S_TYPE_BOTTOM - Part of a region, which is not supported by the same region, but it is supported either by another region, or by a soluble interface layer.
|
||||
// S_TYPE_INTERNAL - Part of a region, which is supported by the same region type.
|
||||
// If a part of a region is of S_TYPE_BOTTOM and S_TYPE_TOP, the S_TYPE_BOTTOM wins.
|
||||
void PrintObject::detect_surfaces_type()
|
||||
{
|
||||
// Slic3r::debugf "Detecting solid surfaces...\n";
|
||||
for (int idx_region = 0; idx_region < this->_print->regions.size(); ++ idx_region) {
|
||||
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
|
||||
for (int idx_layer = 0; idx_layer < int(this->layer_count()); ++ idx_layer) {
|
||||
LayerRegion *layerm = this->layers[idx_layer]->get_region(idx_region);
|
||||
layerm->slices_to_fill_surfaces_clipped();
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
layerm->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
}
|
||||
|
||||
for (int idx_layer = 0; idx_layer < int(this->layer_count()); ++ idx_layer) {
|
||||
Layer *layer = this->layers[idx_layer];
|
||||
LayerRegion *layerm = layer->get_region(idx_region);
|
||||
// comparison happens against the *full* slices (considering all regions)
|
||||
// unless internal shells are requested
|
||||
Layer *upper_layer = idx_layer + 1 < this->layer_count() ? this->get_layer(idx_layer + 1) : NULL;
|
||||
Layer *lower_layer = idx_layer > 0 ? this->get_layer(idx_layer - 1) : NULL;
|
||||
// collapse very narrow parts (using the safety offset in the diff is not enough)
|
||||
float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f;
|
||||
|
||||
Polygons layerm_slices_surfaces = to_polygons(layerm->slices.surfaces);
|
||||
|
||||
// find top surfaces (difference between current surfaces
|
||||
// of current layer and upper one)
|
||||
Surfaces top;
|
||||
if (upper_layer) {
|
||||
// Config value $self->config->interface_shells is true, if a support is separated from the object
|
||||
// by a soluble material (for example a PVA plastic).
|
||||
Polygons upper_slices = this->config.interface_shells.value ?
|
||||
to_polygons(upper_layer->get_region(idx_region)->slices.surfaces) :
|
||||
to_polygons(upper_layer->slices);
|
||||
surfaces_append(top,
|
||||
offset2_ex(diff(layerm_slices_surfaces, upper_slices, true), -offset, offset),
|
||||
stTop);
|
||||
} else {
|
||||
// if no upper layer, all surfaces of this one are solid
|
||||
// we clone surfaces because we're going to clear the slices collection
|
||||
top = layerm->slices.surfaces;
|
||||
for (Surfaces::iterator it = top.begin(); it != top.end(); ++ it)
|
||||
it->surface_type = stTop;
|
||||
}
|
||||
|
||||
// find bottom surfaces (difference between current surfaces
|
||||
// of current layer and lower one)
|
||||
Surfaces bottom;
|
||||
if (lower_layer) {
|
||||
// If we have soluble support material, don't bridge. The overhang will be squished against a soluble layer separating
|
||||
// the support from the print.
|
||||
SurfaceType surface_type_bottom =
|
||||
(this->config.support_material.value && this->config.support_material_contact_distance.value == 0) ?
|
||||
stBottom : stBottomBridge;
|
||||
// Any surface lying on the void is a true bottom bridge (an overhang)
|
||||
surfaces_append(
|
||||
bottom,
|
||||
offset2_ex(
|
||||
diff(layerm_slices_surfaces, to_polygons(lower_layer->slices), true),
|
||||
-offset, offset),
|
||||
surface_type_bottom);
|
||||
// if user requested internal shells, we need to identify surfaces
|
||||
// lying on other slices not belonging to this region
|
||||
//FIXME Vojtech: config.internal_shells or config.interface_shells? Is it some legacy code?
|
||||
// Why shall multiple regions over soluble support be treated specially?
|
||||
if (this->config.interface_shells.value) {
|
||||
// non-bridging bottom surfaces: any part of this layer lying
|
||||
// on something else, excluding those lying on our own region
|
||||
surfaces_append(
|
||||
bottom,
|
||||
offset2_ex(
|
||||
diff(
|
||||
intersection(layerm_slices_surfaces, to_polygons(lower_layer->slices)), // supported
|
||||
to_polygons(lower_layer->get_region(idx_region)->slices.surfaces),
|
||||
true),
|
||||
-offset, offset),
|
||||
stBottom);
|
||||
}
|
||||
} else {
|
||||
// if no lower layer, all surfaces of this one are solid
|
||||
// we clone surfaces because we're going to clear the slices collection
|
||||
bottom = layerm->slices.surfaces;
|
||||
// if we have raft layers, consider bottom layer as a bridge
|
||||
// just like any other bottom surface lying on the void
|
||||
SurfaceType surface_type_bottom =
|
||||
(this->config.raft_layers.value > 0 && this->config.support_material_contact_distance.value > 0) ?
|
||||
stBottomBridge : stBottom;
|
||||
for (Surfaces::iterator it = bottom.begin(); it != bottom.end(); ++ it)
|
||||
it->surface_type = surface_type_bottom;
|
||||
}
|
||||
|
||||
// now, if the object contained a thin membrane, we could have overlapping bottom
|
||||
// and top surfaces; let's do an intersection to discover them and consider them
|
||||
// as bottom surfaces (to allow for bridge detection)
|
||||
if (! top.empty() && ! bottom.empty()) {
|
||||
// Polygons overlapping = intersection(to_polygons(top), to_polygons(bottom));
|
||||
// Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->layer->id, scalar(@$overlapping)
|
||||
// if $Slic3r::debug;
|
||||
Polygons top_polygons = to_polygons(STDMOVE(top));
|
||||
top.clear();
|
||||
surfaces_append(top,
|
||||
#if 0
|
||||
offset2_ex(diff(top_polygons, to_polygons(bottom), true), -offset, offset),
|
||||
#else
|
||||
diff_ex(top_polygons, to_polygons(bottom), false),
|
||||
#endif
|
||||
stTop);
|
||||
}
|
||||
|
||||
// save surfaces to layer
|
||||
layerm->slices.surfaces.clear();
|
||||
|
||||
// find internal surfaces (difference between top/bottom surfaces and others)
|
||||
{
|
||||
Polygons topbottom = to_polygons(top);
|
||||
polygons_append(topbottom, to_polygons(bottom));
|
||||
surfaces_append(layerm->slices.surfaces,
|
||||
#if 0
|
||||
offset2_ex(diff(layerm_slices_surfaces, topbottom, true), -offset, offset),
|
||||
#else
|
||||
diff_ex(layerm_slices_surfaces, topbottom, false),
|
||||
#endif
|
||||
stInternal);
|
||||
}
|
||||
|
||||
surfaces_append(layerm->slices.surfaces, STDMOVE(top));
|
||||
surfaces_append(layerm->slices.surfaces, STDMOVE(bottom));
|
||||
|
||||
// Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n",
|
||||
// $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug;
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
layerm->export_region_slices_to_svg_debug("detect_surfaces_type-final");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
} // for each layer of a region
|
||||
|
||||
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
|
||||
for (int idx_layer = 0; idx_layer < int(this->layer_count()); ++ idx_layer) {
|
||||
LayerRegion *layerm = this->layers[idx_layer]->get_region(idx_region);
|
||||
layerm->slices_to_fill_surfaces_clipped();
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
layerm->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-final");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
} // for each layer of a region
|
||||
} // for each $self->print->region_count
|
||||
}
|
||||
|
||||
void
|
||||
PrintObject::process_external_surfaces()
|
||||
{
|
||||
@ -367,10 +523,23 @@ PrintObject::discover_vertical_shells()
|
||||
++ idx;
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge };
|
||||
for (int n = (int)idx_layer - layerm->region()->config.bottom_solid_layers + 1; n < (int)idx_layer + layerm->region()->config.top_solid_layers; ++ n)
|
||||
if (n >= 0 && n < (int)this->layers.size())
|
||||
polygons_append(shell, this->layers[n]->perimeter_expolygons.expolygons);
|
||||
//FIXME Add the top / bottom layerm->slices to the mix!
|
||||
if (n >= 0 && n < (int)this->layers.size()) {
|
||||
Layer &neighbor_layer = *this->layers[n];
|
||||
LayerRegion &neighbor_region = *neighbor_layer.get_region(int(idx_region));
|
||||
polygons_append(shell, neighbor_layer.perimeter_expolygons.expolygons);
|
||||
if (n > int(idx_layer)) {
|
||||
// Collect top surfaces.
|
||||
polygons_append(shell, to_polygons(neighbor_region.slices.filter_by_type(stTop)));
|
||||
polygons_append(shell, to_polygons(neighbor_region.fill_surfaces.filter_by_type(stTop)));
|
||||
}
|
||||
else if (n < int(idx_layer)) {
|
||||
// Collect bottom and bottom bridge surfaces.
|
||||
polygons_append(shell, to_polygons(neighbor_region.slices.filter_by_types(surfaces_bottom, 2)));
|
||||
polygons_append(shell, to_polygons(neighbor_region.fill_surfaces.filter_by_types(surfaces_bottom, 2)));
|
||||
}
|
||||
}
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static size_t idx = 0;
|
||||
@ -430,25 +599,47 @@ PrintObject::discover_vertical_shells()
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// Trim the shells region by the internal & internal void surfaces.
|
||||
const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid };
|
||||
const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid, stInternalSolid };
|
||||
const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 2));
|
||||
shell = intersection(shell, polygonsInternal, true);
|
||||
if (shell.empty())
|
||||
continue;
|
||||
|
||||
// Append the internal solids, so they will be merged with the new ones.
|
||||
polygons_append(shell, to_polygons(layerm->fill_surfaces.filter_by_type(stInternalSolid)));
|
||||
|
||||
// These regions will be filled by a rectilinear full infill. Currently this type of infill
|
||||
// only fills regions, which fit at least a single line. To avoid gaps in the sparse infill,
|
||||
// make sure that this region does not contain parts narrower than the infill spacing width.
|
||||
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
Polygons shell_before = shell;
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
#if 1
|
||||
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
|
||||
// Intentionally inflate a bit more than how much the region has been shrunk,
|
||||
// so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill).
|
||||
shell = offset2(shell, - 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing,
|
||||
CLIPPER_OFFSET_SCALE, ClipperLib::jtSquare);
|
||||
if (shell.empty())
|
||||
continue;
|
||||
#else
|
||||
// Ensure each region is at least 3x infill line width wide, so it could be filled in.
|
||||
// float margin = float(infill_line_spacing) * 3.f;
|
||||
float margin = float(infill_line_spacing) * 1.5f;
|
||||
// we use a higher miterLimit here to handle areas with acute angles
|
||||
// in those cases, the default miterLimit would cut the corner and we'd
|
||||
// get a triangle in $too_narrow; if we grow it below then the shell
|
||||
// would have a different shape from the external surface and we'd still
|
||||
// have the same angle, so the next shell would be grown even more and so on.
|
||||
Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, 5.), true);
|
||||
if (! too_narrow.empty()) {
|
||||
// grow the collapsing parts and add the extra area to the neighbor layer
|
||||
// as well as to our original surfaces so that we support this
|
||||
// additional area in the next shell too
|
||||
// make sure our grown surfaces don't exceed the fill area
|
||||
polygons_append(shell, intersection(offset(too_narrow, margin), polygonsInternal));
|
||||
}
|
||||
#endif
|
||||
ExPolygons new_internal_solid = intersection_ex(polygonsInternal, shell, false);
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
@ -465,8 +656,6 @@ PrintObject::discover_vertical_shells()
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// Trim the internal & internalvoid by the shell.
|
||||
// Enforce some overlap with the other infill regions.
|
||||
shell = offset(shell, - 0.25f * min_perimeter_infill_spacing);
|
||||
Slic3r::ExPolygons new_internal = diff_ex(
|
||||
to_polygons(layerm->fill_surfaces.filter_by_type(stInternal)),
|
||||
shell,
|
||||
@ -489,7 +678,7 @@ PrintObject::discover_vertical_shells()
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// Assign resulting internal surfaces to layer.
|
||||
const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge, stInternalSolid };
|
||||
const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge };
|
||||
layerm->fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType));
|
||||
layerm->fill_surfaces.append(stInternal , new_internal);
|
||||
layerm->fill_surfaces.append(stInternalVoid , new_internal_void);
|
||||
|
@ -107,6 +107,7 @@ _constant()
|
||||
void set_step_started(PrintObjectStep step)
|
||||
%code%{ THIS->state.set_started(step); %};
|
||||
|
||||
void detect_surfaces_type();
|
||||
void process_external_surfaces();
|
||||
void discover_vertical_shells();
|
||||
void bridge_over_infill();
|
||||
|
Loading…
Reference in New Issue
Block a user