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:
bubnikv 2016-11-10 19:23:01 +01:00
parent 317e9131e8
commit 4460b5ce50
8 changed files with 345 additions and 293 deletions

View File

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

View File

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

View File

@ -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();

View File

@ -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 */
}

View File

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

View File

@ -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();

View File

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

View File

@ -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();