diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 87849b67da9..d989d2eb219 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -409,6 +409,8 @@ set(SLIC3R_SOURCES TriangleSelectorWrapper.cpp TriangleSelectorWrapper.hpp MTUtils.hpp + ZDither.hpp + ZDither.cpp Zipper.hpp Zipper.cpp MinAreaBoundingBox.hpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 9e7ffc009a1..6b840476642 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -396,6 +396,7 @@ std::vector> GCodeGener coordf_t print_z; size_t object_idx; size_t layer_idx; + coordf_t layer_height; }; std::vector per_object(print.objects().size(), ObjectsLayerToPrint()); @@ -409,11 +410,14 @@ std::vector> GCodeGener for (const ObjectLayerToPrint <p : per_object[i]) { ordering_item.print_z = ltp.print_z(); ordering_item.layer_idx = <p - &front; + ordering_item.layer_height = ltp.layer()->height; ordering.emplace_back(ordering_item); } } - std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; }); + std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { + return fabs(oi1.print_z - oi2.print_z) > EPSILON ? oi1.print_z < oi2.print_z : oi1.layer_height < oi2.layer_height; + }); std::vector> layers_to_print; @@ -422,16 +426,16 @@ std::vector> GCodeGener // Find the last layer with roughly the same print_z. size_t j = i + 1; coordf_t zmax = ordering[i].print_z + EPSILON; - for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j); + coordf_t hmax = ordering[i].layer_height + EPSILON; // z-dithering may make layers of different height but same print_z + for (; j < ordering.size() && ordering[j].print_z <= zmax && ordering[j].layer_height < hmax; ++j); // Merge into layers_to_print. std::pair merged; // Assign an average print_z to the set of layers with nearly equal print_z. merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z); - merged.second.assign(print.objects().size(), ObjectLayerToPrint()); + // z-dithering may result in 2 layers from the same object in merged for (; i < j; ++i) { const OrderingItem& oi = ordering[i]; - assert(merged.second[oi.object_idx].layer() == nullptr); - merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]); + merged.second.emplace_back(std::move(per_object[oi.object_idx][oi.layer_idx])); } layers_to_print.emplace_back(std::move(merged)); } @@ -2064,14 +2068,13 @@ namespace Skirt { // not at the print_z of the interlaced support material layers. std::map> skirt_loops_per_extruder_out; if (print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt && + // Bevare of several layers with same print_z but different heights which could happen + // in case of dithered overhanging layers with support + fabs(skirt_done.back() - layer_tools.print_z) > EPSILON && // Not enough skirt layers printed yet. - //FIXME infinite or high skirt does not make sense for sequential print! - (skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt())) { + (layer_tools.print_z < print.skirt_print_z() + EPSILON || print.has_infinite_skirt())) { bool valid = ! skirt_done.empty() && skirt_done.back() < layer_tools.print_z - EPSILON; assert(valid); - // This print_z has not been extruded yet (sequential print) - // FIXME: The skirt_done should not be empty at this point. The check is a workaround - // of https://github.com/prusa3d/PrusaSlicer/issues/5652, but it deserves a real fix. if (valid) { #if 0 // Prime just the first printing extruder. This is original Slic3r's implementation. diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 5e7e7835dab..2f09b0f292a 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -334,6 +334,7 @@ class Layer coordf_t print_z; // Z used for printing in unscaled coordinates coordf_t height; // layer height in unscaled coordinates coordf_t bottom_z() const { return this->print_z - this->height; } + bool dithered = false; // is this is a layer produced by z-dithering. //Extrusions estimated to be seriously malformed, estimated during "Estimating curled extrusions" step. These lines should be avoided during fast travels. CurledLines curled_lines; @@ -401,6 +402,8 @@ class Layer friend class PrintObject; friend std::vector new_layers(PrintObject*, const std::vector&); friend std::string fix_slicing_errors(LayerPtrs&, const std::function&); + // Create dithering layer. bottom & top >= 0 and <= 1 + friend Layer *make_dithered_layer(Layer *refLayer, double bottom, double top); Layer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) : upper_layer(nullptr), lower_layer(nullptr), @@ -465,12 +468,13 @@ class SupportLayer : public Layer }; template -inline std::vector zs_from_layers(const LayerContainer &layers) +inline std::vector zs_from_layers(const LayerContainer &layers, bool exclude_dithered = true) { std::vector zs; zs.reserve(layers.size()); for (const Layer *l : layers) - zs.emplace_back((float)l->slice_z); + if (!(l->dithered && exclude_dithered)) + zs.emplace_back((float) l->slice_z); return zs; } diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index b26e696cc97..ce8e926a819 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -118,8 +118,15 @@ void LayerRegion::make_perimeters( auto perimeters_begin = uint32_t(m_perimeters.size()); auto gap_fills_begin = uint32_t(m_thin_fills.size()); auto fill_expolygons_begin = uint32_t(fill_expolygons.size()); - if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase) - PerimeterGenerator::process_arachne( + bool use_arachne = this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase; + bool dithered_layer = this->layer()->dithered; + // arachne width averaging is not so good for thin areas of dithered layers becuase it may generate open contours + // which make routh attachment/seams between dithered layers and adjacent non-dithered (regular) layers. + // On the other hand, for very narrow regions, classic algothim sometimes makes no perimeters at all. + // In this case we better fall back on arachne algorithm (even if user selected classic). + // Thus the logic of trying classic first for dithered layers and if it makes no perimeters then trying arachne. + if (!use_arachne || dithered_layer) + PerimeterGenerator::process_classic( // input: params, surface, @@ -129,8 +136,8 @@ void LayerRegion::make_perimeters( m_perimeters, m_thin_fills, fill_expolygons); - else - PerimeterGenerator::process_classic( + if (dithered_layer && m_perimeters.empty() || !dithered_layer && use_arachne) + PerimeterGenerator::process_arachne( // input: params, surface, diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 9e3d2833a30..7aefd79383a 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -469,7 +469,7 @@ static std::vector s_Preset_print_options { "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits", - "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", + "perimeter_generator", "z_dither", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "wall_distribution_count", "min_feature_size", "min_bead_width" }; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 30893f22456..85f70d2301d 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -599,6 +599,13 @@ std::string Print::validate(std::vector* warnings) const return _u8L("Variable layer height is not supported with Organic supports."); } + if (m_default_object_config.z_dither) { + if (m_default_region_config.infill_every_layers > 1) + return _u8L("Z-dither slicing option is not compatible with option to combine infills of multiple layers."); + if (extruders.size() > 1) // Is there a better way to check for a possibility of multimaterial printing? + return _u8L("Z-dither slicing option is currently not supported for printers with multiple extruders."); + } + if (this->has_wipe_tower() && ! m_objects.empty()) { // Make sure all extruders use same diameter filament and have the same nozzle diameter // EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments @@ -1070,18 +1077,17 @@ void Print::_make_skirt() // The skirt_height option from config is expressed in layers, but our // object might have different layer heights, so we need to find the print_z // of the highest layer involved. - // Note that unless has_infinite_skirt() == true - // the actual skirt might not reach this $skirt_height_z value since the print - // order of objects on each layer is not guaranteed and will not generally - // include the thickest object first. It is just guaranteed that a skirt is - // prepended to the first 'n' layers (with 'n' = skirt_height). - // $skirt_height_z in this case is the highest possible skirt height for safety. - coordf_t skirt_height_z = 0.; + m_skirt_height_z = 0.; for (const PrintObject *object : m_objects) { - size_t skirt_layers = this->has_infinite_skirt() ? - object->layer_count() : - std::min(size_t(m_config.skirt_height.value), object->layer_count()); - skirt_height_z = std::max(skirt_height_z, object->m_layers[skirt_layers-1]->print_z); + size_t skirt_layer = this->has_infinite_skirt() ? object->layer_count() - 1: 0; + if (!this->has_infinite_skirt()) { + for (size_t i = 0, non_dithered = 0; i < object->layer_count() && non_dithered < size_t(m_config.skirt_height.value); i++) + if (!object->m_layers[i]->dithered) { // Skip dithered layers + skirt_layer = i; + non_dithered += 1; + } + } + m_skirt_height_z = std::max(m_skirt_height_z, object->m_layers[skirt_layer]->print_z); } // Collect points from all layers contained in skirt height. @@ -1090,7 +1096,7 @@ void Print::_make_skirt() Points object_points; // Get object layers up to skirt_height_z. for (const Layer *layer : object->m_layers) { - if (layer->print_z > skirt_height_z) + if (layer->print_z > m_skirt_height_z) break; for (const ExPolygon &expoly : layer->lslices) // Collect the outer contour points only, ignore holes for the calculation of the convex hull. @@ -1098,7 +1104,7 @@ void Print::_make_skirt() } // Get support layers up to skirt_height_z. for (const SupportLayer *layer : object->support_layers()) { - if (layer->print_z > skirt_height_z) + if (layer->print_z > m_skirt_height_z) break; layer->support_fills.collect_points(object_points); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 3d555bdbcea..91f4dc9c500 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -400,6 +400,8 @@ class PrintObject : public PrintObjectBaseWithState double skirt_first_layer_height() const; Flow brim_flow() const; Flow skirt_flow() const; + coordf_t skirt_print_z() const { return m_skirt_height_z; }; std::vector object_extruders() const; std::vector support_material_extruders() const; @@ -704,6 +707,7 @@ class Print : public PrintBaseWithState // It does NOT encompass MMU/MMU2 starting (wipe) areas. Polygon m_first_layer_convex_hull; Points m_skirt_convex_hull; + coordf_t m_skirt_height_z; // Following section will be consumed by the GCodeGenerator. ToolOrdering m_tool_ordering; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index cedad6ceb6a..11076bf33f8 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2508,6 +2508,14 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(1)); + def = this->add("z_dither", coBool); + def->label = L("Z-dither"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("This experimental setting is used to halve stairstep effect on low slope surfaces. " + "Near slice periphery it introduces additional layers that are 25% and 50% of nominal layer height."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("slowdown_below_layer_time", coInts); def->label = L("Slow down if layer print time is below"); def->tooltip = L("If layer print time is estimated below this number of seconds, print moves " @@ -4030,8 +4038,6 @@ void PrintConfigDef::init_sla_params() def->multiline = true; def->full_width = true; def->height = 13; - // TODO currently notes are the only way to pass data - // for non-PrusaResearch printers. We therefore need to always show them def->mode = comSimple; def->set_default_value(new ConfigOptionString("")); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5f9d37a0fe6..7a468b68399 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -598,6 +598,7 @@ PRINT_CONFIG_CLASS_DEFINE( // The rest ((ConfigOptionBool, thick_bridges)) ((ConfigOptionFloat, xy_size_compensation)) + ((ConfigOptionBool, z_dither)) ((ConfigOptionBool, wipe_into_objects)) ) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index dc5a86fc3bc..9d76d33104d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -209,8 +209,11 @@ void PrintObject::make_perimeters() PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); - LayerRegion &layerm = *m_layers[layer_idx]->get_region(region_id); - const LayerRegion &upper_layerm = *m_layers[layer_idx+1]->get_region(region_id); + LayerRegion &layerm = *m_layers[layer_idx]->get_region(region_id); + int upper_layer_idx = this->next_layer_index(layer_idx, false); + if (upper_layer_idx == m_layers.size()) + continue; + const LayerRegion &upper_layerm = *m_layers[upper_layer_idx]->get_region(region_id); const Polygons upper_layerm_polygons = to_polygons(upper_layerm.slices().surfaces); // Filter upper layer polygons in intersection_ppl by their bounding boxes? // my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; @@ -284,6 +287,9 @@ void PrintObject::prepare_infill() if (! this->set_started(posPrepareInfill)) return; + for (size_t idx = 0; idx < m_layers.size(); ++idx) + assert(m_layers[idx]->id() == idx + m_config.raft_layers); // subsequent calls will depend on such numbering + m_print->set_status(30, _u8L("Preparing infill")); if (m_typed_slices) { @@ -672,6 +678,18 @@ Layer* PrintObject::add_layer(int id, coordf_t height, coordf_t print_z, coordf_ return m_layers.back(); } +// Returns index of a layer below or above the idx. +// With introduction of z-dithering we can no longer rely of just incrementing or decrementing indecies. +int PrintObject::next_layer_index(size_t idx, bool lower) const +{ + const Layer *current_layer = m_layers[idx]; + const Layer *next_layer = lower ? current_layer->lower_layer : current_layer->upper_layer; + if (next_layer != nullptr) + return int(next_layer->id() - m_config.raft_layers); + else + return lower ? -1 : int(m_layers.size()); +} + void PrintObject::clear_support_layers() { for (Layer *l : m_support_layers) @@ -754,11 +772,12 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posSlice); } else if (opt_key == "support_material") { steps.emplace_back(posSupportMaterial); - if (m_config.support_material_contact_distance == 0.) { + if (m_config.support_material_contact_distance == 0. || m_config.z_dither) { // Enabling / disabling supports while soluble support interface is enabled. // This changes the bridging logic (bridging enabled without supports, disabled with supports). - // Reset everything. // See GH #1482 for details. + // Similarly enabling / disabling supports affects the logic of dithered layer calculations + // Reset everything. steps.emplace_back(posSlice); } } else if ( @@ -871,7 +890,8 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "wall_transition_angle" || opt_key == "wall_distribution_count" || opt_key == "min_feature_size" - || opt_key == "min_bead_width") { + || opt_key == "min_bead_width" + || opt_key == "z_dither") { steps.emplace_back(posSlice); } else if ( opt_key == "seam_position" @@ -1019,8 +1039,8 @@ void PrintObject::detect_surfaces_type() LayerRegion *layerm = layer->m_regions[region_id]; // comparison happens against the *full* slices (considering all regions) // unless internal shells are requested - Layer *upper_layer = (idx_layer + 1 < this->layer_count()) ? m_layers[idx_layer + 1] : nullptr; - Layer *lower_layer = (idx_layer > 0) ? m_layers[idx_layer - 1] : nullptr; + Layer *upper_layer = layer->upper_layer; + Layer *lower_layer = layer->lower_layer; // collapse very narrow parts (using the safety offset in the diff is not enough) float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f; @@ -1216,7 +1236,7 @@ void PrintObject::process_external_surfaces() [this, &surfaces_covered, &layer_expansions_and_voids, unsupported_width](const tbb::blocked_range& range) { PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) - if (layer_expansions_and_voids[layer_idx + 1]) { + if (layer_expansions_and_voids[next_layer_index(layer_idx, false)]) { // Layer above is partially filled with solid infill (top, bottom, bridging...), // while some sparse inill regions are empty (0% infill). m_print->throw_if_canceled(); @@ -1244,11 +1264,12 @@ void PrintObject::process_external_surfaces() for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); // BOOST_LOG_TRIVIAL(trace) << "Processing external surface, layer" << m_layers[layer_idx]->print_z; - m_layers[layer_idx]->get_region(int(region_id))->process_external_surfaces( + Layer *lower_layer = this->m_layers[layer_idx]->lower_layer; + Polygons *covered = (lower_layer == nullptr || surfaces_covered.empty() || surfaces_covered[lower_layer->id()].empty()) // lower layer - (layer_idx == 0) ? nullptr : m_layers[layer_idx - 1], + ? nullptr : &surfaces_covered[lower_layer->id()]; // lower layer polygons with density > 0% - (layer_idx == 0 || surfaces_covered.empty() || surfaces_covered[layer_idx - 1].empty()) ? nullptr : &surfaces_covered[layer_idx - 1]); + m_layers[layer_idx]->get_region(int(region_id))->process_external_surfaces(lower_layer, covered); } } ); @@ -1468,12 +1489,13 @@ void PrintObject::discover_vertical_shells() if (int n_top_layers = region_config.top_solid_layers.value; n_top_layers > 0) { // Gather top regions projected to this layer. coordf_t print_z = layer->print_z; - int i = int(idx_layer) + 1; - int itop = int(idx_layer) + n_top_layers; + int i = next_layer_index(idx_layer, false); + int count = 1; bool at_least_one_top_projected = false; for (; i < int(cache_top_botom_regions.size()) && - (i < itop || m_layers[i]->print_z - print_z < region_config.top_solid_min_thickness - EPSILON); - ++ i) { + (count < n_top_layers || + m_layers[i]->print_z - print_z < region_config.top_solid_min_thickness - EPSILON); + i = next_layer_index(i, false), ++count) { at_least_one_top_projected = true; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; combine_holes(cache.holes); @@ -1491,18 +1513,18 @@ void PrintObject::discover_vertical_shells() if (one_more_layer_below_top_bottom_surfaces) if (i < int(cache_top_botom_regions.size()) && - (i <= itop || m_layers[i]->bottom_z() - print_z < region_config.top_solid_min_thickness - EPSILON)) + (count <= n_top_layers || m_layers[i]->bottom_z() - print_z < region_config.top_solid_min_thickness - EPSILON)) combine_holes(cache_top_botom_regions[i].holes); } if (int n_bottom_layers = region_config.bottom_solid_layers.value; n_bottom_layers > 0) { // Gather bottom regions projected to this layer. coordf_t bottom_z = layer->bottom_z(); - int i = int(idx_layer) - 1; - int ibottom = int(idx_layer) - n_bottom_layers; + int i = next_layer_index(idx_layer, true); + int count = 1; bool at_least_one_bottom_projected = false; - for (; i >= 0 && - (i > ibottom || bottom_z - m_layers[i]->bottom_z() < region_config.bottom_solid_min_thickness - EPSILON); - -- i) { + for (; i >= 0 && (count < n_bottom_layers || + bottom_z - m_layers[i]->bottom_z() < region_config.bottom_solid_min_thickness - EPSILON); + i = next_layer_index(i, true), ++count) { at_least_one_bottom_projected = true; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; combine_holes(cache.holes); @@ -1518,7 +1540,7 @@ void PrintObject::discover_vertical_shells() if (one_more_layer_below_top_bottom_surfaces) if (i >= 0 && - (i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_solid_min_thickness - EPSILON)) + (count < n_bottom_layers || bottom_z - m_layers[i]->print_z < region_config.bottom_solid_min_thickness - EPSILON)) combine_holes(cache_top_botom_regions[i].holes); } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -1614,10 +1636,10 @@ void PrintObject::discover_vertical_shells() Polygons object_volume; Polygons internal_volume; { - Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{}; - Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ? - to_polygons(m_layers[idx_layer + 1]->lslices) : - Polygons{}; + int lower_layer_index = next_layer_index(idx_layer, true); + Polygons shrinked_bottom_slice = lower_layer_index >= 0 ? to_polygons(m_layers[lower_layer_index]->lslices) : Polygons{}; + int upper_layer_index = next_layer_index(idx_layer, false); + Polygons shrinked_upper_slice = upper_layer_index < m_layers.size() ? to_polygons(m_layers[upper_layer_index]->lslices) : Polygons{}; object_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice); internal_volume = closing(polygonsInternal, float(SCALED_EPSILON)); } @@ -1919,8 +1941,8 @@ void PrintObject::bridge_over_infill() std::vector layers_to_generate_infill; for (const auto &pair : surfaces_by_layer) { assert(pair.first > 0); - infill_lines[pair.first - 1] = {}; - layers_to_generate_infill.push_back(pair.first - 1); + infill_lines[next_layer_index(pair.first, true)] = {}; + layers_to_generate_infill.push_back(next_layer_index(pair.first, true)); } tbb::parallel_for(tbb::blocked_range(0, layers_to_generate_infill.size()), [po = static_cast(this), @@ -1998,15 +2020,15 @@ void PrintObject::bridge_over_infill() } // LAMBDA to gather areas with sparse infill deep enough that we can fit thick bridges there. - auto gather_areas_w_depth = [target_flow_height_factor](const PrintObject *po, int lidx, float target_flow_height) { + auto gather_areas_w_depth = [target_flow_height_factor, this](const PrintObject *po, int lidx, float target_flow_height) { // Gather layers sparse infill areas, to depth defined by used bridge flow ExPolygons layers_sparse_infill{}; ExPolygons not_sparse_infill{}; double bottom_z = po->get_layer(lidx)->print_z - target_flow_height * target_flow_height_factor - EPSILON; - for (int i = int(lidx) - 1; i >= 0; --i) { + for (int i = next_layer_index(lidx, true); i >= 0; i = next_layer_index(i, true)) { // Stop iterating if layer is lower than bottom_z and at least one iteration was made const Layer *layer = po->get_layer(i); - if (layer->print_z < bottom_z && i < int(lidx) - 1) + if (layer->print_z < bottom_z && i < next_layer_index(lidx, true)) break; for (const LayerRegion *region : layer->regions()) { @@ -2363,7 +2385,7 @@ void PrintObject::bridge_over_infill() total_fill_area = closing(total_fill_area, float(SCALED_EPSILON)); expansion_area = closing(expansion_area, float(SCALED_EPSILON)); expansion_area = intersection(expansion_area, deep_infill_area); - Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing)); + Polylines anchors = intersection_pl(infill_lines[po->next_layer_index(lidx,true)], shrink(expansion_area, spacing)); Polygons internal_unsupported_area = shrink(deep_infill_area, spacing * 4.5); #ifdef DEBUG_BRIDGE_OVER_INFILL @@ -2737,7 +2759,7 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c // Polygons upper_internal; // for (int layer_id = int(m_layers.size()) - 1; layer_id > 0; -- layer_id) { // Layer *layer = m_layers[layer_id]; -// Layer *lower_layer = m_layers[layer_id - 1]; +// Layer* lower_layer = layer->lower_layer; // // Detect things that we need to support. // // Cummulative fill surfaces. // Polygons fill_surfaces; @@ -2843,6 +2865,9 @@ void PrintObject::discover_horizontal_shells() // fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries. void PrintObject::combine_infill() { + // z-dithering is not currently compatible with combining infills + if (m_config.z_dither) return; + // Work on each region separately. for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { const PrintRegion ®ion = this->printing_region(region_id); @@ -2869,7 +2894,7 @@ void PrintObject::combine_infill() // would exceed max layer height or max combined layer count. if (current_height + layer->height >= nozzle_diameter + EPSILON || num_layers >= every) { // Append combination to lower layer. - combine[layer_idx - 1] = num_layers; + combine[next_layer_index(layer_idx, true)] = num_layers; current_height = 0.; num_layers = 0; } @@ -2962,6 +2987,7 @@ void PrintObject::_generate_support_material() static void project_triangles_to_slabs(SpanOfConstPtrs layers, const indexed_triangle_set &custom_facets, const Transform3f &tr, bool seam, std::vector &out) { + // this function relies on absence of dithered layers in "layers" and therefore used only by slicing of supports if (custom_facets.indices.empty()) return; @@ -3162,16 +3188,28 @@ void PrintObject::project_and_append_custom_facets( seam, out); else { std::vector projected; + SpanOfConstPtrs layers = this->layers(); // Support blockers or enforcers. Project downward facing painted areas upwards to their respective slicing plane. - slice_mesh_slabs(custom_facets, zs_from_layers(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &projected, [](){}); + slice_mesh_slabs(custom_facets, zs_from_layers(layers, true), this->trafo_centered() * mv->get_matrix(), nullptr, &projected, [](){}); // Merge these projections with the output, layer by layer. assert(! projected.empty()); - assert(out.empty() || out.size() == projected.size()); - if (out.empty()) - out = std::move(projected); - else - for (size_t i = 0; i < out.size(); ++ i) - append(out[i], std::move(projected[i])); + bool out_empty = out.empty(); + assert(out_empty || out.size() == layers.size()); + size_t proj_idx = 0; + + for (size_t i = 0; i < layers.size(); ++i) { + if (!layers[i]->dithered) { + if (out_empty) + out.emplace_back(std::move(projected[proj_idx])); + else + append(out[i], std::move(projected[proj_idx])); + + proj_idx++; + } else if (out_empty) { + out.emplace_back(Polygons()); + } + } + assert(proj_idx == projected.size()); } } } diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 3da6e3aa3d2..656c73d64d9 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -9,6 +9,7 @@ #include "MultiMaterialSegmentation.hpp" #include "Print.hpp" #include "ShortestPath.hpp" +#include "ZDither.hpp" #include @@ -47,21 +48,24 @@ static std::vector slice_volume( const ModelVolume &volume, const std::vector &zs, const MeshSlicingParamsEx ¶ms, + std::vector *sublayers, const std::function &throw_on_cancel_callback) { - std::vector layers; - if (! zs.empty()) { - indexed_triangle_set its = volume.mesh().its; - if (its.indices.size() > 0) { - MeshSlicingParamsEx params2 { params }; - params2.trafo = params2.trafo * volume.get_matrix(); - if (params2.trafo.rotation().determinant() < 0.) - its_flip_triangles(its); - layers = slice_mesh_ex(its, zs, params2, throw_on_cancel_callback); - throw_on_cancel_callback(); - } - } - return layers; + indexed_triangle_set its = volume.mesh().its; + if (zs.empty() || its.indices.size() == 0) + return std::vector(); + + MeshSlicingParamsEx params2{params}; + params2.trafo = params2.trafo * volume.get_matrix(); + if (params2.trafo.rotation().determinant() < 0.) + its_flip_triangles(its); + std::vector expolys = slice_mesh_ex(its, zs, params2, throw_on_cancel_callback); + if (params2.z_dither_mode != Z_dither_mode::None) + expolys = z_dither(its, zs, params2, expolys, sublayers, throw_on_cancel_callback); + else + *sublayers = std::vector(expolys.size()); + throw_on_cancel_callback(); + return expolys; } // Slice single triangle mesh. @@ -71,13 +75,14 @@ static std::vector slice_volume( const std::vector &z, const std::vector &ranges, const MeshSlicingParamsEx ¶ms, + std::vector * outSublayers, const std::function &throw_on_cancel_callback) { std::vector out; if (! z.empty() && ! ranges.empty()) { if (ranges.size() == 1 && z.front() >= ranges.front().first && z.back() < ranges.front().second) { // All layers fit into a single range. - out = slice_volume(volume, z, params, throw_on_cancel_callback); + out = slice_volume(volume, z, params, outSublayers, throw_on_cancel_callback); } else { std::vector z_filtered; std::vector> n_filtered; @@ -93,12 +98,15 @@ static std::vector slice_volume( n_filtered.emplace_back(std::make_pair(first, i)); } if (! n_filtered.empty()) { - std::vector layers = slice_volume(volume, z_filtered, params, throw_on_cancel_callback); + std::vector sublayers; + std::vector layers = slice_volume(volume, z_filtered, params, &sublayers, throw_on_cancel_callback); out.assign(z.size(), ExPolygons()); i = 0; for (const std::pair &span : n_filtered) - for (size_t j = span.first; j < span.second; ++ j) + for (size_t j = span.first; j < span.second; ++j) { out[j] = std::move(layers[i ++]); + outSublayers->emplace_back(std::move(sublayers[j])); + } } } } @@ -117,6 +125,12 @@ static inline bool model_volume_needs_slicing(const ModelVolume &mv) return type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER; } +struct VolumeSublayers +{ + ObjectID volume_id; + std::vector sublayers; +}; + // Slice printable volumes, negative volumes and modifier volumes, sorted by ModelVolume::id(). // Apply closing radius. // Apply positive XY compensation to ModelVolumeType::MODEL_PART and ModelVolumeType::PARAMETER_MODIFIER, not to ModelVolumeType::NEGATIVE_VOLUME. @@ -128,12 +142,15 @@ static std::vector slice_volumes_inner( ModelVolumePtrs model_volumes, const std::vector &layer_ranges, const std::vector &zs, + std::vector *outSublayers, const std::function &throw_on_cancel_callback) { model_volumes_sort_by_id(model_volumes); std::vector out; out.reserve(model_volumes.size()); + outSublayers->clear(); + outSublayers->reserve(model_volumes.size()); std::vector slicing_ranges; if (layer_ranges.size() > 1) @@ -144,6 +161,11 @@ static std::vector slice_volumes_inner( params_base.extra_offset = 0; params_base.trafo = object_trafo; params_base.resolution = print_config.resolution.value; + const std::vector &diameters = print_config.nozzle_diameter.values; + params_base.nozzle_diameter = float(*std::min_element(diameters.begin(), diameters.end())); + params_base.z_dither_mode = print_object_config.z_dither + ? (print_object_config.support_material ? Z_dither_mode::Both : Z_dither_mode::Upward) + : Z_dither_mode::None; switch (print_object_config.slicing_mode.value) { case SlicingMode::Regular: params_base.mode = MeshSlicingParams::SlicingMode::Regular; break; @@ -175,10 +197,12 @@ static std::vector slice_volumes_inner( for (; params.slicing_mode_normal_below_layer < zs.size() && zs[params.slicing_mode_normal_below_layer] < region_config.bottom_solid_min_thickness - EPSILON; ++ params.slicing_mode_normal_below_layer); } - out.push_back({ - model_volume->id(), - slice_volume(*model_volume, zs, params, throw_on_cancel_callback) - }); + std::vector sublayers; + std::vector expoly = slice_volume(*model_volume, zs, params, + &sublayers, + throw_on_cancel_callback); + out.push_back({model_volume->id(), std::move(expoly)}); + outSublayers->push_back({model_volume->id(), std::move(sublayers)}); } } else { assert(! print_config.spiral_vase); @@ -186,16 +210,20 @@ static std::vector slice_volumes_inner( for (const PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) if (layer_range.has_volume(model_volume->id())) slicing_ranges.emplace_back(layer_range.layer_height_range); - if (! slicing_ranges.empty()) - out.push_back({ - model_volume->id(), - slice_volume(*model_volume, zs, slicing_ranges, params, throw_on_cancel_callback) - }); + if (!slicing_ranges.empty()) { + std::vector sublayers; + std::vector expoly = slice_volume(*model_volume, zs, slicing_ranges, + params, &sublayers, + throw_on_cancel_callback); + out.push_back({model_volume->id(), std::move(expoly)}); + outSublayers->push_back({model_volume->id(), std::move(sublayers)}); + } } - if (! out.empty() && out.back().slices.empty()) + if (!out.empty() && out.back().slices.empty()) { out.pop_back(); + outSublayers->pop_back(); + } } - return out; } @@ -389,6 +417,7 @@ static std::vector> slices_to_regions( } } if (merged) + // to handle region overlaps. Indeed, one may intentionally let the regions overlap to produce crossing perimeters expolygons = closing_ex(expolygons, float(scale_(EPSILON))); slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons); i = j; @@ -540,10 +569,18 @@ void PrintObject::slice() tbb::parallel_for( tbb::blocked_range(1, m_layers.size()), [this](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - m_print->throw_if_canceled(); - Layer::build_up_down_graph(*m_layers[layer_idx - 1], *m_layers[layer_idx]); - } + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + m_print->throw_if_canceled(); + // Layer::build_up_down_graph and subsequent support stability checks + // appear to get in trouble when multiple layers point to the same + // above/below layer as happens in case of z-dithering. For now I just avoid + // mixing dithered and non-dithered layers in the same graph. + Layer *above = m_layers[layer_idx]; + Layer *below = above->lower_layer; + if (below != nullptr && above->dithered == below->dithered) + Layer::build_up_down_graph(*below, *above); + // Layer::build_up_down_graph(*m_layers[layer_idx - 1], *m_layers[layer_idx]); + } }); if (m_layers.empty()) throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); @@ -676,6 +713,99 @@ void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_can }); } +Layer *make_dithered_layer(Layer *refLayer, double bottom, double top) +{ + coordf_t height = refLayer->height; + coordf_t hi = refLayer->slice_z + height / 2; + coordf_t lo = refLayer->slice_z - height / 2; + coordf_t h_new = height * (top - bottom); + Layer * layer = new Layer(refLayer->id(), refLayer->object(), h_new, + refLayer->bottom_z() + height * coordf_t(top), + refLayer->bottom_z() + height * coordf_t(bottom) + h_new / 2); + layer->dithered = true; + return layer; +} + +void merge_sublayers_to_slices(std::vector & volume_slices, + std::vector &volume_sublayers, + int which, // 0 - bottom, 1 - halfUp, 2 - halfDn, 3 - top + int from, + int to) +{ + for (VolumeSublayers &sublayers : volume_sublayers) { + VolumeSlices &v_slices = volume_slices_find_by_id(volume_slices, sublayers.volume_id); + auto & slices = v_slices.slices; + if (which == 0) + slices.insert(slices.begin() + to, std::move(sublayers.sublayers[from].bottom_)); + else if (which == 1) + slices.insert(slices.begin() + to, std::move(sublayers.sublayers[from].halfUp_)); + else if (which == 2) + slices.insert(slices.begin() + to, std::move(sublayers.sublayers[from].halfDn_)); + else if (which == 3) + slices.insert(slices.begin() + to, std::move(sublayers.sublayers[from].top_)); + else + BOOST_LOG_TRIVIAL(error) << "merge_sublayers_to_slices illegal call"; + } +} + +LayerPtrs add_dithering_layers(const LayerPtrs & layers, + std::vector & volume_slices, + std::vector &volume_sublayers) +{ + LayerPtrs original(layers); + LayerPtrs resulting; + + for (int ll = 0; ll < original.size(); ll++) { + Layer *newLayer[4] = {nullptr, nullptr, nullptr, nullptr}; + if (std::any_of(volume_sublayers.begin(), volume_sublayers.end(), + [&ll](VolumeSublayers &v_sub) { + return !v_sub.sublayers[ll].bottom_.empty(); + })) { + newLayer[0] = make_dithered_layer(original[ll], 0., 0.25); + newLayer[0]->lower_layer = original[ll]->lower_layer; + merge_sublayers_to_slices(volume_slices, volume_sublayers, 0, ll, resulting.size()); + resulting.push_back(newLayer[0]); + } + if (std::any_of(volume_sublayers.begin(), volume_sublayers.end(), + [&ll](VolumeSublayers &v_sub) { + return !v_sub.sublayers[ll].halfUp_.empty(); + })) { + newLayer[1] = make_dithered_layer(original[ll], 0.25, 0.75); + newLayer[1]->lower_layer = newLayer[0]; + if (newLayer[0]) + newLayer[0]->upper_layer = newLayer[1]; + merge_sublayers_to_slices(volume_slices, volume_sublayers, 1, ll, resulting.size()); + resulting.push_back(newLayer[1]); + } + if (std::any_of(volume_sublayers.begin(), volume_sublayers.end(), + [&ll](VolumeSublayers &v_sub) { + return !v_sub.sublayers[ll].halfDn_.empty(); + })) { + newLayer[2] = make_dithered_layer(original[ll], 0.25, 0.75); + merge_sublayers_to_slices(volume_slices, volume_sublayers, 2, ll, resulting.size()); + resulting.push_back(newLayer[2]); + } + if (std::any_of(volume_sublayers.begin(), volume_sublayers.end(), + [&ll](VolumeSublayers &v_sub) { + return !v_sub.sublayers[ll].top_.empty(); + })) { + newLayer[3] = make_dithered_layer(original[ll], 0.75, 1.); + newLayer[3]->upper_layer = original[ll]->upper_layer; + newLayer[3]->lower_layer = newLayer[2]; + if (newLayer[2]) + newLayer[2]->upper_layer = newLayer[3]; + merge_sublayers_to_slices(volume_slices, volume_sublayers, 3, ll, resulting.size()); + resulting.push_back(newLayer[3]); + } + resulting.push_back(original[ll]); + } + // Renumber + size_t start = original[0]->id(); + for (size_t ll = 0; ll < resulting.size(); ll++) + resulting[ll]->set_id(start + ll); + return resulting; +} + // 1) Decides Z positions of the layers, // 2) Initializes layers and their regions // 3) Slices the object meshes @@ -699,12 +829,24 @@ void PrintObject::slice_volumes() layer->m_regions.emplace_back(new LayerRegion(layer, pr.get())); } - std::vector slice_zs = zs_from_layers(m_layers); + std::vector slice_zs = zs_from_layers(m_layers, true); + std::vector volume_sublayers; + std::vector volume_slices = slice_volumes_inner(print->config(), this->config(), this->trafo_centered(), + this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, &volume_sublayers, throw_on_cancel_callback); + + if (this->config().z_dither) { + m_layers = add_dithering_layers(m_layers, volume_slices, volume_sublayers); + for (Layer *layer : m_layers) + if (layer->dithered) { + layer->m_regions.reserve(m_shared_regions->all_regions.size()); + for (const std::unique_ptr &pr : m_shared_regions->all_regions) + layer->m_regions.emplace_back(new LayerRegion(layer, pr.get())); + } + slice_zs = zs_from_layers(m_layers, false); + } + std::vector> region_slices = slices_to_regions(this->model_object()->volumes, *m_shared_regions, slice_zs, - slice_volumes_inner( - print->config(), this->config(), this->trafo_centered(), - this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback), - throw_on_cancel_callback); + std::move(volume_slices), throw_on_cancel_callback); for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) { std::vector &by_layer = region_slices[region_id]; @@ -727,7 +869,7 @@ void PrintObject::slice_volumes() // Is any ModelVolume MMU painted? if (const auto& volumes = this->model_object()->volumes; - m_print->config().nozzle_diameter.size() > 1 && + m_print->config().nozzle_diameter.size() > 1 && !this->config().z_dither && std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) { // If XY Size compensation is also enabled, notify the user that XY Size compensation @@ -834,7 +976,9 @@ std::vector PrintObject::slice_support_volumes(const ModelVolumeType m std::vector slices; if (it_volume != it_volume_end) { // Found at least a single support volume of model_volume_type. - std::vector zs = zs_from_layers(this->layers()); + // Exclude z of ditthered layers. Do we need to improve this? + std::vector zs = zs_from_layers(this->layers(), true); + size_t num_layers = this->layers().size(); std::vector merge_layers; bool merge = false; const Print *print = this->print(); @@ -843,29 +987,41 @@ std::vector PrintObject::slice_support_volumes(const ModelVolumeType m params.trafo = this->trafo_centered(); for (; it_volume != it_volume_end; ++ it_volume) if ((*it_volume)->type() == model_volume_type) { - std::vector slices2 = slice_volume(*(*it_volume), zs, params, throw_on_cancel_callback); + std::vector sublayers2; + std::vector slices2 = slice_volume(*(*it_volume), zs, params, &sublayers2, throw_on_cancel_callback); if (slices.empty()) { - slices.reserve(slices2.size()); - for (ExPolygons &src : slices2) - slices.emplace_back(to_polygons(std::move(src))); + slices.reserve(num_layers); + size_t slice_idx = 0; + for (size_t i = 0; i < num_layers; i++) { + if (this->layers()[i]->dithered) + slices.emplace_back(Polygons()); + else { + slices.emplace_back(to_polygons(std::move(slices2[slice_idx]))); + slice_idx++; + } + } } else if (!slices2.empty()) { if (merge_layers.empty()) - merge_layers.assign(zs.size(), false); - for (size_t i = 0; i < zs.size(); ++ i) { - if (slices[i].empty()) - slices[i] = to_polygons(std::move(slices2[i])); - else if (! slices2[i].empty()) { - append(slices[i], to_polygons(std::move(slices2[i]))); - merge_layers[i] = true; - merge = true; + merge_layers.assign(num_layers, false); + size_t slice_idx = 0; + for (size_t i = 0; i < num_layers; ++ i) { + if (!this->layers()[i]->dithered) { + if (slices[i].empty()) + slices[i] = to_polygons(std::move(slices2[slice_idx])); + else if (!slices2[slice_idx].empty()) { + append(slices[i], to_polygons(std::move(slices2[slice_idx]))); + merge_layers[i] = true; + merge = true; + } + slice_idx++; } } } } if (merge) { std::vector to_merge; - to_merge.reserve(zs.size()); - for (size_t i = 0; i < zs.size(); ++ i) + to_merge.reserve(num_layers); + for (size_t i = 0; i < num_layers; ++ i) if (merge_layers[i]) to_merge.emplace_back(&slices[i]); tbb::parallel_for( diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index 937a4bf5df8..0a4450a66de 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1683,7 +1683,11 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { const Layer &layer = *object.layers()[layer_id]; - Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id - 1]->lslices); + const Layer *lower_layer = layer.lower_layer; + if (lower_layer == nullptr && layer_id != 0 && layer.dithered) + continue; + + Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(lower_layer->lslices); SlicesMarginCache slices_margin; auto [overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset] = diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 679ef0215fe..b92526d4bd2 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -1053,7 +1053,14 @@ std::tuple check_stability(const PrintObject SliceMappings slice_mappings; - for (size_t layer_idx = 0; layer_idx < po->layer_count(); ++layer_idx) { + // Skip over dithered layers + assert(!po->get_layer(0)->dithered); + auto next_layer_index = [&po](size_t layer_idx) { + const Layer *upper_layer = po->get_layer(layer_idx)->upper_layer; + return upper_layer != nullptr ? upper_layer->id() : po->layer_count(); + }; + + for (size_t layer_idx = 0; layer_idx < po->layer_count(); layer_idx = next_layer_index(layer_idx)) { cancel_func(); const Layer *layer = po->get_layer(layer_idx); float bottom_z = layer->bottom_z(); diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp index a7ad62acd9a..5e8a2c1c996 100644 --- a/src/libslic3r/TriangleMeshSlicer.hpp +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -39,6 +39,12 @@ struct MeshSlicingParams Transform3d trafo { Transform3d::Identity() }; }; +enum class Z_dither_mode { + None, // No z-dithering + Upward, // Dither just upward facing surfaces, no overhangs + Both // Dither Both upward andf downward surfacefaces. Requires presence of support. +}; + struct MeshSlicingParamsEx : public MeshSlicingParams { // Morphological closing operation when creating output expolygons, unscaled. @@ -48,6 +54,9 @@ struct MeshSlicingParamsEx : public MeshSlicingParams // Resolution for contour simplification, unscaled. // 0 = don't simplify. double resolution { 0 }; + // nozzle diameter (needed for z_dithering optimization) + float nozzle_diameter{0}; + Z_dither_mode z_dither_mode = Z_dither_mode::None; }; // All the following slicing functions shall produce consistent results with the same mesh, same transformation matrix and slicing parameters. diff --git a/src/libslic3r/ZDither.cpp b/src/libslic3r/ZDither.cpp new file mode 100644 index 00000000000..c285e00e857 --- /dev/null +++ b/src/libslic3r/ZDither.cpp @@ -0,0 +1,262 @@ +#include "ZDither.hpp" +#include "ExPolygon.hpp" +#include "TriangleMeshSlicer.hpp" +#include "Utils.hpp" + + +namespace Slic3r { + +// Find z where additional midlevel slices need to be made for z-dithering +// It is assumed that original layers corresponding to zs[i] will be filled +// from zs[i] - layerHight/2 till zs[i] + layerHeight/2 +// Required midlevel slices are to be computed halfway between layers, +// starting with top of layer 0 and ending with top of last layer ; + +void midslice_zs(const indexed_triangle_set &mesh, + const std::vector & zs, + const Transform3d & trafo, + float nozzle_diameter, + bool dither_both_up_n_down, + std::vector * mid_zs, + std::vector * upwrd_mididx, + std::vector * dnwrd_mididx) +{ + mid_zs->clear(); + upwrd_mididx->clear(); + dnwrd_mididx->clear(); + + auto n_zs = zs.size(); + if (n_zs < 2) return; + + std::vector transformed_vertices(mesh.vertices); + auto trafo_float = trafo.cast(); + for (stl_vertex &v : transformed_vertices) { v = trafo_float * v; } + + std::vector candidate_zs(n_zs); + std::vector layerHeight(n_zs); + float layerHeightMax = 0; + + for (auto i = 0; i < n_zs - 1; i++) { + // This may be a bit inaccurate if layer heights vary, but will still be an improvement. + candidate_zs[i] = (zs[i + 1] + zs[i]) / 2; + layerHeight[i] = zs[i + 1] - zs[i]; + layerHeightMax = std::max(layerHeight[i], layerHeightMax); + } + layerHeight[n_zs - 1] = layerHeight[n_zs - 2]; + candidate_zs[n_zs - 1] = zs[n_zs - 1] + layerHeight[n_zs - 1] / 2; + + upwrd_mididx->assign(n_zs, -1); + dnwrd_mididx->assign(n_zs, -1); + + float nDia = 1.0; // Cooficient to avoid cutting expolys with too vertical triangles (see below) + // With nozzle_diameter = 0.4 and layer_height = 0.25 + // nDia = 1.5 results in slopes below 22.5 degrees; nDia = 1 results in slopes under 32 degrees + // nDia = 0.7 - 42 degrees; nDia = 0.5 51.4 degrees + + float eps = layerHeightMax / 100; + + for (auto i = 0; i < mesh.indices.size(); ++i) { + const stl_triangle_vertex_indices &facetVertices = mesh.indices[i]; + stl_vertex vertex[3] = {transformed_vertices[facetVertices[0]], transformed_vertices[facetVertices[1]], + transformed_vertices[facetVertices[2]]}; + + Vec3f norm((vertex[1] - vertex[0]).cross(vertex[2] - vertex[0]).normalized()); + // Skip triangle if over a thickest layer it would not fit nDia * nozzle_diameter + if (nozzle_diameter * sqrt(1 - norm[2] * norm[2]) * nDia > fabs(norm[2]) * layerHeightMax ) continue; + + float z_min = std::min(std::min(vertex[0][2], vertex[1][2]), vertex[2][2]); + float z_max = std::max(std::max(vertex[0][2], vertex[1][2]), vertex[2][2]); + + int start = std::lower_bound(candidate_zs.begin(), candidate_zs.end(), z_min - eps) - candidate_zs.begin(); + + for (auto cc = start; cc < candidate_zs.size() && candidate_zs[cc] < z_max + eps; cc++) { + // Ignore facets that are too vertical to fit nDia nozzles at layer height + if (nozzle_diameter * sqrt(1 - norm[2] * norm[2]) * nDia < fabs(norm[2]) * layerHeight[cc]) { + if (norm[2] > 0) + upwrd_mididx->at(cc) = cc; + else if (dither_both_up_n_down) + dnwrd_mididx->at(cc) = cc; + } + } + } + + if (std::all_of(upwrd_mididx->begin(), upwrd_mididx->end(), [](int idx) { return idx == -1; }) && + std::all_of(dnwrd_mididx->begin(), dnwrd_mididx->end(), [](int idx) { return idx == -1; })) + return; + + // Retrn mid_zs which will contribute to z-dithering + for (int i = 0; i < n_zs; i++) { + if (upwrd_mididx->at(i) + dnwrd_mididx->at(i) != -2) { + int count = mid_zs->size(); + mid_zs->push_back(candidate_zs[i]); + if (upwrd_mididx->at(i) != -1) upwrd_mididx->at(i) = count; + if (dnwrd_mididx->at(i) != -1) dnwrd_mididx->at(i) = count; + } + } + return; +} + +namespace { +void export_sublayers_to_svg(size_t layer_id, + const ExPolygons &whole, + const ExPolygons &bottom, + const ExPolygons &middleUp, + const ExPolygons &middleDn, + const ExPolygons &top) +{ + BoundingBox bbox = get_extents(whole); + bbox.merge(get_extents(bottom)); + bbox.merge(get_extents(middleUp)); + bbox.merge(get_extents(middleDn)); + bbox.merge(get_extents(top)); + SVG svg(debug_out_path("z-dither_%d_sublayers.svg", layer_id).c_str(), bbox); + svg.draw(whole, "green"); + svg.draw(bottom, "lightcoral"); + svg.draw_outline(middleUp, "red", "red", scale_(0.05)); + svg.draw(top, "cyan"); + svg.draw_outline(middleDn, "blue", "blue", scale_(0.05)); + svg.Close(); +} + +void export_cuts_to_svg(size_t layer_id, const ExPolygons &expoly, const ExPolygons &below, const ExPolygons &above) +{ + BoundingBox bbox = get_extents(expoly); + bbox.merge(get_extents(below)); + bbox.merge(get_extents(above)); + SVG svg(debug_out_path("z-dither_%d_cuts.svg", layer_id).c_str(), bbox); + svg.draw_outline(below, "lightcoral", "lightcoral", scale_(0.05)); + svg.draw_outline(expoly, "green", "green", scale_(0.05)); + svg.draw_outline(above, "cyan", "cyan", scale_(0.05)); + svg.Close(); +} + +// Subtraction of ExPolygons that are very close to each other along some portion of their boundary +// may result in ExPolygons covering tiny, very narrow areas. We need to filter them out. +ExPolygons &filter_tiny_areas(ExPolygons &expolys, double min) +{ + expolys.erase(std::remove_if(expolys.begin(), expolys.end(), [&min](ExPolygon &poly) { + // Use area and perimeter to estimate width of a closed polygon + double area = poly.contour.area(); + double perimeter = poly.contour.length(); + // For cirle area/perimeter = width / 4; For thin rectangle area/perimeter = width / 2. + // Arbitrary shape will have average width less than width of a circle + double width = area / perimeter * 4; + return width < scale_(min); + }), expolys.end()); + return expolys; +} + +int total_num_contours(const ExPolygons &expolys) +{ + int total = 0; + for (const ExPolygon &poly : expolys) + total += poly.num_contours(); + return total; +} + +} // namespace + +std::vector apply_z_dither(std::vector &expolys, + double min_contour_width, + std::vector &expolys_mid, + const std::vector & upwrd_mididx, + const std::vector & dnwrd_mididx, + std::vector * sublayers) +{ + sublayers->clear(); + sublayers->resize(expolys.size()); + std::vector out(expolys.size()); + + out[0] = std::move(expolys[0]); // Do not make sublayers of first layer + for (auto ll = 1; ll < expolys.size(); ll++) { + // idx0 - bottom of layer, idx1 - top of layer + int upwrd_idx0 = upwrd_mididx[ll - 1]; + int dnwrd_idx0 = dnwrd_mididx[ll - 1]; + int upwrd_idx1 = upwrd_mididx[ll]; + int dnwrd_idx1 = dnwrd_mididx[ll]; + + auto useMidCut = [](int idx) { return idx != -1; }; + + if (useMidCut(dnwrd_idx0) && useMidCut(dnwrd_idx1) && + total_num_contours(expolys_mid[dnwrd_idx0]) != total_num_contours(expolys_mid[dnwrd_idx1])) { + dnwrd_idx0 = dnwrd_idx1 = -1; // Don't mess up with bridging even in the presence of support + } + if (!useMidCut(upwrd_idx0) && !useMidCut(dnwrd_idx0) && !useMidCut(upwrd_idx1) && !useMidCut(dnwrd_idx1)) { + out[ll] = std::move(expolys[ll]); + continue; + } else { + ExPolygons bottom, middleUp, middleDn, top, whole; + + if (useMidCut(upwrd_idx0) || useMidCut(upwrd_idx1)) { + bottom = std::move( + diff_ex(useMidCut(upwrd_idx0) ? expolys_mid[upwrd_idx0] : expolys[ll], + useMidCut(upwrd_idx1) ? expolys_mid[upwrd_idx1] : expolys[ll])); + } + if (useMidCut(upwrd_idx1)) { + middleUp = std::move(diff_ex(expolys[ll], expolys_mid[upwrd_idx1])); + } + + if (useMidCut(dnwrd_idx0) || useMidCut(dnwrd_idx1)) { + top = std::move( + diff_ex(useMidCut(dnwrd_idx1) ? expolys_mid[dnwrd_idx1] : expolys[ll], + useMidCut(dnwrd_idx0) ? expolys_mid[dnwrd_idx0] : expolys[ll])); + } + if (useMidCut(dnwrd_idx0)) { + middleDn = std::move(diff_ex(expolys[ll], expolys_mid[dnwrd_idx0])); + } + + filter_tiny_areas(bottom, min_contour_width); + filter_tiny_areas(middleUp, min_contour_width); + filter_tiny_areas(middleDn, min_contour_width); + filter_tiny_areas(top, min_contour_width); + + if (!bottom.empty() || !top.empty()) { + whole = std::move(diff_ex(expolys[ll], top)); + whole = std::move(diff_ex(filter_tiny_areas(whole, min_contour_width), bottom)); + filter_tiny_areas(whole, min_contour_width); + } + else { + out[ll] = std::move(expolys[ll]); + continue; + } + + #if 0 + export_sublayers_to_svg(ll, whole, bottom, middleUp, middleDn, top); + export_cuts_to_svg(ll, expolys[ll], + useMidCut(dnwrd_idx0) ? expolys_mid[dnwrd_idx0] : (useMidCut(upwrd_idx0) ? expolys_mid[upwrd_idx0] : ExPolygons()), + useMidCut(dnwrd_idx1) ? expolys_mid[dnwrd_idx1] : (useMidCut(upwrd_idx1) ? expolys_mid[upwrd_idx1] : ExPolygons())); + #endif + + out[ll] = std::move(whole); + sublayers->at(ll).bottom_ = std::move(bottom); + sublayers->at(ll).halfUp_ = std::move(middleUp); + sublayers->at(ll).halfDn_ = std::move(middleDn); + sublayers->at(ll).top_ = std::move(top); + } + } + return out; +} + + +std::vector z_dither(const indexed_triangle_set &mesh, + const std::vector &zs, + const MeshSlicingParamsEx ¶ms, + std::vector & expolys, + std::vector * sublayers, + const std::function &throw_on_cancel_callback) +{ + std::vector mid_zs; + std::vector upwrd_mididx; + std::vector dnwrd_mididx; + midslice_zs(mesh, zs, params.trafo, params.nozzle_diameter, params.z_dither_mode == Z_dither_mode::Both, + &mid_zs, &upwrd_mididx, &dnwrd_mididx); + if (!mid_zs.empty()) { + std::vector expolys_mid = slice_mesh_ex(mesh, mid_zs, params, throw_on_cancel_callback); + return apply_z_dither(expolys, params.nozzle_diameter / 10, expolys_mid, upwrd_mididx, dnwrd_mididx, sublayers); + } else { + *sublayers = std::vector(expolys.size()); + return expolys; + } +} + +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/ZDither.hpp b/src/libslic3r/ZDither.hpp new file mode 100644 index 00000000000..6d4e19c80c7 --- /dev/null +++ b/src/libslic3r/ZDither.hpp @@ -0,0 +1,44 @@ +#ifndef slic3r_ZDither_hpp +#define slic3r_Zdither_hpp + +#include "Point.hpp" +#include "TriangleMeshSlicer.hpp" + +namespace Slic3r { + + struct SubLayers +{ + ExPolygons bottom_; // polygons filling the botthom 0.25% of layer thickness + ExPolygons halfUp_; // polygons filling only half a layer leaving 0.25% at top and botton not filled, located above bottom_ + ExPolygons halfDn_; // similar to halfUp_ but located under top_ + ExPolygons top_; // polygons filling the top 0.25% of layer thickness + SubLayers() = default; + SubLayers(const SubLayers &other) = default; + SubLayers(SubLayers &&other) = default; + bool empty() const { return bottom_.empty() && halfUp_.empty() && halfDn_.empty() && top_.empty(); }; +}; + +void midslice_zs(const indexed_triangle_set &mesh, + const std::vector & zs, + const Transform3d & trafo, + float nozzle_diameter, + std::vector * mid_zs, + std::vector * upwrd_mididx, + std::vector * dnwrd_mididx); + +std::vector apply_z_dither(std::vector &layers, + double min_contour_width, + std::vector &mid_layers, + const std::vector &do_low, + const std::vector &do_high, + std::vector * sublayers); + +std::vector z_dither(const indexed_triangle_set &mesh, + const std::vector & zs, + const MeshSlicingParamsEx ¶ms, + std::vector & layers, + std::vector * sublayers, + const std::function &throw_on_cancel_callback); + +} // namespace Slic3r +#endif diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6f6fb18a696..e39921c761c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1487,6 +1487,7 @@ void TabPrint::build() optgroup->append_single_option_line("external_perimeters_first", category_path + "external-perimeters-first"); optgroup->append_single_option_line("gap_fill_enabled", category_path + "fill-gaps"); optgroup->append_single_option_line("perimeter_generator"); + optgroup->append_single_option_line("z_dither", category_path + "z-dither"); optgroup = page->new_optgroup(L("Fuzzy skin (experimental)")); category_path = "fuzzy-skin_246186/#";