Slic3r/xs/src/libslic3r/PrintObject.cpp

1758 lines
75 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "Log.hpp"
#include <algorithm>
#include <vector>
namespace Slic3r {
PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox)
: layer_height_spline(model_object->layer_height_spline),
typed_slices(false),
_print(print),
_model_object(model_object)
{
// Compute the translation to be applied to our meshes so that we work with smaller coordinates
{
// Translate meshes so that our toolpath generation algorithms work with smaller
// XY coordinates; this translation is an optimization and not strictly required.
// A cloned mesh will be aligned to 0 before slicing in _slice_region() since we
// don't assume it's already aligned and we don't alter the original position in model.
// We store the XY translation so that we can place copies correctly in the output G-code
// (copies are expressed in G-code coordinates and this translation is not publicly exposed).
this->_copies_shift = Point(
scale_(modobj_bbox.min.x), scale_(modobj_bbox.min.y));
// Scale the object size and store it
Pointf3 size = modobj_bbox.size();
this->size = Point3(scale_(size.x), scale_(size.y), scale_(size.z));
}
this->reload_model_instances();
this->layer_height_ranges = model_object->layer_height_ranges;
}
PrintObject::~PrintObject()
{
}
Points
PrintObject::copies() const
{
return this->_copies;
}
bool
PrintObject::add_copy(const Pointf &point)
{
Points points = this->_copies;
points.push_back(Point::new_scale(point.x, point.y));
return this->set_copies(points);
}
bool
PrintObject::delete_last_copy()
{
Points points = this->_copies;
points.pop_back();
return this->set_copies(points);
}
bool
PrintObject::delete_all_copies()
{
Points points;
return this->set_copies(points);
}
bool
PrintObject::set_copies(const Points &points)
{
this->_copies = points;
// order copies with a nearest neighbor search and translate them by _copies_shift
this->_shifted_copies.clear();
this->_shifted_copies.reserve(points.size());
// order copies with a nearest-neighbor search
std::vector<Points::size_type> ordered_copies;
Slic3r::Geometry::chained_path(points, ordered_copies);
for (std::vector<Points::size_type>::const_iterator it = ordered_copies.begin(); it != ordered_copies.end(); ++it) {
Point copy = points[*it];
copy.translate(this->_copies_shift);
this->_shifted_copies.push_back(copy);
}
bool invalidated = false;
if (this->_print->invalidate_step(psSkirt)) invalidated = true;
if (this->_print->invalidate_step(psBrim)) invalidated = true;
return invalidated;
}
bool
PrintObject::reload_model_instances()
{
Points copies;
for (ModelInstancePtrs::const_iterator i = this->_model_object->instances.begin(); i != this->_model_object->instances.end(); ++i) {
copies.push_back(Point::new_scale((*i)->offset.x, (*i)->offset.y));
}
return this->set_copies(copies);
}
BoundingBox
PrintObject::bounding_box() const
{
// since the object is aligned to origin, bounding box coincides with size
Points pp;
pp.push_back(Point(0,0));
pp.push_back(this->size);
return BoundingBox(pp);
}
// returns 0-based indices of used extruders
std::set<size_t>
PrintObject::extruders() const
{
std::set<size_t> extruders = this->_print->extruders();
std::set<size_t> sm_extruders = this->support_material_extruders();
extruders.insert(sm_extruders.begin(), sm_extruders.end());
return extruders;
}
// returns 0-based indices of used extruders
std::set<size_t>
PrintObject::support_material_extruders() const
{
std::set<size_t> extruders;
if (this->has_support_material()) {
extruders.insert(this->config.support_material_extruder - 1);
extruders.insert(this->config.support_material_interface_extruder - 1);
}
return extruders;
}
void
PrintObject::add_region_volume(int region_id, int volume_id)
{
region_volumes[region_id].push_back(volume_id);
}
/* This is the *total* layer count (including support layers)
this value is not supposed to be compared with Layer::id
since they have different semantics */
size_t
PrintObject::total_layer_count() const
{
return this->layer_count() + this->support_layer_count();
}
size_t
PrintObject::layer_count() const
{
return this->layers.size();
}
void
PrintObject::clear_layers()
{
for (int i = this->layers.size()-1; i >= 0; --i)
this->delete_layer(i);
}
Layer*
PrintObject::add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z)
{
layers.emplace_back(new Layer(id, this, height, print_z, slice_z));
return layers.back();
}
void
PrintObject::delete_layer(int idx)
{
LayerPtrs::iterator i = this->layers.begin() + idx;
delete *i;
this->layers.erase(i);
}
size_t
PrintObject::support_layer_count() const
{
return this->support_layers.size();
}
void
PrintObject::clear_support_layers()
{
for (int i = this->support_layers.size()-1; i >= 0; --i)
this->delete_support_layer(i);
}
SupportLayer*
PrintObject::add_support_layer(int id, coordf_t height, coordf_t print_z)
{
SupportLayer* layer = new SupportLayer(id, this, height, print_z, -1);
support_layers.push_back(layer);
return layer;
}
void
PrintObject::delete_support_layer(int idx)
{
SupportLayerPtrs::iterator i = this->support_layers.begin() + idx;
delete *i;
this->support_layers.erase(i);
}
bool
PrintObject::invalidate_state_by_config(const PrintConfigBase &config)
{
const t_config_option_keys diff = this->config.diff(config);
std::set<PrintObjectStep> steps;
bool all = false;
// this method only accepts PrintObjectConfig and PrintRegionConfig option keys
for (const t_config_option_key &opt_key : diff) {
if (opt_key == "layer_height"
|| opt_key == "first_layer_height"
|| opt_key == "adaptive_slicing"
|| opt_key == "adaptive_slicing_quality"
|| opt_key == "match_horizontal_surfaces"
|| opt_key == "regions_overlap") {
steps.insert(posLayers);
} else if (opt_key == "xy_size_compensation"
|| opt_key == "raft_layers") {
steps.insert(posSlice);
} else if (opt_key == "support_material_contact_distance") {
steps.insert(posSlice);
steps.insert(posPerimeters);
steps.insert(posSupportMaterial);
} else if (opt_key == "support_material") {
steps.insert(posPerimeters);
steps.insert(posSupportMaterial);
} else if (opt_key == "support_material_angle"
|| opt_key == "support_material_extruder"
|| opt_key == "support_material_extrusion_width"
|| opt_key == "support_material_interface_layers"
|| opt_key == "support_material_interface_extruder"
|| opt_key == "support_material_interface_extrusion_width"
|| opt_key == "support_material_interface_spacing"
|| opt_key == "support_material_interface_speed"
|| opt_key == "support_material_buildplate_only"
|| opt_key == "support_material_pattern"
|| opt_key == "support_material_spacing"
|| opt_key == "support_material_threshold"
|| opt_key == "support_material_pillar_size"
|| opt_key == "support_material_pillar_spacing"
|| opt_key == "dont_support_bridges") {
steps.insert(posSupportMaterial);
} else if (opt_key == "interface_shells"
|| opt_key == "infill_only_where_needed") {
steps.insert(posPrepareInfill);
} else if (opt_key == "seam_position"
|| opt_key == "support_material_speed") {
// these options only affect G-code export, so nothing to invalidate
} else {
// for legacy, if we can't handle this option let's invalidate all steps
all = true;
break;
}
}
if (!diff.empty())
this->config.apply(config, true);
bool invalidated = false;
if (all) {
invalidated = this->invalidate_all_steps();
} else {
for (const PrintObjectStep &step : steps)
if (this->invalidate_step(step))
invalidated = true;
}
return invalidated;
}
bool
PrintObject::invalidate_step(PrintObjectStep step)
{
bool invalidated = this->state.invalidate(step);
// propagate to dependent steps
if (step == posPerimeters) {
invalidated |= this->invalidate_step(posPrepareInfill);
invalidated |= this->_print->invalidate_step(psSkirt);
invalidated |= this->_print->invalidate_step(psBrim);
} else if (step == posDetectSurfaces) {
invalidated |= this->invalidate_step(posPrepareInfill);
} else if (step == posPrepareInfill) {
invalidated |= this->invalidate_step(posInfill);
} else if (step == posInfill) {
invalidated |= this->_print->invalidate_step(psSkirt);
invalidated |= this->_print->invalidate_step(psBrim);
} else if (step == posSlice) {
invalidated |= this->invalidate_step(posPerimeters);
invalidated |= this->invalidate_step(posDetectSurfaces);
invalidated |= this->invalidate_step(posSupportMaterial);
}else if (step == posLayers) {
invalidated |= this->invalidate_step(posSlice);
} else if (step == posSupportMaterial) {
invalidated |= this->_print->invalidate_step(psSkirt);
invalidated |= this->_print->invalidate_step(psBrim);
}
return invalidated;
}
bool
PrintObject::invalidate_all_steps()
{
// make a copy because when invalidating steps the iterators are not working anymore
std::set<PrintObjectStep> steps = this->state.started;
bool invalidated = false;
for (std::set<PrintObjectStep>::const_iterator step = steps.begin(); step != steps.end(); ++step) {
if (this->invalidate_step(*step)) invalidated = true;
}
return invalidated;
}
bool
PrintObject::has_support_material() const
{
return this->config.support_material
|| this->config.raft_layers > 0
|| this->config.support_material_enforce_layers > 0;
}
// This will assign a type (top/bottom/internal) to layerm->slices
// and transform layerm->fill_surfaces from expolygon
// to typed top/bottom/internal surfaces;
void
PrintObject::detect_surfaces_type()
{
if (this->state.is_done(posDetectSurfaces)) return;
this->state.set_started(posDetectSurfaces);
// prerequisites
this->slice();
parallelize<Layer*>(
std::queue<Layer*>(std::deque<Layer*>(this->layers.begin(), this->layers.end())), // cast LayerPtrs to std::queue<Layer*>
boost::bind(&Slic3r::Layer::detect_surfaces_type, _1),
this->_print->config.threads.value
);
this->typed_slices = true;
this->state.set_done(posDetectSurfaces);
}
void
PrintObject::process_external_surfaces()
{
parallelize<Layer*>(
std::queue<Layer*>(std::deque<Layer*>(this->layers.begin(), this->layers.end())), // cast LayerPtrs to std::queue<Layer*>
boost::bind(&Slic3r::Layer::process_external_surfaces, _1),
this->_print->config.threads.value
);
}
/* This method applies bridge flow to the first internal solid layer above
sparse infill */
void
PrintObject::bridge_over_infill()
{
FOREACH_REGION(this->_print, region) {
const size_t region_id = region - this->_print->regions.begin();
// skip bridging in case there are no voids
if ((*region)->config.fill_density.value == 100) continue;
// get bridge flow
const Flow bridge_flow = (*region)->flow(
frSolidInfill,
-1, // layer height, not relevant for bridge flow
true, // bridge
false, // first layer
-1, // custom width, not relevant for bridge flow
*this
);
// get the average extrusion volume per surface unit
const double mm3_per_mm = bridge_flow.mm3_per_mm();
const double mm3_per_mm2 = mm3_per_mm / bridge_flow.width;
FOREACH_LAYER(this, layer_it) {
// skip first layer
if (layer_it == this->layers.begin()) continue;
Layer* layer = *layer_it;
LayerRegion* layerm = layer->get_region(region_id);
// extract the stInternalSolid surfaces that might be transformed into bridges
Polygons internal_solid;
layerm->fill_surfaces.filter_by_type(stInternal | stSolid, &internal_solid);
if (internal_solid.empty()) continue;
// check whether we should bridge or not according to density
{
// get the normal solid infill flow we would use if not bridging
const Flow normal_flow = layerm->flow(frSolidInfill, false);
// Bridging over sparse infill has two purposes:
// 1) cover better the gaps of internal sparse infill, especially when
// printing at very low densities;
// 2) provide a greater flow when printing very thin layers where normal
// solid flow would be very poor.
// So we calculate density threshold as interpolation according to normal flow.
// If normal flow would be equal or greater than the bridge flow, we can keep
// a low threshold like 25% in order to bridge only when printing at very low
// densities, when sparse infill has significant gaps.
// If normal flow would be equal or smaller than half the bridge flow, we
// use a higher threshold like 50% in order to bridge in more cases.
// We still never bridge whenever fill density is greater than 50% because
// we would overstuff.
const float min_threshold = 25.0;
const float max_threshold = 50.0;
const float density_threshold = std::max(
std::min<float>(
min_threshold
+ (max_threshold - min_threshold)
* (normal_flow.mm3_per_mm() - mm3_per_mm)
/ (mm3_per_mm/2 - mm3_per_mm),
max_threshold
),
min_threshold
);
if ((*region)->config.fill_density.value > density_threshold) continue;
}
// check whether the lower area is deep enough for absorbing the extra flow
// (for obvious physical reasons but also for preventing the bridge extrudates
// from overflowing in 3D preview)
ExPolygons to_bridge;
{
Polygons to_bridge_pp = internal_solid;
// Only bridge where internal infill exists below the solid shell matching
// these two conditions:
// 1) its depth is at least equal to our bridge extrusion diameter;
// 2) its free volume (thus considering infill density) is at least equal
// to the volume needed by our bridge flow.
double excess_mm3_per_mm2 = mm3_per_mm2;
// iterate through lower layers spanned by bridge_flow
const double bottom_z = layer->print_z - bridge_flow.height;
for (int i = (layer_it - this->layers.begin()) - 1; i >= 0; --i) {
const Layer* lower_layer = this->layers[i];
// subtract the void volume of this layer
excess_mm3_per_mm2 -= lower_layer->height * (100 - (*region)->config.fill_density.value)/100;
// stop iterating if both conditions are matched
if (lower_layer->print_z < bottom_z && excess_mm3_per_mm2 <= 0) break;
// iterate through regions and collect internal surfaces
Polygons lower_internal;
FOREACH_LAYERREGION(lower_layer, lower_layerm_it)
(*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal);
// intersect such lower internal surfaces with the candidate solid surfaces
to_bridge_pp = intersection(to_bridge_pp, lower_internal);
}
// don't bridge if the volume condition isn't matched
if (excess_mm3_per_mm2 > 0) continue;
// there's no point in bridging too thin/short regions
{
const double min_width = bridge_flow.scaled_width() * 3;
to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width);
}
if (to_bridge_pp.empty()) continue;
// convert into ExPolygons
to_bridge = union_ex(to_bridge_pp);
}
#ifdef SLIC3R_DEBUG
printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id());
#endif
// compute the remaning internal solid surfaces as difference
const ExPolygons not_to_bridge = diff_ex(internal_solid, to_polygons(to_bridge), true);
// build the new collection of fill_surfaces
{
Surfaces new_surfaces;
for (Surfaces::const_iterator surface = layerm->fill_surfaces.surfaces.begin(); surface != layerm->fill_surfaces.surfaces.end(); ++surface) {
if (surface->surface_type != (stInternal | stSolid))
new_surfaces.push_back(*surface);
}
for (ExPolygons::const_iterator ex = to_bridge.begin(); ex != to_bridge.end(); ++ex)
new_surfaces.push_back(Surface(stInternal | stBridge, *ex));
for (ExPolygons::const_iterator ex = not_to_bridge.begin(); ex != not_to_bridge.end(); ++ex)
new_surfaces.push_back(Surface(stInternal | stSolid, *ex));
layerm->fill_surfaces.surfaces = new_surfaces;
}
/*
# exclude infill from the layers below if needed
# see discussion at https://github.com/slic3r/Slic3r/issues/240
# Update: do not exclude any infill. Sparse infill is able to absorb the excess material.
if (0) {
my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height;
for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) {
Slic3r::debugf " skipping infill below those areas at layer %d\n", $i;
foreach my $lower_layerm (@{$self->get_layer($i)->regions}) {
my @new_surfaces = ();
# subtract the area from all types of surfaces
foreach my $group (@{$lower_layerm->fill_surfaces->group}) {
push @new_surfaces, map $group->[0]->clone(expolygon => $_),
@{diff_ex(
[ map $_->p, @$group ],
[ map @$_, @$to_bridge ],
)};
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNALVOID,
), @{intersection_ex(
[ map $_->p, @$group ],
[ map @$_, @$to_bridge ],
)};
}
$lower_layerm->fill_surfaces->clear;
$lower_layerm->fill_surfaces->append($_) for @new_surfaces;
}
$excess -= $self->get_layer($i)->height;
}
}
*/
}
}
}
// adjust the layer height to the next multiple of the z full-step resolution
coordf_t PrintObject::adjust_layer_height(coordf_t layer_height) const
{
coordf_t result = layer_height;
if(this->_print->config.z_steps_per_mm > 0) {
coordf_t min_dz = 1 / this->_print->config.z_steps_per_mm;
result = int(layer_height / min_dz + 0.5) * min_dz;
}
return result > 0 ? result : layer_height;
}
// generate a vector of print_z coordinates in object coordinate system (starting with 0) but including
// the first_layer_height if provided.
std::vector<coordf_t> PrintObject::generate_object_layers(coordf_t first_layer_height) {
std::vector<coordf_t> result;
// collect values from config
coordf_t min_nozzle_diameter = 1.0;
coordf_t min_layer_height = 0.0;
coordf_t max_layer_height = 10.0;
std::set<size_t> object_extruders = this->_print->object_extruders();
for (std::set<size_t>::const_iterator it_extruder = object_extruders.begin(); it_extruder != object_extruders.end(); ++ it_extruder) {
min_nozzle_diameter = std::min(min_nozzle_diameter, this->_print->config.nozzle_diameter.get_at(*it_extruder));
min_layer_height = std::max(min_layer_height, this->_print->config.min_layer_height.get_at(*it_extruder));
max_layer_height = std::min(max_layer_height, this->_print->config.max_layer_height.get_at(*it_extruder));
}
coordf_t layer_height = std::min(min_nozzle_diameter, this->config.layer_height.getFloat());
layer_height = this->adjust_layer_height(layer_height);
this->config.layer_height.value = layer_height;
// respect first layer height
if(first_layer_height) {
result.push_back(first_layer_height);
}
coordf_t print_z = first_layer_height;
coordf_t height = first_layer_height;
// Update object size at the spline object to define upper border
this->layer_height_spline.setObjectHeight(unscale(this->size.z));
if (this->state.is_done(posLayers)) {
// layer heights are already generated, just update layers from spline
// we don't need to respect first layer here, it's correctly provided by the spline object
result = this->layer_height_spline.getInterpolatedLayers();
}else{ // create new set of layers
// create stateful objects and variables for the adaptive slicing process
SlicingAdaptive as;
coordf_t adaptive_quality = this->config.adaptive_slicing_quality.value;
if(this->config.adaptive_slicing.value) {
const ModelVolumePtrs volumes = this->model_object()->volumes;
for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it)
if (! (*it)->modifier)
as.add_mesh(&(*it)->mesh);
as.prepare(unscale(this->size.z));
}
// loop until we have at least one layer and the max slice_z reaches the object height
while ((scale_(print_z + EPSILON)) < this->size.z) {
if (this->config.adaptive_slicing.value) {
height = 999;
// determine next layer height
height = as.next_layer_height(print_z, adaptive_quality, min_layer_height, max_layer_height);
// check for horizontal features and object size
if(this->config.match_horizontal_surfaces.value) {
coordf_t horizontal_dist = as.horizontal_facet_distance(print_z + height, min_layer_height);
if((horizontal_dist < min_layer_height) && (horizontal_dist > 0)) {
#ifdef SLIC3R_DEBUG
std::cout << "Horizontal feature ahead, distance: " << horizontal_dist << std::endl;
#endif
// can we shrink the current layer a bit?
if(height-(min_layer_height - horizontal_dist) > min_layer_height) {
// yes we can
height -= (min_layer_height - horizontal_dist);
#ifdef SLIC3R_DEBUG
std::cout << "Shrink layer height to " << height << std::endl;
#endif
}else{
// no, current layer would become too thin
height += horizontal_dist;
#ifdef SLIC3R_DEBUG
std::cout << "Widen layer height to " << height << std::endl;
#endif
}
}
}
}else{
height = layer_height;
}
// look for an applicable custom range
for (t_layer_height_ranges::const_iterator it_range = this->layer_height_ranges.begin(); it_range != this->layer_height_ranges.end(); ++ it_range) {
if(print_z >= it_range->first.first && print_z <= it_range->first.second) {
if(it_range->second > 0) {
height = it_range->second;
}
}
}
print_z += height;
result.push_back(print_z);
}
// Store layer vector for interactive manipulation
this->layer_height_spline.setLayers(result);
if (this->config.adaptive_slicing.value) {
// smoothing after adaptive algorithm
result = this->layer_height_spline.getInterpolatedLayers();
// remove top layer if empty
coordf_t slice_z = result.back() - (result[result.size()-1] - result[result.size()-2])/2.0;
if(slice_z > unscale(this->size.z)) {
result.pop_back();
}
}
// Reduce or thicken the top layer in order to match the original object size.
// This is not actually related to z_steps_per_mm but we only enable it in case
// user provided that value, as it means they really care about the layer height
// accuracy and we don't provide unexpected result for people noticing the last
// layer has a different layer height.
if ((this->_print->config.z_steps_per_mm > 0 || this->config.adaptive_slicing.value) && result.size() > 1) {
coordf_t diff = result.back() - unscale(this->size.z);
int last_layer = result.size()-1;
if (diff < 0) {
// we need to thicken last layer
coordf_t new_h = result[last_layer] - result[last_layer-1];
if(this->config.adaptive_slicing.value) { // use min/max layer_height values from adaptive algo.
new_h = std::min(max_layer_height, new_h - diff); // add (negativ) diff value
}else{
new_h = std::min(min_nozzle_diameter, new_h - diff); // add (negativ) diff value
}
result[last_layer] = result[last_layer-1] + new_h;
} else {
// we need to reduce last layer
coordf_t new_h = result[last_layer] - result[last_layer-1];
if(this->config.adaptive_slicing.value) { // use min/max layer_height values from adaptive algo.
new_h = std::max(min_layer_height, new_h - diff); // subtract (positive) diff value
}else{
if(min_nozzle_diameter/2 < new_h) { //prevent generation of a too small layer
new_h = std::max(min_nozzle_diameter/2, new_h - diff); // subtract (positive) diff value
}
}
result[last_layer] = result[last_layer-1] + new_h;
}
}
this->state.set_done(posLayers);
}
// push modified spline object back to model
this->_model_object->layer_height_spline = this->layer_height_spline;
// apply z-gradation.
// For static layer height: the adjusted layer height is still useful
// to have the layer height a multiple of a printable z-interval.
// If we don't do this, we might get aliasing effects if a small error accumulates
// over multiple layer until we get a slightly thicker layer.
if(this->_print->config.z_steps_per_mm > 0) {
coordf_t gradation = 1 / this->_print->config.z_steps_per_mm;
coordf_t last_z = 0;
coordf_t height;
for(std::vector<coordf_t>::iterator l = result.begin(); l != result.end(); ++l) {
height = *l - last_z;
coordf_t gradation_effect = unscale((scale_(height)) % (scale_(gradation)));
if(gradation_effect > gradation/2 && (height + (gradation-gradation_effect)) <= max_layer_height) { // round up
height = height + (gradation-gradation_effect);
}else{ // round down
height = height - gradation_effect;
}
// limiting the height to max_ / min_layer_height can violate the gradation requirement
// we now might exceed the layer height limits a bit, but that is probably better than having
// systematic errors introduced by the steppers...
//height = std::min(std::max(height, min_layer_height), max_layer_height);
*l = last_z + height;
last_z = *l;
}
}
return result;
}
// 1) Decides Z positions of the layers,
// 2) Initializes layers and their regions
// 3) Slices the object meshes
// 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
// 5) Applies size compensation (offsets the slices in XY plane)
// 6) Replaces bad slices by the slices reconstructed from the upper/lower layer
// Resulting expolygons of layer regions are marked as Internal.
//
// this should be idempotent
void PrintObject::_slice()
{
coordf_t raft_height = 0;
coordf_t first_layer_height = this->config.first_layer_height.get_abs_value(this->config.layer_height.value);
// take raft layers into account
int id = 0;
if (this->config.raft_layers > 0) {
id = this->config.raft_layers;
coordf_t min_support_nozzle_diameter = 1.0;
std::set<size_t> support_material_extruders = this->_print->support_material_extruders();
for (std::set<size_t>::const_iterator it_extruder = support_material_extruders.begin(); it_extruder != support_material_extruders.end(); ++ it_extruder) {
min_support_nozzle_diameter = std::min(min_support_nozzle_diameter, this->_print->config.nozzle_diameter.get_at(*it_extruder));
}
coordf_t support_material_layer_height = 0.75 * min_support_nozzle_diameter;
// raise first object layer Z by the thickness of the raft itself
// plus the extra distance required by the support material logic
raft_height += first_layer_height;
raft_height += support_material_layer_height * (this->config.raft_layers - 1);
// reset for later layer generation
first_layer_height = 0;
// detachable support
if(this->config.support_material_contact_distance > 0) {
first_layer_height = min_support_nozzle_diameter;
raft_height += this->config.support_material_contact_distance;
}
}
// Initialize layers and their slice heights.
std::vector<float> slice_zs;
{
this->clear_layers();
// All print_z values for this object, without the raft.
std::vector<coordf_t> object_layers = this->generate_object_layers(first_layer_height);
// Reserve object layers for the raft. Last layer of the raft is the contact layer.
slice_zs.reserve(object_layers.size());
Layer *prev = nullptr;
coordf_t lo = raft_height;
coordf_t hi = lo;
for (size_t i_layer = 0; i_layer < object_layers.size(); i_layer++) {
lo = hi; // store old value
hi = object_layers[i_layer] + raft_height;
coordf_t slice_z = 0.5 * (lo + hi) - raft_height;
Layer *layer = this->add_layer(id++, hi - lo, hi, slice_z);
slice_zs.push_back(float(slice_z));
if (prev != nullptr) {
prev->upper_layer = layer;
layer->lower_layer = prev;
}
// Make sure all layers contain layer region objects for all regions.
for (size_t region_id = 0; region_id < this->_print->regions.size(); ++ region_id)
layer->add_region(this->print()->regions[region_id]);
prev = layer;
}
}
if (this->print()->regions.size() == 1) {
// Optimized for a single region. Slice the single non-modifier mesh.
std::vector<ExPolygons> expolygons_by_layer = this->_slice_region(0, slice_zs, false);
for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id)
this->layers[layer_id]->regions.front()->slices.append(std::move(expolygons_by_layer[layer_id]), stInternal);
} else {
// Slice all non-modifier volumes.
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
std::vector<ExPolygons> expolygons_by_layer = this->_slice_region(region_id, slice_zs, false);
for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id)
this->layers[layer_id]->regions[region_id]->slices.append(std::move(expolygons_by_layer[layer_id]), stInternal);
}
// Slice all modifier volumes.
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
std::vector<ExPolygons> expolygons_by_layer = this->_slice_region(region_id, slice_zs, true);
// loop through the other regions and 'steal' the slices belonging to this one
for (size_t other_region_id = 0; other_region_id < this->print()->regions.size(); ++ other_region_id) {
if (region_id == other_region_id)
continue;
for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) {
Layer *layer = layers[layer_id];
LayerRegion *layerm = layer->regions[region_id];
LayerRegion *other_layerm = layer->regions[other_region_id];
if (layerm == nullptr || other_layerm == nullptr)
continue;
Polygons other_slices = to_polygons(other_layerm->slices);
ExPolygons my_parts = intersection_ex(other_slices, to_polygons(expolygons_by_layer[layer_id]));
if (my_parts.empty())
continue;
// Remove such parts from original region.
other_layerm->slices.set(diff_ex(other_slices, to_polygons(my_parts)), stInternal);
// Append new parts to our region.
layerm->slices.append(std::move(my_parts), stInternal);
}
}
}
}
// remove last layer(s) if empty
bool done = false;
while (! this->layers.empty()) {
const Layer *layer = this->layers.back();
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id)
if (layer->regions[region_id] != nullptr && ! layer->regions[region_id]->slices.empty()) {
done = true;
break;
}
if(done) {
break;
}
this->delete_layer(int(this->layers.size()) - 1);
}
// Apply size compensation and perform clipping of multi-part objects.
const coord_t xy_size_compensation = scale_(this->config.xy_size_compensation.value);
for (Layer* layer : this->layers) {
if (abs(xy_size_compensation) > 0) {
if (layer->regions.size() == 1) {
// Single region, growing or shrinking.
LayerRegion* layerm = layer->regions.front();
layerm->slices.set(
offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), xy_size_compensation),
stInternal
);
} else {
// Multiple regions, growing, shrinking or just clipping one region by the other.
// When clipping the regions, priority is given to the first regions.
Polygons processed;
for (size_t region_id = 0; region_id < layer->regions.size(); ++region_id) {
LayerRegion* layerm = layer->regions[region_id];
Polygons slices = layerm->slices;
if (abs(xy_size_compensation) > 0)
slices = offset(slices, xy_size_compensation);
if (region_id > 0)
// Trim by the slices of already processed regions.
slices = diff(std::move(slices), processed);
if (region_id + 1 < layer->regions.size())
// Collect the already processed regions to trim the to be processed regions.
append_to(processed, slices);
layerm->slices.set(union_ex(slices), stInternal);
}
}
}
// Merge all regions' slices to get islands, chain them by a shortest path.
layer->make_slices();
// Apply regions overlap
if (this->config.regions_overlap.value > 0) {
const coord_t delta = scale_(this->config.regions_overlap.value)/2;
for (LayerRegion* layerm : layer->regions)
layerm->slices.set(
intersection_ex(
offset(layerm->slices, +delta),
layer->slices
),
stInternal
);
}
}
}
// called from slice()
std::vector<ExPolygons>
PrintObject::_slice_region(size_t region_id, std::vector<float> z, bool modifier)
{
std::vector<ExPolygons> layers;
std::vector<int> &region_volumes = this->region_volumes[region_id];
if (region_volumes.empty()) return layers;
ModelObject &object = *this->model_object();
// compose mesh
TriangleMesh mesh;
for (const auto& i : region_volumes) {
const ModelVolume &volume = *(object.volumes[i]);
if (volume.modifier != modifier) continue;
mesh.merge(volume.mesh);
}
if (mesh.facets_count() == 0) return layers;
// transform mesh
// we ignore the per-instance transformations currently and only
// consider the first one
object.instances[0]->transform_mesh(&mesh, true);
// align mesh to Z = 0 (it should be already aligned actually) and apply XY shift
mesh.translate(
-unscale(this->_copies_shift.x),
-unscale(this->_copies_shift.y),
-object.bounding_box().min.z
);
// perform actual slicing
TriangleMeshSlicer<Z>(&mesh).slice(z, &layers);
return layers;
}
/*
1) Decides Z positions of the layers,
2) Initializes layers and their regions
3) Slices the object meshes
4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
5) Applies size compensation (offsets the slices in XY plane)
6) Replaces bad slices by the slices reconstructed from the upper/lower layer
Resulting expolygons of layer regions are marked as Internal.
This should be idempotent.
*/
void
PrintObject::slice()
{
if (this->state.is_done(posSlice)) return;
this->state.set_started(posSlice);
if (_print->status_cb != nullptr) {
_print->status_cb(10, "Processing triangulated mesh");
}
this->_slice();
// detect slicing errors
if (std::any_of(this->layers.cbegin(), this->layers.cend(),
[](const Layer* l){ return l->slicing_errors; }))
Slic3r::Log::warn("PrintObject") << "The model has overlapping or self-intersecting facets. "
<< "I tried to repair it, however you might want to check "
<< "the results or repair the input file and retry.\n";
bool warning_thrown = false;
for (size_t i = 0; i < this->layer_count(); ++i) {
Layer* layer{ this->get_layer(i) };
if (!layer->slicing_errors) continue;
if (!warning_thrown) {
Slic3r::Log::warn("PrintObject") << "The model has overlapping or self-intersecting facets. "
<< "I tried to repair it, however you might want to check "
<< "the results or repair the input file and retry.\n";
warning_thrown = true;
}
// try to repair the layer surfaces by merging all contours and all holes from
// neighbor layers
#ifdef SLIC3R_DEBUG
std::cout << "Attempting to repair layer " << i << std::endl;
#endif
for (size_t region_id = 0; region_id < layer->region_count(); ++region_id) {
LayerRegion* layerm{ layer->get_region(region_id) };
ExPolygons slices;
for (size_t j = i+1; j < this->layer_count(); ++j) {
const Layer* upper = this->get_layer(j);
if (!upper->slicing_errors) {
append_to(slices, (ExPolygons)upper->get_region(region_id)->slices);
break;
}
}
for (int j = i-1; j >= 0; --j) {
const Layer* lower = this->get_layer(j);
if (!lower->slicing_errors) {
append_to(slices, (ExPolygons)lower->get_region(region_id)->slices);
break;
}
}
// TODO: do we actually need to split contours and holes before performing the diff?
Polygons contours, holes;
for (ExPolygon ex : slices)
contours.push_back(ex.contour);
for (ExPolygon ex : slices)
append_to(holes, ex.holes);
const ExPolygons diff = diff_ex(contours, holes);
layerm->slices.clear();
layerm->slices.append(diff, stInternal);
}
// update layer slices after repairing the single regions
layer->make_slices();
}
// remove empty layers from bottom
while (!this->layers.empty() && this->get_layer(0)->slices.empty()) {
this->delete_layer(0);
for (Layer* layer : this->layers)
layer->set_id(layer->id()-1);
}
// simplify slices if required
if (this->_print->config.resolution() > 0)
this->_simplify_slices(scale_(this->_print->config.resolution()));
if (this->layers.empty()) {
Slic3r::Log::error("PrintObject") << "slice(): " << "No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n";
return; // make this throw an exception instead?
}
this->typed_slices = false;
this->state.set_done(posSlice);
}
void
PrintObject::make_perimeters()
{
if (this->state.is_done(posPerimeters)) return;
this->state.set_started(posPerimeters);
// Temporary workaround for detect_surfaces_type() not being idempotent (see #3764).
// We can remove this when idempotence is restored. This make_perimeters() method
// will just call merge_slices() to undo the typed slices and invalidate posDetectSurfaces.
if (this->typed_slices)
this->state.invalidate(posSlice);
// prerequisites
this->slice();
// merge slices if they were split into types
// This is not currently taking place because since merge_slices + detect_surfaces_type
// are not truly idempotent we are invalidating posSlice here (see the Perl part of
// this method).
if (this->typed_slices) {
// merge_slices() undoes detect_surfaces_type()
FOREACH_LAYER(this, layer_it)
(*layer_it)->merge_slices();
this->typed_slices = false;
this->state.invalidate(posDetectSurfaces);
}
// compare each layer to the one below, and mark those slices needing
// one additional inner perimeter, like the top of domed objects-
// this algorithm makes sure that at least one perimeter is overlapping
// but we don't generate any extra perimeter if fill density is zero, as they would be floating
// inside the object - infill_only_where_needed should be the method of choice for printing
// hollow objects
FOREACH_REGION(this->_print, region_it) {
size_t region_id = region_it - this->_print->regions.begin();
const PrintRegion &region = **region_it;
if (!region.config.extra_perimeters
|| region.config.perimeters == 0
|| region.config.fill_density == 0
|| this->layer_count() < 2) continue;
for (size_t i = 0; i <= (this->layer_count()-2); ++i) {
LayerRegion &layerm = *this->get_layer(i)->get_region(region_id);
const LayerRegion &upper_layerm = *this->get_layer(i+1)->get_region(region_id);
// In order to avoid diagonal gaps (GH #3732) we ignore the external half of the upper
// perimeter, since it's not truly covering this layer.
const Polygons upper_layerm_polygons = offset(
upper_layerm.slices,
-upper_layerm.flow(frExternalPerimeter).scaled_width()/2
);
// Filter upper layer polygons in intersection_ppl by their bounding boxes?
// my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ];
double total_loop_length = 0;
for (Polygons::const_iterator it = upper_layerm_polygons.begin(); it != upper_layerm_polygons.end(); ++it)
total_loop_length += it->length();
const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing();
const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter);
const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width();
const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing();
for (Surfaces::iterator slice = layerm.slices.surfaces.begin();
slice != layerm.slices.surfaces.end(); ++slice) {
while (true) {
// compute the total thickness of perimeters
const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2
+ (region.config.perimeters-1 + slice->extra_perimeters) * perimeter_spacing;
// define a critical area where we don't want the upper slice to fall into
// (it should either lay over our perimeters or outside this area)
const coord_t critical_area_depth = perimeter_spacing * 1.5;
const Polygons critical_area = diff(
offset(slice->expolygon, -perimeters_thickness),
offset(slice->expolygon, -(perimeters_thickness + critical_area_depth))
);
// check whether a portion of the upper slices falls inside the critical area
const Polylines intersection = intersection_pl(
upper_layerm_polygons,
critical_area
);
// only add an additional loop if at least 30% of the slice loop would benefit from it
{
double total_intersection_length = 0;
for (Polylines::const_iterator it = intersection.begin(); it != intersection.end(); ++it)
total_intersection_length += it->length();
if (total_intersection_length <= total_loop_length*0.3) break;
}
/*
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"extra.svg",
no_arrows => 1,
expolygons => union_ex($critical_area),
polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ],
);
}
*/
slice->extra_perimeters++;
}
#ifdef DEBUG
if (slice->extra_perimeters > 0)
printf(" adding %d more perimeter(s) at layer %zu\n", slice->extra_perimeters, i);
#endif
}
}
}
parallelize<Layer*>(
std::queue<Layer*>(std::deque<Layer*>(this->layers.begin(), this->layers.end())), // cast LayerPtrs to std::queue<Layer*>
boost::bind(&Slic3r::Layer::make_perimeters, _1),
this->_print->config.threads.value
);
/*
simplify slices (both layer and region slices),
we only need the max resolution for perimeters
### This makes this method not-idempotent, so we keep it disabled for now.
###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION);
*/
this->state.set_done(posPerimeters);
}
void
PrintObject::infill()
{
if (this->state.is_done(posInfill)) return;
this->state.set_started(posInfill);
// prerequisites
this->prepare_infill();
parallelize<Layer*>(
std::queue<Layer*>(std::deque<Layer*>(this->layers.begin(), this->layers.end())), // cast LayerPtrs to std::queue<Layer*>
boost::bind(&Slic3r::Layer::make_fills, _1),
this->_print->config.threads.value
);
/* we could free memory now, but this would make this step not idempotent
### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers};
*/
this->state.set_done(posInfill);
}
void
PrintObject::prepare_infill()
{
if (this->state.is_done(posPrepareInfill)) return;
// This prepare_infill() is not really idempotent.
// TODO: It should clear and regenerate fill_surfaces at every run
// instead of modifying it in place.
this->state.invalidate(posPerimeters);
this->make_perimeters();
this->state.set_started(posPrepareInfill);
// prerequisites
this->detect_surfaces_type();
if (this->_print->status_cb != nullptr)
this->_print->status_cb(30, "Preparing infill");
// decide what surfaces are to be filled
for (auto& layer : this->layers)
for (auto& layerm : layer->regions)
layerm->prepare_fill_surfaces();
// this will detect bridges and reverse bridges
// and rearrange top/bottom/internal surfaces
this->process_external_surfaces();
// detect which fill surfaces are near external layers
// they will be split in internal and internal-solid surfaces
this->discover_horizontal_shells();
this->clip_fill_surfaces();
// the following step needs to be done before combination because it may need
// to remove only half of the combined infill
this->bridge_over_infill();
// combine fill surfaces to honor the "infill every N layers" option
this->combine_infill();
this->state.set_done(posPrepareInfill);
}
// combine fill surfaces across layers
// 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.
void
PrintObject::combine_infill()
{
// Work on each region separately.
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
const PrintRegion *region = this->print()->regions[region_id];
const int every = region->config.infill_every_layers();
if (every < 2 || region->config.fill_density == 0.)
continue;
// Limit the number of combined layers to the maximum height allowed by this regions' nozzle.
// FIXME: limit the layer height to max_layer_height
const double nozzle_diameter = std::min(
this->_print->config.nozzle_diameter.get_at(region->config.infill_extruder.value - 1),
this->_print->config.nozzle_diameter.get_at(region->config.solid_infill_extruder.value - 1)
);
// define the combinations
std::vector<size_t> combine(this->layers.size(), 0); // layer_idx => number of additional combined lower layers
{
double current_height = 0.;
size_t num_layers = 0;
for (size_t layer_idx = 0; layer_idx < this->layers.size(); ++layer_idx) {
const Layer *layer = this->layers[layer_idx];
// Skip first print layer (which may not be first layer in array because of raft).
if (layer->id() == 0)
continue;
// Check whether the combination of this layer with the lower layers' buffer
// would exceed max layer height or max combined layer count.
if (current_height + layer->height >= nozzle_diameter + EPSILON || num_layers >= static_cast<size_t>(every) ) {
// Append combination to lower layer.
combine[layer_idx - 1] = num_layers;
current_height = 0.;
num_layers = 0;
}
current_height += layer->height;
++num_layers;
}
// Append lower layers (if any) to uppermost layer.
combine[this->layers.size() - 1] = num_layers;
}
// loop through layers to which we have assigned layers to combine
for (size_t layer_idx = 0; layer_idx < combine.size(); ++layer_idx) {
const size_t& num_layers = combine[layer_idx];
if (num_layers <= 1)
continue;
// Get all the LayerRegion objects to be combined.
std::vector<LayerRegion*> layerms;
layerms.reserve(num_layers);
for (size_t i = layer_idx + 1 - num_layers; i <= layer_idx; ++i)
layerms.push_back(this->layers[i]->regions[region_id]);
// We need to perform a multi-layer intersection, so let's split it in pairs.
// Initialize the intersection with the candidates of the lowest layer.
ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces.filter_by_type(stInternal));
// Start looping from the second layer and intersect the current intersection with it.
for (size_t i = 1; i < layerms.size(); ++i)
intersection = intersection_ex(
to_polygons(intersection),
to_polygons(layerms[i]->fill_surfaces.filter_by_type(stInternal))
);
// Remove ExPolygons whose area is <= infill_area_threshold()
const double area_threshold = layerms.front()->infill_area_threshold();
intersection.erase(std::remove_if(intersection.begin(), intersection.end(),
[area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }),
intersection.end());
if (intersection.empty())
continue;
#ifdef SLIC3R_DEBUG
std::cout << " combining " << intersection.size()
<< " internal regions from layers " << (layer_idx-(every-1))
<< "-" << layer_idx << std::endl;
#endif
// intersection now contains the regions that can be combined across the full amount of layers,
// so let's remove those areas from all layers.
const float clearance_offset =
0.5f * layerms.back()->flow(frPerimeter).scaled_width() +
// Because fill areas for rectilinear and honeycomb are grown
// later to overlap perimeters, we need to counteract that too.
((region->config.fill_pattern == ipRectilinear ||
region->config.fill_pattern == ipGrid ||
region->config.fill_pattern == ipHoneycomb) ? 1.5f : 0.5f)
* layerms.back()->flow(frSolidInfill).scaled_width();
Polygons intersection_with_clearance;
intersection_with_clearance.reserve(intersection.size());
for (const ExPolygon &expoly : intersection)
polygons_append(intersection_with_clearance, offset(expoly, clearance_offset));
for (LayerRegion *layerm : layerms) {
const Polygons internal = to_polygons(layerm->fill_surfaces.filter_by_type(stInternal));
layerm->fill_surfaces.remove_type(stInternal);
layerm->fill_surfaces.append(
diff_ex(internal, intersection_with_clearance),
stInternal
);
if (layerm == layerms.back()) {
// Apply surfaces back with adjusted depth to the uppermost layer.
Surface templ(stInternal, ExPolygon());
templ.thickness = 0.;
for (const LayerRegion *layerm2 : layerms)
templ.thickness += layerm2->layer()->height;
templ.thickness_layers = (unsigned short)layerms.size();
layerm->fill_surfaces.append(intersection, templ);
} else {
// Save void surfaces.
layerm->fill_surfaces.append(
intersection_ex(internal, intersection_with_clearance),
stInternal | stVoid);
}
}
}
}
}
SupportMaterial *
PrintObject::_support_material()
{
// TODO what does this line do //= FLOW_ROLE_SUPPORT_MATERIAL;
Flow first_layer_flow = Flow::new_from_config_width(
frSupportMaterial,
print()->config
.first_layer_extrusion_width, // check why this line is put || config.support_material_extrusion_width,
static_cast<float>(print()->config.nozzle_diameter.get_at(static_cast<size_t>(
config.support_material_extruder
- 1))), // Check why this is put in perl "// $self->print->config->nozzle_diameter->[0]"
static_cast<float>(config.get_abs_value("first_layer_height")),
0 // No bridge flow ratio.
);
return new SupportMaterial(
&print()->config,
&config,
first_layer_flow,
_support_material_flow(),
_support_material_flow(frSupportMaterialInterface)
);
}
Flow
PrintObject::_support_material_flow(FlowRole role)
{
const int extruder = (role == frSupportMaterial)
? this->config.support_material_extruder.value
: this->config.support_material_interface_extruder.value;
auto width = this->config.support_material_extrusion_width;
if (width.value == 0) width = this->config.extrusion_width;
if (role == frSupportMaterialInterface
&& this->config.support_material_interface_extrusion_width.value > 0)
width = this->config.support_material_interface_extrusion_width;
// We use a bogus layer_height because we use the same flow for all
// support material layers.
return Flow::new_from_config_width(
role,
width,
this->_print->config.nozzle_diameter.get_at(extruder-1),
this->config.layer_height.value,
0
);
}
void
PrintObject::generate_support_material()
{
//prereqs
this->slice();
if (this->state.is_done(posSupportMaterial)) { return; }
this->state.set_started(posSupportMaterial);
this->clear_support_layers();
if ((!config.support_material
&& config.raft_layers == 0
&& config.support_material_enforce_layers == 0)
|| this->layers.size() < 2
) {
this->state.set_done(posSupportMaterial);
return;
}
if (_print->status_cb != nullptr)
_print->status_cb(85, "Generating support material");
this->_support_material()->generate(this);
this->state.set_done(posSupportMaterial);
std::stringstream stats {""};
if (_print->status_cb != nullptr)
_print->status_cb(85, stats.str().c_str());
}
void
PrintObject::discover_horizontal_shells()
{
#ifdef SLIC3R_DEBUG
std::cout << "==> DISCOVERING HORIZONTAL SHELLS" << std::endl;
#endif
for (size_t region_id = 0U; region_id < _print->regions.size(); ++region_id) {
for (size_t i = 0; i < this->layer_count(); ++i) {
auto* layerm = this->get_layer(i)->get_region(region_id);
const auto& region_config = layerm->region()->config;
if (region_config.solid_infill_every_layers() > 0 && region_config.fill_density() > 0
&& (i % region_config.solid_infill_every_layers()) == 0) {
const auto type = region_config.fill_density() == 100 ? (stInternal | stSolid) : (stInternal | stBridge);
for (auto* s : layerm->fill_surfaces.filter_by_type(stInternal))
s->surface_type = type;
}
this->_discover_external_horizontal_shells(layerm, i, region_id);
}
}
}
void
PrintObject::_discover_external_horizontal_shells(LayerRegion* layerm, const size_t& i, const size_t& region_id)
{
const auto& region_config = layerm->region()->config;
for (auto& type : { stTop, stBottom, (stBottom | stBridge) }) {
// find slices of current type for current layer
// use slices instead of fill_surfaces because they also include the perimeter area
// which needs to be propagated in shells; we need to grow slices like we did for
// fill_surfaces though. Using both ungrown slices and grown fill_surfaces will
// not work in some situations, as there won't be any grown region in the perimeter
// area (this was seen in a model where the top layer had one extra perimeter, thus
// its fill_surfaces were thinner than the lower layer's infill), however it's the best
// solution so far. Growing the external slices by EXTERNAL_INFILL_MARGIN will put
// too much solid infill inside nearly-vertical slopes.
Polygons solid;
polygons_append(solid, to_polygons(layerm->slices.filter_by_type(type)));
polygons_append(solid, to_polygons(layerm->fill_surfaces.filter_by_type(type)));
if (solid.empty()) continue;
#ifdef SLIC3R_DEBUG
std::cout << "Layer " << i << " has " << (type == stTop ? "top" : "bottom") << " surfaces" << std::endl;
#endif
auto solid_layers = type == stTop
? region_config.top_solid_layers()
: region_config.bottom_solid_layers();
if (region_config.min_top_bottom_shell_thickness() > 0) {
auto current_shell_thickness = static_cast<coordf_t>(solid_layers) * this->get_layer(i)->height;
const auto min_shell_thickness = region_config.min_top_bottom_shell_thickness();
while (std::abs(min_shell_thickness - current_shell_thickness) > Slic3r::Geometry::epsilon) {
solid_layers++;
current_shell_thickness = static_cast<coordf_t>(solid_layers) * this->get_layer(i)->height;
}
}
_discover_neighbor_horizontal_shells(layerm, i, region_id, type, solid, solid_layers);
}
}
void
PrintObject::_discover_neighbor_horizontal_shells(LayerRegion* layerm, const size_t& i, const size_t& region_id, const SurfaceType& type, Polygons& solid, const size_t& solid_layers)
{
const auto& region_config = layerm->region()->config;
for (int n = (type == stTop ? i-1 : i+1); std::abs(n-int(i)) < solid_layers; (type == stTop ? n-- : n++)) {
if (n < 0 || static_cast<size_t>(n) >= this->layer_count()) continue;
LayerRegion* neighbor_layerm { this->get_layer(n)->get_region(region_id) };
// make a copy so we can use them even after clearing the original collection
SurfaceCollection neighbor_fill_surfaces{ neighbor_layerm->fill_surfaces };
// find intersection between neighbor and current layer's surfaces
// intersections have contours and holes
Polygons new_internal_solid = intersection(
solid,
to_polygons(neighbor_fill_surfaces.filter_by_type({stInternal, stInternal | stSolid})),
true
);
if (new_internal_solid.empty()) {
// No internal solid needed on this layer. In order to decide whether to continue
// searching on the next neighbor (thus enforcing the configured number of solid
// layers, use different strategies according to configured infill density:
if (region_config.fill_density == 0) {
// If user expects the object to be void (for example a hollow sloping vase),
// don't continue the search. In this case, we only generate the external solid
// shell if the object would otherwise show a hole (gap between perimeters of
// the two layers), and internal solid shells are a subset of the shells found
// on each previous layer.
return;
} else {
// If we have internal infill, we can generate internal solid shells freely.
continue;
}
}
if (region_config.fill_density == 0) {
// if we're printing a hollow object we discard any solid shell thinner
// than a perimeter width, since it's probably just crossing a sloping wall
// and it's not wanted in a hollow print even if it would make sense when
// obeying the solid shell count option strictly (DWIM!)
const auto margin = neighbor_layerm->flow(frExternalPerimeter).scaled_width();
const auto too_narrow = diff(
new_internal_solid,
offset2(new_internal_solid, -margin, +margin, CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, 5),
true
);
if (!too_narrow.empty())
new_internal_solid = solid = diff(new_internal_solid, too_narrow);
}
// make sure the new internal solid is wide enough, as it might get collapsed
// when spacing is added in Slic3r::Fill
{
// require at least this size
const auto margin = 3 * layerm->flow(frSolidInfill).scaled_width();
// 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.
const auto too_narrow = diff(
new_internal_solid,
offset2(new_internal_solid, -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 tmp;
for (auto& s : neighbor_fill_surfaces)
if (s.is_internal() && !s.is_bridge())
append_to(tmp, (Polygons)s);
const auto grown = intersection(
offset(too_narrow, +margin),
// Discard bridges as they are grown for anchoring and we cant
// remove such anchors. (This may happen when a bridge is being
// anchored onto a wall where little space remains after the bridge
// is grown, and that little space is an internal solid shell so
// it triggers this too_narrow logic.)
tmp
);
append_to(new_internal_solid, grown);
solid = new_internal_solid;
}
}
// internal-solid are the union of the existing internal-solid surfaces
// and new ones
Polygons tmp { to_polygons(neighbor_fill_surfaces.filter_by_type(stInternal | stSolid)) };
polygons_append(tmp, new_internal_solid);
const auto internal_solid = union_ex(tmp);
// subtract intersections from layer surfaces to get resulting internal surfaces
tmp = to_polygons(neighbor_fill_surfaces.filter_by_type(stInternal));
const auto internal = diff_ex(tmp, to_polygons(internal_solid), 1);
// assign resulting internal surfaces to layer
neighbor_layerm->fill_surfaces.clear();
neighbor_layerm->fill_surfaces.append(internal, stInternal);
// assign new internal-solid surfaces to layer
neighbor_layerm->fill_surfaces.append(internal_solid, stInternal | stSolid);
// assign top and bottom surfaces to layer
SurfaceCollection tmp_coll;
for (const auto& s : neighbor_fill_surfaces.surfaces)
if (s.surface_type == stTop || s.is_bottom())
tmp_coll.append(s);
for (auto s : tmp_coll.group()) {
Polygons tmp;
append_to(tmp, to_polygons(internal_solid));
append_to(tmp, to_polygons(internal));
const auto solid_surfaces = diff_ex(to_polygons(s), tmp, true);
neighbor_layerm->fill_surfaces.append(solid_surfaces, s.front()->surface_type);
}
}
}
// 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.
void
PrintObject::clip_fill_surfaces()
{
if (! this->config.infill_only_where_needed.value ||
! std::any_of(this->print()->regions.begin(), this->print()->regions.end(),
[](const PrintRegion *region) { return region->config.fill_density > 0; }))
return;
// We only want infill under ceilings; this is almost like an
// internal support material.
// Proceed top-down, skipping the bottom layer.
Polygons upper_internal;
for (int layer_id = int(this->layers.size()) - 1; layer_id > 0; --layer_id) {
const Layer *layer = this->layers[layer_id];
Layer *lower_layer = this->layers[layer_id - 1];
// Detect things that we need to support.
// Solid surfaces to be supported.
Polygons overhangs;
for (const LayerRegion *layerm : layer->regions) {
for (const Surface &surface : layerm->fill_surfaces.surfaces) {
Polygons polygons = to_polygons(surface.expolygon);
if (surface.is_solid())
polygons_append(overhangs, polygons);
//polygons_append(fill_surfaces, std::move(polygons));
}
}
// We also need to support perimeters when there's at least one full unsupported loop
{
// Get perimeters area as the difference between slices and fill_surfaces
Polygons fill_surfaces;
for (const LayerRegion *layerm : layer->regions)
polygons_append(fill_surfaces, (Polygons)layerm->fill_surfaces);
Polygons perimeters = diff(layer->slices, fill_surfaces);
// Only consider the area that is not supported by lower perimeters
Polygons lower_layer_fill_surfaces;
for (const LayerRegion *layerm : lower_layer->regions)
polygons_append(lower_layer_fill_surfaces, (Polygons)layerm->fill_surfaces);
perimeters = intersection(perimeters, lower_layer_fill_surfaces, true);
// Only consider perimeter areas that are at least one extrusion width thick.
//FIXME Offset2 eats out from both sides, while the perimeters are create outside in.
//Should the pw not be half of the current value?
float pw = FLT_MAX;
for (const LayerRegion *layerm : layer->regions)
pw = std::min<float>(pw, layerm->flow(frPerimeter).scaled_width());
perimeters = offset2(perimeters, -pw, +pw);
// Append such thick perimeters to the areas that need support
polygons_append(overhangs, perimeters);
}
// Find new internal infill.
{
polygons_append(overhangs, std::move(upper_internal));
// get our current internal fill boundaries
Polygons lower_layer_internal_surfaces;
for (const auto* layerm : lower_layer->regions)
polygons_append(lower_layer_internal_surfaces, to_polygons(
layerm->fill_surfaces.filter_by_type({ stInternal, stInternal | stVoid })
));
upper_internal = intersection(overhangs, lower_layer_internal_surfaces);
}
// Apply new internal infill to regions.
for (auto* layerm : lower_layer->regions) {
if (layerm->region()->config.fill_density.value == 0)
continue;
Polygons internal{ to_polygons(layerm->fill_surfaces.filter_by_type({ stInternal, stInternal | stVoid })) };
layerm->fill_surfaces.remove_types({ stInternal, stInternal | stVoid });
layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, true), stInternal);
layerm->fill_surfaces.append(diff_ex (internal, upper_internal, true), stInternal | stVoid);
// If there are voids it means that our internal infill is not adjacent to
// perimeters. In this case it would be nice to add a loop around infill to
// make it more robust and nicer. TODO.
}
}
}
// Simplify the sliced model, if "resolution" configuration parameter > 0.
// The simplification is problematic, because it simplifies the slices independent from each other,
// which makes the simplified discretization visible on the object surface.
void
PrintObject::_simplify_slices(double distance)
{
for (auto* layer : this->layers) {
layer->slices.simplify(distance);
for (auto* layerm : layer->regions)
layerm->slices.simplify(distance);
}
}
}