diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index ecd0ab4f5c..c39dcb3f35 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -787,7 +787,11 @@ Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPol for (const ExPolygon& expoly : poly2) expolys.push_back(expoly); return union_ex(expolys); - } +} + +Slic3r::ExPolygons xor_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset) { + return _clipper_ex(ClipperLib::ctXor, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); +} template Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index c36778e827..585a519107 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -529,6 +529,8 @@ Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPol ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject); ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject); +Slic3r::ExPolygons xor_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); + Slic3r::Polygons union_pt_chained_outside_in(const Slic3r::Polygons &subject); ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 47703f9298..97a830ab69 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -756,7 +756,8 @@ static std::vector s_Preset_print_options { "bridge_density", "precise_outer_wall", "overhang_speed_classic", "bridge_acceleration", "sparse_infill_acceleration", "internal_solid_infill_acceleration", "tree_support_adaptive_layer_height", "tree_support_auto_brim", "tree_support_brim_width", "gcode_comments", "gcode_label_objects", - "initial_layer_travel_speed", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max" + "initial_layer_travel_speed", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max", + "make_overhang_printable", "make_overhang_printable_angle", "make_overhang_printable_hole_size" }; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 96c361d7eb..2b3775f3a0 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -521,6 +521,7 @@ private: // // object id size_t m_id; + void apply_conical_overhang(); public: //BBS: When printing multi-material objects, this settings will make slicer to clip the overlapping object parts one by the other. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 13f55b83b0..3408884ed9 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2371,6 +2371,35 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("{input_filename_base}_{filament_type[0]}_{print_time}.gcode")); + def = this->add("make_overhang_printable", coBool); + def->label = L("Make overhang printable"); + def->category = L("Quality"); + def->tooltip = L("Modify the geometry to print overhangs without support material."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("make_overhang_printable_angle", coFloat); + def->label = L("Make overhang printable maximum angle"); + def->category = L("Quality"); + def->tooltip = L("Maximum angle of overhangs to allow after making more steep overhangs printable." + "90° will not change the model at all and allow any overhang, while 0 will " + "replace all overhangs with conical material."); + def->sidetext = L("°"); + def->mode = comAdvanced; + def->min = 0.; + def->max = 90.; + def->set_default_value(new ConfigOptionFloat(55.)); + + def = this->add("make_overhang_printable_hole_size", coFloat); + def->label = L("Make overhang printable hole area"); + def->category = L("Quality"); + def->tooltip = L("Maximum area of a hole in the base of the model before it's filled by conical material." + "A value of 0 will fill all the holes in the model base."); + def->sidetext = L("mm²"); + def->mode = comAdvanced; + def->min = 0.; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("detect_overhang_wall", coBool); def->label = L("Detect overhang wall"); def->category = L("Quality"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 0f90ddac51..d494411b40 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -713,6 +713,10 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInt, wall_distribution_count)) ((ConfigOptionPercent, min_feature_size)) ((ConfigOptionPercent, min_bead_width)) + + // Orca + ((ConfigOptionFloat, make_overhang_printable_angle)) + ((ConfigOptionFloat, make_overhang_printable_hole_size)) ) // This object is mapped to Perl as Slic3r::Config::PrintRegion. @@ -791,6 +795,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, infill_anchor)) ((ConfigOptionFloatOrPercent, infill_anchor_max)) + // Orca + ((ConfigOptionBool, make_overhang_printable)) ) PRINT_CONFIG_CLASS_DEFINE( diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f4e89957f7..03d2935617 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -731,7 +731,10 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "raft_layers" || opt_key == "raft_contact_distance" || opt_key == "slice_closing_radius" - || opt_key == "slicing_mode") { + || opt_key == "slicing_mode" + || opt_key == "make_overhang_printable" + || opt_key == "make_overhang_printable_angle" + || opt_key == "make_overhang_printable_hole_size") { steps.emplace_back(posSlice); } else if ( opt_key == "elefant_foot_compensation" diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 8be854289c..07da728213 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -777,6 +777,7 @@ void PrintObject::slice() m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile)); this->slice_volumes(); m_print->throw_if_canceled(); + int firstLayerReplacedBy = 0; #if 1 @@ -1028,6 +1029,8 @@ void PrintObject::slice_volumes() apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); }); } + this->apply_conical_overhang(); + m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin"; { @@ -1166,6 +1169,107 @@ void PrintObject::slice_volumes() BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - end"; } +void PrintObject::apply_conical_overhang() { + BOOST_LOG_TRIVIAL(info) << "Make overhang printable..."; + + if (m_layers.empty()) { + return; + } + + const double conical_overhang_angle = this->config().make_overhang_printable_angle; + if (conical_overhang_angle == 90.0) { + return; + } + const double angle_radians = conical_overhang_angle * M_PI / 180.; + const double max_hole_area = this->config().make_overhang_printable_hole_size; // in MM^2 + const double tan_angle = tan(angle_radians); // the XY-component of the angle + BOOST_LOG_TRIVIAL(info) << "angle " << angle_radians << " maxHoleArea " << max_hole_area << " tan_angle " + << tan_angle; + const coordf_t layer_thickness = m_config.layer_height.value; + const coordf_t max_dist_from_lower_layer = tan_angle * layer_thickness; // max dist which can be bridged, in MM + BOOST_LOG_TRIVIAL(info) << "layer_thickness " << layer_thickness << " max_dist_from_lower_layer " + << max_dist_from_lower_layer; + + // Pre-scale config + const coordf_t scaled_max_dist_from_lower_layer = -float(scale_(max_dist_from_lower_layer)); + const coordf_t scaled_max_hole_area = float(scale_(scale_(max_hole_area))); + + + for (auto i = m_layers.rbegin() + 1; i != m_layers.rend(); ++i) { + m_print->throw_if_canceled(); + Layer *layer = *i; + Layer *upper_layer = layer->upper_layer; + + if (upper_layer->empty()) { + continue; + } + + // Skip if entire layer has this disabled + if (std::all_of(layer->m_regions.begin(), layer->m_regions.end(), + [](const LayerRegion *r) { return r->slices.empty() || !r->region().config().make_overhang_printable; })) { + continue; + } + + //layer->export_region_slices_to_svg_debug("layer_before_conical_overhang"); + //upper_layer->export_region_slices_to_svg_debug("upper_layer_before_conical_overhang"); + + + // Merge the upper layer because we want to offset the entire layer uniformly, otherwise + // the model could break at the region boundary. + auto upper_poly = upper_layer->merged(float(SCALED_EPSILON)); + upper_poly = union_ex(upper_poly); + + // Avoid closing up of recessed holes in the base of a model. + // Detects when a hole is completely covered by the layer above and removes the hole from the layer above before + // adding it in. + // This should have no effect any time a hole in a layer interacts with any polygon in the layer above + if (scaled_max_hole_area > 0.0) { + // Merge layer for the same reason + auto current_poly = layer->merged(float(SCALED_EPSILON)); + current_poly = union_ex(current_poly); + + // Now go through all the holes in the current layer and check if they intersect anything in the layer above + // If not, then they're the top of a hole and should be cut from the layer above before the union + for (auto layer_polygon : current_poly) { + for (auto hole : layer_polygon.holes) { + if (std::abs(hole.area()) < scaled_max_hole_area) { + ExPolygon hole_poly(hole); + auto hole_with_above = intersection_ex(upper_poly, hole_poly); + if (!hole_with_above.empty()) { + // The hole had some intersection with the above layer, check if it's a complete overlap + auto hole_difference = xor_ex(hole_with_above, hole_poly); + if (hole_difference.empty()) { + // The layer above completely cover it, remove it from the layer above + upper_poly = diff_ex(upper_poly, hole_poly); + } + } + } + } + } + } + + // Now offset the upper layer to be added into current layer + upper_poly = offset_ex(upper_poly, scaled_max_dist_from_lower_layer); + + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + // export_to_svg(debug_out_path("Surface-obj-%d-layer-%d-region-%d.svg", id().id, layer->id(), region_id).c_str(), + // layer->m_regions[region_id]->slices.surfaces); + + // Disable on given region + if (!upper_layer->m_regions[region_id]->region().config().make_overhang_printable) { + continue; + } + + // Calculate the scaled upper poly that belongs to current region + auto p = intersection_ex(upper_layer->m_regions[region_id]->slices.surfaces, upper_poly); + // And now union it + ExPolygons layer_polygons = to_expolygons(layer->m_regions[region_id]->slices.surfaces); + layer->m_regions[region_id]->slices.set(union_ex(layer_polygons, p), stInternal); + } + //layer->export_region_slices_to_svg_debug("layer_after_conical_overhang"); + } +} + //BBS: this function is used to offset contour and holes of expolygons seperately by different value ExPolygons PrintObject::_shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const { diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index fe956d04b3..88c0c3113a 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -702,6 +702,11 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co if(gcflavor == gcfKlipper) toggle_field("accel_to_decel_factor", config->opt_bool("accel_to_decel_enable")); + // SoftFever + bool have_make_overhang_printable = config->opt_bool("make_overhang_printable"); + toggle_line("make_overhang_printable_angle", have_make_overhang_printable); + toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable); + toggle_line("exclude_object", gcflavor == gcfKlipper); } diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 624a89d694..4c3d21cac7 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -75,7 +75,8 @@ std::map> SettingsFactory::OBJECT_C //{"initial_layer_print_height", "",2}, {"seam_position", "",2}, {"slice_closing_radius", "",3}, {"resolution", "",4}, - {"xy_hole_compensation", "",5}, {"xy_contour_compensation", "",6}, {"elefant_foot_compensation", "",7} + {"xy_hole_compensation", "",5}, {"xy_contour_compensation", "",6}, {"elefant_foot_compensation", "",7}, + {"make_overhang_printable_angle","", 8},{"make_overhang_printable_hole_size","",9} }}, { L("Support"), {{"brim_type", "",1},{"brim_width", "",2},{"brim_object_gap", "",3}, {"enable_support", "",4},{"support_type", "",5},{"support_threshold_angle", "",6},{"support_on_build_plate_only", "",7}, @@ -91,7 +92,7 @@ std::map> SettingsFactory::OBJECT_C std::map> SettingsFactory::PART_CATEGORY_SETTINGS= { - { L("Quality"), {{"ironing_type", "",8},{"ironing_flow", "",9},{"ironing_spacing", "",10},{"bridge_flow", "",11},{"bridge_density", "", 1} + { L("Quality"), {{"ironing_type", "",8},{"ironing_flow", "",9},{"ironing_spacing", "",10},{"bridge_flow", "",11},{"make_overhang_printable", "",11},{"bridge_density", "", 1} }}, { L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", L("Top Solid Layers"),1},{"top_shell_thickness", L("Top Minimum Shell Thickness"),1}, {"bottom_shell_layers", L("Bottom Solid Layers"),1}, {"bottom_shell_thickness", L("Bottom Minimum Shell Thickness"),1}, diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d9d0aef6c1..76afc9f927 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1888,6 +1888,9 @@ void TabPrint::build() optgroup->append_single_option_line("only_one_wall_top"); optgroup->append_single_option_line("only_one_wall_first_layer"); optgroup->append_single_option_line("detect_overhang_wall"); + optgroup->append_single_option_line("make_overhang_printable"); + optgroup->append_single_option_line("make_overhang_printable_angle"); + optgroup->append_single_option_line("make_overhang_printable_hole_size"); optgroup->append_single_option_line("reduce_crossing_wall"); optgroup->append_single_option_line("max_travel_detour_distance");