diff --git a/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po b/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po index 2e2d49487e4..effc208b238 100644 --- a/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po +++ b/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po @@ -7315,11 +7315,11 @@ msgstr "" msgid "" "When using support material for the support interface, We recommend the " "following settings:\n" -"0 top z distance, 0 interface spacing, concentric pattern and disable " +"0 top z distance, 0 interface spacing, interlaced rectilinear pattern and disable " "independent support layer height" msgstr "" "当使用支持界面的支持材料时,我们推荐以下设置:\n" -"0顶层z距离,0接触层间距,同心图案,并且禁用独立支撑层高" +"0顶层z距离,0接触层间距,交叠直线图案,并且禁用独立支撑层高" msgid "" "Enabling this option will modify the model's shape. If your print requires " diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 7c673231e03..6db340051a8 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -309,16 +309,16 @@ set(lisbslic3r_sources Support/SupportLayer.hpp Support/SupportMaterial.cpp Support/SupportMaterial.hpp - Support/SupportParameters.hpp Support/SupportSpotsGenerator.cpp Support/SupportSpotsGenerator.hpp Support/TreeSupport.hpp Support/TreeSupport.cpp - Support/TreeSupport3D.cpp Support/TreeSupport3D.hpp - Support/TreeSupportCommon.hpp - Support/TreeModelVolumes.cpp + Support/TreeSupport3D.cpp Support/TreeModelVolumes.hpp + Support/TreeModelVolumes.cpp + Support/TreeSupportCommon.hpp + Support/SupportParameters.hpp PrincipalComponents2D.cpp PrincipalComponents2D.hpp MinimumSpanningTree.hpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index b9a63e37e7f..7d893a3bf22 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -664,6 +664,12 @@ Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &c { return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons diff_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return diff(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); } +Slic3r::ExPolygons diff_clipped(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) + { return diff_ex(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); } +Slic3r::ExPolygons diff_clipped(const Slic3r::ExPolygons & subject, const Slic3r::ExPolygons & clip, ApplySafetyOffset do_safety_offset) +{ + return diff_ex(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); +} Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 167449dc21e..c6aebb5e4a3 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -433,6 +433,8 @@ Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygon // Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox(). // To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon. Slic3r::Polygons diff_clipped(const Slic3r::Polygons &src, const Slic3r::Polygons &clipping, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_clipped(const Slic3r::ExPolygons &src, const Slic3r::Polygons &clipping, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_clipped(const Slic3r::ExPolygons &src, const Slic3r::ExPolygons &clipping, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index d5ce03d3b4e..d64ab81689a 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -56,7 +56,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipOctagramSpiral: return new FillOctagramSpiral(); case ipAdaptiveCubic: return new FillAdaptive::Filler(); case ipSupportCubic: return new FillAdaptive::Filler(); - case ipSupportBase: return new FillSupportBase(); + case ipSupportBase: return new FillSupportBase(); // simply line fill case ipLightning: return new FillLightning::Filler(); // BBS: for internal solid infill only case ipConcentricInternal: return new FillConcentricInternal(); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index a1351cb6f57..c391d2be3a1 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1202,14 +1202,23 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) { double top_cd = object.config().support_top_z_distance; - double bottom_cd = object.config().support_bottom_z_distance; - + double bottom_cd = object.config().support_bottom_z_distance == 0. ? top_cd : object.config().support_bottom_z_distance; + //if (!object.print()->config().independent_support_layer_height) + { // the actual support gap may be larger than the configured one due to rounding to layer height for organic support, regardless of independent support layer height + top_cd = std::ceil(top_cd / object.config().layer_height) * object.config().layer_height; + bottom_cd = std::ceil(bottom_cd / object.config().layer_height) * object.config().layer_height; + } double extra_gap = (layer_to_print.support_layer ? bottom_cd : top_cd); // raft contact distance should not trigger any warning - if(last_extrusion_layer && last_extrusion_layer->support_layer) + if (last_extrusion_layer && last_extrusion_layer->support_layer) { + double raft_gap = object.config().raft_contact_distance.value; + //if (!object.print()->config().independent_support_layer_height) + { + raft_gap = std::ceil(raft_gap / object.config().layer_height) * object.config().layer_height; + } extra_gap = std::max(extra_gap, object.config().raft_contact_distance.value); - + } double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + layer_to_print.layer()->height + std::max(0., extra_gap); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index a277aca29e3..48f1ca95e29 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -139,7 +139,7 @@ class Layer // BBS mutable ExPolygons sharp_tails; mutable ExPolygons cantilevers; - mutable std::map sharp_tails_height; + mutable std::vector sharp_tails_height; // Collection of expolygons generated by slicing the possibly multiple meshes of the source geometry // (with possibly differing extruder ID and slicing parameters) and merged. @@ -150,6 +150,7 @@ class Layer // These lslices are also used to detect overhangs and overlaps between successive layers, therefore it is important // that the 1st lslice is not compensated by the Elephant foot compensation algorithm. ExPolygons lslices; + ExPolygons lslices_extrudable; // BBS: the extrudable part of lslices used for tree support std::vector lslices_bboxes; // BBS @@ -274,11 +275,10 @@ class SupportLayer : public Layer ExPolygons support_islands; // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; - SupportInnerType support_type = stInnerNormal; + SupportInnerType support_type = stInnerNormal; // for tree supports ExPolygons base_areas; - ExPolygons overhang_areas; // Is there any valid extrusion assigned to this LayerRegion? @@ -311,14 +311,13 @@ class SupportLayer : public Layer { ExPolygon *area; int type; + int interface_id = 0; coordf_t dist_to_top; // mm dist to top bool need_infill = false; bool need_extra_wall = false; AreaGroup(ExPolygon *a, int t, coordf_t d) : area(a), type(t), dist_to_top(d) {} }; - enum OverhangType { Detected = 0, Enforced }; std::vector area_groups; - std::map overhang_types; }; template diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 4fe0d6b4b16..492e70b730c 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1363,7 +1363,7 @@ static inline std::vector> mmu_segmentation_top_and_bott if (!zs.empty() && is_volume_sinking(painted, volume_trafo)) { std::vector zs_sinking = {0.f}; Slic3r::append(zs_sinking, zs); - slice_mesh_slabs(painted, zs_sinking, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, throw_on_cancel_callback); + slice_mesh_slabs(painted, zs_sinking, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, nullptr, throw_on_cancel_callback); MeshSlicingParams slicing_params; slicing_params.trafo = volume_trafo; @@ -1374,7 +1374,7 @@ static inline std::vector> mmu_segmentation_top_and_bott bottom[0] = union_(bottom[0], bottom_slice); } else - slice_mesh_slabs(painted, zs, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, throw_on_cancel_callback); + slice_mesh_slabs(painted, zs, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, nullptr, throw_on_cancel_callback); auto merge = [](std::vector &&src, std::vector &dst) { auto it_src = find_if(src.begin(), src.end(), [](const Polygons &p){ return ! p.empty(); }); if (it_src != src.end()) { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 134538dde51..e633d094f87 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -792,7 +792,7 @@ static std::vector s_Preset_print_options { "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance", "fuzzy_skin_first_layer", "fuzzy_skin_noise_type", "fuzzy_skin_scale", "fuzzy_skin_octaves", "fuzzy_skin_persistence", "max_volumetric_extrusion_rate_slope", "max_volumetric_extrusion_rate_slope_segment_length","extrusion_rate_smoothing_external_perimeter_only", "inner_wall_speed", "outer_wall_speed", "sparse_infill_speed", "internal_solid_infill_speed", - "top_surface_speed", "support_speed", "support_object_xy_distance", "support_interface_speed", + "top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed", "bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed", "outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height", "draft_shield", "brim_width", "brim_object_gap", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers", @@ -812,7 +812,7 @@ static std::vector s_Preset_print_options { "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", "flush_into_infill", "flush_into_objects", "flush_into_support", "tree_support_branch_angle", "tree_support_angle_slow", "tree_support_wall_count", "tree_support_top_rate", "tree_support_branch_distance", "tree_support_tip_diameter", - "tree_support_branch_diameter", "tree_support_branch_diameter_angle", "tree_support_branch_diameter_double_wall", + "tree_support_branch_diameter", "tree_support_branch_diameter_angle", "detect_narrow_internal_solid_infill", "gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination","infill_combination_max_layer_height", /*"adaptive_layer_height",*/ "support_bottom_interface_spacing", "enable_overhang_speed", "slowdown_for_curled_perimeters", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 31f225cfa29..642cf79b5c5 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1166,7 +1166,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* // Custom layering is not allowed for tree supports as of now. for (size_t print_object_idx = 0; print_object_idx < m_objects.size(); ++ print_object_idx) if (const PrintObject &print_object = *m_objects[print_object_idx]; - print_object.has_support_material() && is_tree(print_object.config().support_type.value) && (print_object.config().support_style.value == smsOrganic || + print_object.has_support_material() && is_tree(print_object.config().support_type.value) && (print_object.config().support_style.value == smsTreeOrganic || // Orca: use organic as default print_object.config().support_style.value == smsDefault) && print_object.model_object()->has_custom_layering()) { @@ -1339,7 +1339,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* // Prusa: Fixing crashes with invalid tip diameter or branch diameter // https://github.com/prusa3d/PrusaSlicer/commit/96b3ae85013ac363cd1c3e98ec6b7938aeacf46d - if (is_tree(object->config().support_type.value) && (object->config().support_style == smsOrganic || + if (is_tree(object->config().support_type.value) && (object->config().support_style == smsTreeOrganic || // Orca: use organic as default object->config().support_style == smsDefault)) { float extrusion_width = std::min( diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 7b7d13e709e..783987a021c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -385,7 +385,7 @@ class PrintObject : public PrintObjectBaseWithState slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } // Helpers to project custom facets on slices - void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const; + void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys, std::vector>* vertical_points=nullptr) const; //BBS BoundingBox get_first_layer_bbox(float& area, float& layer_height, std::string& name); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8fb93e3024e..bbaa7e6c97e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -230,7 +230,7 @@ static t_config_enum_values s_keys_map_SupportMaterialStyle { { "tree_slim", smsTreeSlim }, { "tree_strong", smsTreeStrong }, { "tree_hybrid", smsTreeHybrid }, - { "organic", smsOrganic } + { "organic", smsTreeOrganic } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle) @@ -4493,6 +4493,17 @@ void PrintConfigDef::init_fff_params() //Support with too small spacing may touch the object and difficult to remove. def->set_default_value(new ConfigOptionFloat(0.35)); + def = this->add("support_object_first_layer_gap", coFloat); + def->label = L("Support/object first layer gap"); + def->category = L("Support"); + def->tooltip = L("XY separation between an object and its support at the first layer."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comAdvanced; + //Support with too small spacing may touch the object and difficult to remove. + def->set_default_value(new ConfigOptionFloat(0.2)); + def = this->add("support_angle", coFloat); def->label = L("Pattern angle"); def->category = L("Support"); @@ -4798,7 +4809,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloatOrPercent(50., true)); def = this->add("tree_support_branch_angle", coFloat); - def->label = L("Tree support branch angle"); + def->label = L("Branch angle"); def->category = L("Support"); def->tooltip = L("This setting determines the maximum overhang angle that t he branches of tree support allowed to make." "If the angle is increased, the branches can be printed more horizontally, allowing them to reach farther."); @@ -4832,7 +4843,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(25)); def = this->add("tree_support_branch_distance", coFloat); - def->label = L("Tree support branch distance"); + def->label = L("Branch distance"); def->category = L("Support"); def->tooltip = L("This setting determines the distance between neighboring tree support nodes."); def->sidetext = L("mm"); @@ -4896,7 +4907,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(0.8)); def = this->add("tree_support_branch_diameter", coFloat); - def->label = L("Tree support branch diameter"); + def->label = L("Branch diameter"); def->category = L("Support"); def->tooltip = L("This setting determines the initial diameter of support nodes."); def->sidetext = L("mm"); @@ -4905,16 +4916,6 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(5.)); - def = this->add("tree_support_branch_diameter_organic", coFloat); - def->label = L("Tree support branch diameter"); - def->category = L("Support"); - def->tooltip = L("This setting determines the initial diameter of support nodes."); - def->sidetext = L("mm"); - def->min = 1.0; - def->max = 10; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(2.)); - def = this->add("tree_support_branch_diameter_angle", coFloat); // TRN PrintSettings: #lmFIXME def->label = L("Branch Diameter Angle"); @@ -4929,23 +4930,22 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(5)); - def = this->add("tree_support_branch_diameter_double_wall", coFloat); - def->label = L("Branch Diameter with double walls"); + def = this->add("tree_support_branch_diameter_organic", coFloat); + def->label = L("Tree support branch diameter"); def->category = L("Support"); - // TRN PrintSettings: "Organic supports" > "Branch Diameter" - def->tooltip = L("Branches with area larger than the area of a circle of this diameter will be printed with double walls for stability. " - "Set this value to zero for no double walls."); + def->tooltip = L("This setting determines the initial diameter of support nodes."); def->sidetext = L("mm"); - def->min = 0; - def->max = 100.f; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(3.)); + def->min = 1.0; + def->max = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(2.)); def = this->add("tree_support_wall_count", coInt); def->label = L("Support wall loops"); def->category = L("Support"); - def->tooltip = L("This setting specify the count of walls around support"); + def->tooltip = L("This setting specifies the count of support walls in the range of [0,2]. 0 means auto."); def->min = 0; + def->max = 2; def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(0)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 33a0e7eabf4..06778e1ad17 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -132,7 +132,7 @@ enum SupportMaterialPattern { }; enum SupportMaterialStyle { - smsDefault, smsGrid, smsSnug, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsOrganic, + smsDefault, smsGrid, smsSnug, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsTreeOrganic, }; enum LongRectrationLevel @@ -831,6 +831,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInt, support_threshold_angle)) ((ConfigOptionFloatOrPercent, support_threshold_overlap)) ((ConfigOptionFloat, support_object_xy_distance)) + ((ConfigOptionFloat, support_object_first_layer_gap)) ((ConfigOptionFloat, xy_hole_compensation)) ((ConfigOptionFloat, xy_contour_compensation)) ((ConfigOptionBool, flush_into_objects)) @@ -841,9 +842,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, tree_support_branch_distance)) ((ConfigOptionFloat, tree_support_tip_diameter)) ((ConfigOptionFloat, tree_support_branch_diameter)) - ((ConfigOptionFloat, tree_support_branch_diameter_angle)) - ((ConfigOptionFloat, tree_support_branch_diameter_double_wall)) ((ConfigOptionFloat, tree_support_branch_angle)) + ((ConfigOptionFloat, tree_support_branch_diameter_angle)) ((ConfigOptionFloat, tree_support_angle_slow)) ((ConfigOptionInt, tree_support_wall_count)) ((ConfigOptionBool, tree_support_adaptive_layer_height)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b76eaa4574d..9f1fbdc1e2f 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -633,13 +633,9 @@ void PrintObject::generate_support_material() if (this->set_started(posSupportMaterial)) { this->clear_support_layers(); - if ((this->has_support() && m_layers.size() > 1) || (this->has_raft() && ! m_layers.empty())) { - m_print->set_status(50, L("Generating support")); - - this->_generate_support_material(); - m_print->throw_if_canceled(); - } else if(!m_print->get_no_check_flag()) { + if(!has_support() && !m_print->get_no_check_flag()) { // BBS: pop a warning if objects have significant amount of overhangs but support material is not enabled + // Note: we also need to pop warning if support is disabled and only raft is enabled m_print->set_status(50, L("Checking support necessity")); typedef std::chrono::high_resolution_clock clock_; typedef std::chrono::duration > second_; @@ -669,6 +665,12 @@ void PrintObject::generate_support_material() #endif } + if ((this->has_support() && m_layers.size() > 1) || (this->has_raft() && !m_layers.empty())) { + m_print->set_status(50, L("Generating support")); + + this->_generate_support_material(); + m_print->throw_if_canceled(); + } this->set_done(posSupportMaterial); } } @@ -731,7 +733,8 @@ void PrintObject::simplify_extrusion_path() } if (this->set_started(posSimplifySupportPath)) { - //BBS: share same progress + //BBS: disable circle simplification for support as it causes separation of support walls + #if 0 m_print->set_status(75, L("Optimizing toolpath")); BOOST_LOG_TRIVIAL(debug) << "Simplify extrusion path of support in parallel - start"; tbb::parallel_for( @@ -745,6 +748,7 @@ void PrintObject::simplify_extrusion_path() ); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Simplify extrusion path of support in parallel - end"; + #endif this->set_done(posSimplifySupportPath); } } @@ -843,14 +847,8 @@ void PrintObject::clear_support_layers() std::shared_ptr PrintObject::alloc_tree_support_preview_cache() { if (!m_tree_support_preview_cache) { - const coordf_t layer_height = m_config.layer_height.value; const coordf_t xy_distance = m_config.support_object_xy_distance.value; - const double angle = m_config.tree_support_branch_angle.value * M_PI / 180.; - const coordf_t max_move_distance - = (angle < M_PI / 2) ? (coordf_t)(tan(angle) * layer_height) : std::numeric_limits::max(); - const coordf_t radius_sample_resolution = g_config_tree_support_collision_resolution; - - m_tree_support_preview_cache = std::make_shared(*this, xy_distance, max_move_distance, radius_sample_resolution); + m_tree_support_preview_cache = std::make_shared(*this, xy_distance, g_config_tree_support_collision_resolution); } return m_tree_support_preview_cache; @@ -1015,6 +1013,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_base_pattern" || opt_key == "support_style" || opt_key == "support_object_xy_distance" + || opt_key == "support_object_first_layer_gap" || opt_key == "support_base_pattern_spacing" || opt_key == "support_expansion" //|| opt_key == "independent_support_layer_height" // BBS @@ -1036,7 +1035,6 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "tree_support_branch_diameter" || opt_key == "tree_support_branch_diameter_organic" || opt_key == "tree_support_branch_diameter_angle" - || opt_key == "tree_support_branch_diameter_double_wall" || opt_key == "tree_support_branch_angle" || opt_key == "tree_support_branch_angle_organic" || opt_key == "tree_support_angle_slow" @@ -3684,91 +3682,8 @@ template void PrintObject::remove_bridges_from_contacts( SupportNecessaryType PrintObject::is_support_necessary() { - static const double super_overhang_area_threshold = SQ(scale_(5.0)); const double cantilevel_dist_thresh = scale_(6); -#if 0 - double threshold_rad = (m_config.support_threshold_angle.value < EPSILON ? 30 : m_config.support_threshold_angle.value + 1) * M_PI / 180.; - int enforce_support_layers = m_config.enforce_support_layers; - // not fixing in extrusion width % PR b/c never called - const coordf_t extrusion_width = m_config.line_width.value; - const coordf_t extrusion_width_scaled = scale_(extrusion_width); - float max_bridge_length = scale_(m_config.max_bridge_length.value); - const bool bridge_no_support = max_bridge_length > 0;// config.bridge_no_support.value; - - for (size_t layer_nr = enforce_support_layers + 1; layer_nr < this->layer_count(); layer_nr++) { - Layer* layer = m_layers[layer_nr]; - Layer* lower_layer = layer->lower_layer; - - coordf_t support_offset_scaled = extrusion_width_scaled * 0.9; - ExPolygons lower_layer_offseted = offset_ex(lower_layer->lslices, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); - - // 1. check sharp tail - for (const LayerRegion* layerm : layer->regions()) { - for (const ExPolygon& expoly : layerm->raw_slices) { - // detect sharp tail - if (intersection_ex({ expoly }, lower_layer_offseted).empty()) - return SharpTail; - } - } - // 2. check overhang area - ExPolygons super_overhang_expolys = std::move(diff_ex(layer->lslices, lower_layer_offseted)); - super_overhang_expolys.erase(std::remove_if( - super_overhang_expolys.begin(), - super_overhang_expolys.end(), - [extrusion_width_scaled](ExPolygon& area) { - return offset_ex(area, -0.1 * extrusion_width_scaled).empty(); - }), - super_overhang_expolys.end()); - - // remove bridge - if (bridge_no_support) - remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &super_overhang_expolys, max_bridge_length); - - Polygons super_overhang_polys = to_polygons(super_overhang_expolys); - - - super_overhang_polys.erase(std::remove_if( - super_overhang_polys.begin(), - super_overhang_polys.end(), - [extrusion_width_scaled](Polygon& area) { - return offset_ex(area, -0.1 * extrusion_width_scaled).empty(); - }), - super_overhang_polys.end()); - - double super_overhang_area = 0.0; - for (Polygon& poly : super_overhang_polys) { - bool is_ccw = poly.is_counter_clockwise(); - double area_ = poly.area(); - if (is_ccw) { - if (area_ > super_overhang_area_threshold) - return LargeOverhang; - super_overhang_area += area_; - } - else { - super_overhang_area -= area_; - } - } - - //if (super_overhang_area > super_overhang_area_threshold) - // return LargeOverhang; - - // 3. check overhang distance - const double distance_threshold_scaled = extrusion_width_scaled * 2; - ExPolygons lower_layer_offseted_2 = offset_ex(lower_layer->lslices, distance_threshold_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); - ExPolygons exceed_overhang = std::move(diff_ex(super_overhang_polys, lower_layer_offseted_2)); - exceed_overhang.erase(std::remove_if( - exceed_overhang.begin(), - exceed_overhang.end(), - [extrusion_width_scaled](ExPolygon& area) { - // tolerance for 1 extrusion width offset - return offset_ex(area, -0.5 * extrusion_width_scaled).empty(); - }), - exceed_overhang.end()); - if (!exceed_overhang.empty()) - return LargeOverhang; - } -#else TreeSupport tree_support(*this, m_slicing_params); tree_support.support_type = SupportType::stTreeAuto; // need to set support type to fully utilize the power of feature detection tree_support.detect_overhangs(true); @@ -3777,7 +3692,7 @@ SupportNecessaryType PrintObject::is_support_necessary() return SharpTail; else if (tree_support.has_cantilever && tree_support.max_cantilever_dist > cantilevel_dist_thresh) return Cantilever; -#endif + return NoNeedSupp; } @@ -3968,7 +3883,7 @@ static void project_triangles_to_slabs(ConstLayerPtrsAdaptor layers, const index } void PrintObject::project_and_append_custom_facets( - bool seam, EnforcerBlockerType type, std::vector& out) const + bool seam, EnforcerBlockerType type, std::vector& out, std::vector>* vertical_points) const { for (const ModelVolume* mv : this->model_object()->volumes) if (mv->is_model_part()) { @@ -3983,7 +3898,7 @@ void PrintObject::project_and_append_custom_facets( else { std::vector projected; // 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(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &projected, vertical_points, [](){}); // Merge these projections with the output, layer by layer. assert(! projected.empty()); assert(out.empty() || out.size() == projected.size()); diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index d8fdbe43fcf..f511bde2852 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -808,10 +808,11 @@ void PrintObject::slice() std::string warning = fix_slicing_errors(this, m_layers, [this](){ m_print->throw_if_canceled(); }, firstLayerReplacedBy); m_print->throw_if_canceled(); //BBS: send warning message to slicing callback - if (!warning.empty()) { - BOOST_LOG_TRIVIAL(info) << warning; - this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingReplaceInitEmptyLayers); - } + // This warning is inaccurate, because the empty layers may have been replaced, or the model has supports. + //if (!warning.empty()) { + // BOOST_LOG_TRIVIAL(info) << warning; + // this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingReplaceInitEmptyLayers); + //} #endif // Detect and process holes that should be converted to polyholes diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index 3aa99e2b76b..2b017b709b6 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -1030,6 +1030,9 @@ void reorder_extrusion_entities(std::vector &entities, const s void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near) { + // this function crashes if there are empty elements in entities + entities.erase(std::remove_if(entities.begin(), entities.end(), [](ExtrusionEntity *entity) { return static_cast(entity)->empty(); }), + entities.end()); reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near)); } diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 6655e3911d2..f3499b7d361 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -114,7 +114,7 @@ SlicingParameters SlicingParameters::create_from_config( params.min_layer_height = std::min(params.min_layer_height, params.layer_height); params.max_layer_height = std::max(params.max_layer_height, params.layer_height); - if (! soluble_interface || is_tree_slim(object_config.support_type.value, object_config.support_style.value)) { + if (! soluble_interface) { params.gap_raft_object = object_config.raft_contact_distance.value; //BBS params.gap_object_support = object_config.support_bottom_z_distance.value; diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index d37324084cb..f4611a175f9 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -36,7 +36,7 @@ namespace Slic3r { // how much we extend support around the actual contact area //FIXME this should be dependent on the nozzle diameter! -#define SUPPORT_MATERIAL_MARGIN 1.5 +#define SUPPORT_MATERIAL_MARGIN 1.5 //#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. //#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 @@ -140,10 +140,11 @@ std::pair generate_interfa if (! intermediate_layers.empty() && support_params.has_interfaces()) { // For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers. BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; - const bool snug_supports = config.support_style.value == smsSnug; - const bool smooth_supports = config.support_style.value != smsGrid; + const bool snug_supports = support_params.support_style == smsSnug; + const bool smooth_supports = support_params.support_style != smsGrid; SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first; SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second; + interface_layers.assign(intermediate_layers.size(), nullptr); if (support_params.has_base_interfaces()) base_interface_layers.assign(intermediate_layers.size(), nullptr); @@ -152,7 +153,7 @@ std::pair generate_interfa const auto closing_distance = smoothing_distance; // scaled(config.support_material_closing_radius.value); // Insert a new layer into base_interface_layers, if intersection with base exists. auto insert_layer = [&layer_storage, smooth_supports, closing_distance, smoothing_distance, minimum_island_radius]( - SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, SupportGeneratorLayer *top_interface_layer, + SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, SupportGeneratorLayer *top_interface_layer, const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { bool has_top_interface = top_interface_layer && ! top_interface_layer->polygons.empty(); assert(! bottom.empty() || ! top.empty() || has_top_interface); @@ -194,7 +195,7 @@ std::pair generate_interfa }; tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), [&bottom_contacts, &top_contacts, &top_interface_layers, &top_base_interface_layers, &intermediate_layers, &insert_layer, &support_params, - snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { + snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below // this intermediate layer. // Index of the first top contact layer intersecting the current intermediate layer. @@ -230,7 +231,7 @@ std::pair generate_interfa //FIXME maybe this adds one interface layer in excess? if (top_contact_layer.bottom_z - EPSILON > top_z) break; - polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, + polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers. // For grid supports, merging of support regions will be performed by the projection into grid. snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); @@ -242,7 +243,7 @@ std::pair generate_interfa coordf_t bottom_interface_z = - std::numeric_limits::max(); if (support_params.num_bottom_base_interface_layers > 0) // Some bottom base interface layers will be generated. - bottom_interface_z = support_params.num_bottom_interface_layers_only() == 0 ? + bottom_interface_z = support_params.num_bottom_interface_layers_only() == 0 ? // Only base interface layers to generate. std::numeric_limits::max() : intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers_only()))]->bottom_z; @@ -307,7 +308,7 @@ std::pair generate_interfa base_interface_layers = merge_remove_empty(base_interface_layers, top_base_interface_layers); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end"; } - + return base_and_interface_layers; } @@ -329,19 +330,21 @@ SupportGeneratorLayersPtr generate_raft_base( const BrimType brim_type = object.config().brim_type; const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; - const auto brim_separation = scaled(object.config().brim_object_gap.value + object.config().brim_width.value); + // BBS: the pattern of raft and brim are the same, thus the brim can be serpated by support raft. + const auto brim_object_gap = scaled(object.config().brim_object_gap.value); + //const auto brim_object_gap = scaled(object.config().brim_object_gap.value + object.config().brim_width.value); for (const ExPolygon &ex : object.layers().front()->lslices) { if (brim_outer && brim_inner) - polygons_append(brim, offset(ex, brim_separation)); + polygons_append(brim, offset(ex, brim_object_gap)); else { if (brim_outer) - polygons_append(brim, offset(ex.contour, brim_separation, ClipperLib::jtRound, float(scale_(0.1)))); + polygons_append(brim, offset(ex.contour, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1)))); else brim.emplace_back(ex.contour); if (brim_inner) { Polygons holes = ex.holes; polygons_reverse(holes); - holes = shrink(holes, brim_separation, ClipperLib::jtRound, float(scale_(0.1))); + holes = shrink(holes, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1))); polygons_reverse(holes); polygons_append(brim, std::move(holes)); } else @@ -378,7 +381,7 @@ SupportGeneratorLayersPtr generate_raft_base( polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - + // Output vector. SupportGeneratorLayersPtr raft_layers; @@ -402,12 +405,12 @@ SupportGeneratorLayersPtr generate_raft_base( } if (! interface_polygons.empty()) { // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. - base = union_(base, interface_polygons); + base = union_(base, interface_polygons); } // Do not add the raft contact layer, only add the raft layers below the contact layer. // Insert the 1st layer. { - SupportGeneratorLayer &new_layer = layer_storage.allocate_unguarded(slicing_params.base_raft_layers > 0 ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface); + SupportGeneratorLayer &new_layer = layer_storage.allocate(slicing_params.base_raft_layers > 0 ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface); raft_layers.push_back(&new_layer); new_layer.print_z = slicing_params.first_print_layer_height; new_layer.height = slicing_params.first_print_layer_height; @@ -441,7 +444,13 @@ SupportGeneratorLayersPtr generate_raft_base( if (columns_base != nullptr) { // Expand the bases of the support columns in the 1st layer. Polygons &raft = columns_base->polygons; - Polygons trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); + Polygons trimming; + // BBS: if first layer of support is intersected with object island, it must have the same function as brim unless in nobrim mode. + // brim_object_gap is changed to 0 by default, it's no longer appropriate to use it to determine the gap of first layer support. + //if (object.has_brim()) + // trimming = offset(object.layers().front()->lslices, (float)scale_(object.config().brim_object_gap.value), SUPPORT_SURFACES_OFFSET_PARAMETERS); + //else + trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (inflate_factor_1st_layer > SCALED_EPSILON) { // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width()))); @@ -536,10 +545,10 @@ static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length) return polylines; } -static inline void tree_supports_generate_paths( +void tree_supports_generate_paths( ExtrusionEntitiesPtr &dst, const Polygons &polygons, - const Flow &flow, + const Flow &flow, const SupportParameters &support_params) { // Offset expolygon inside, returns number of expolygons collected (0 or 1). @@ -606,7 +615,7 @@ static inline void tree_supports_generate_paths( // No hole remaining after an offset. Just copy the outer contour. append(out, std::move(contours)); } else { - // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. + // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. // Subtract the offsetted holes from the offsetted contours. ClipperLib_Z::Clipper clipper; clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) { @@ -637,124 +646,126 @@ static inline void tree_supports_generate_paths( ClipperLib_Z::Paths anchor_candidates; for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) { std::unique_ptr eec; + ExPolygons regions_to_draw_inner_wall{expoly}; if (support_params.tree_branch_diameter_double_wall_area_scaled > 0) if (double area = expoly.area(); area > support_params.tree_branch_diameter_double_wall_area_scaled) { + BOOST_LOG_TRIVIAL(debug)<< "TreeSupports: double wall area: " << area<< " > " << support_params.tree_branch_diameter_double_wall_area_scaled; eec = std::make_unique(); - // Don't reoder internal / external loops of the same island, always start with the internal loop. + // Don't reorder internal / external loops of the same island, always start with the internal loop. eec->no_sort = true; // Make the tree branch stable by adding another perimeter. - ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); - if (level2.size() == 1) { - Polylines polylines; + ExPolygons level2 = offset2_ex({expoly}, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); + if (level2.size() > 0) { + regions_to_draw_inner_wall = level2; extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), - // Disable reversal of the path, always start with the anchor, always print CCW. - false); + // Disable reversal of the path, always start with the anchor, always print CCW. + false); expoly = level2.front(); } } - - // Try to produce one more perimeter to place the seam anchor. - // First genrate a 2nd perimeter loop as a source for anchor candidates. - // The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection. - anchor_candidates.clear(); - shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates); - // Orient all contours CW. - for (auto &path : anchor_candidates) - if (ClipperLib_Z::Area(path) > 0) - std::reverse(path.begin(), path.end()); - - // Draw the perimeters. - Polylines polylines; - polylines.reserve(expoly.holes.size() + 1); - for (int idx_loop = 0; idx_loop < int(expoly.num_contours()); ++ idx_loop) { - // Open the loop with a seam. - const Polygon &loop = expoly.contour_or_hole(idx_loop); - Polyline pl(loop.points); - // Orient all contours CW, because the anchor will be added to the end of polyline while we want to start a loop with the anchor. - if (idx_loop == 0) - // It is an outer contour. - pl.reverse(); - pl.points.emplace_back(pl.points.front()); - pl.clip_end(clip_length); - if (pl.size() < 2) - continue; - // Find the foot of the seam point on anchor_candidates. Only pick an anchor point that was created by offsetting the source contour. - ClipperLib_Z::Path *closest_contour = nullptr; - Vec2d closest_point; - int closest_point_idx = -1; - double closest_point_t = 0.; - double d2min = std::numeric_limits::max(); - Vec2d seam_pt = pl.back().cast(); - for (ClipperLib_Z::Path &path : anchor_candidates) - for (int i = 0; i < int(path.size()); ++ i) { - int j = next_idx_modulo(i, path); - if (path[i].z() == idx_loop || path[j].z() == idx_loop) { - Vec2d pi(path[i].x(), path[i].y()); - Vec2d pj(path[j].x(), path[j].y()); - Vec2d v = pj - pi; - Vec2d w = seam_pt - pi; - auto l2 = v.squaredNorm(); - auto t = std::clamp((l2 == 0) ? 0 : v.dot(w) / l2, 0., 1.); - if ((path[i].z() == idx_loop || t > EPSILON) && (path[j].z() == idx_loop || t < 1. - EPSILON)) { - // Closest point. - Vec2d fp = pi + v * t; - double d2 = (fp - seam_pt).squaredNorm(); - if (d2 < d2min) { - d2min = d2; - closest_contour = &path; - closest_point = fp; - closest_point_idx = i; - closest_point_t = t; + for (ExPolygon &expoly : regions_to_draw_inner_wall) + { + // Try to produce one more perimeter to place the seam anchor. + // First genrate a 2nd perimeter loop as a source for anchor candidates. + // The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection. + anchor_candidates.clear(); + shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates); + // Orient all contours CW. + for (auto &path : anchor_candidates) + if (ClipperLib_Z::Area(path) > 0) std::reverse(path.begin(), path.end()); + + // Draw the perimeters. + Polylines polylines; + polylines.reserve(expoly.holes.size() + 1); + for (int idx_loop = 0; idx_loop < int(expoly.num_contours()); ++idx_loop) { + // Open the loop with a seam. + const Polygon &loop = expoly.contour_or_hole(idx_loop); + Polyline pl(loop.points); + // Orient all contours CW, because the anchor will be added to the end of polyline while we want to start a loop with the anchor. + if (idx_loop == 0) + // It is an outer contour. + pl.reverse(); + pl.points.emplace_back(pl.points.front()); + pl.clip_end(clip_length); + if (pl.size() < 2) continue; + // Find the foot of the seam point on anchor_candidates. Only pick an anchor point that was created by offsetting the source contour. + ClipperLib_Z::Path *closest_contour = nullptr; + Vec2d closest_point; + int closest_point_idx = -1; + double closest_point_t = 0.; + double d2min = std::numeric_limits::max(); + Vec2d seam_pt = pl.back().cast(); + for (ClipperLib_Z::Path &path : anchor_candidates) + for (int i = 0; i < int(path.size()); ++i) { + int j = next_idx_modulo(i, path); + if (path[i].z() == idx_loop || path[j].z() == idx_loop) { + Vec2d pi(path[i].x(), path[i].y()); + Vec2d pj(path[j].x(), path[j].y()); + Vec2d v = pj - pi; + Vec2d w = seam_pt - pi; + auto l2 = v.squaredNorm(); + auto t = std::clamp((l2 == 0) ? 0 : v.dot(w) / l2, 0., 1.); + if ((path[i].z() == idx_loop || t > EPSILON) && (path[j].z() == idx_loop || t < 1. - EPSILON)) { + // Closest point. + Vec2d fp = pi + v * t; + double d2 = (fp - seam_pt).squaredNorm(); + if (d2 < d2min) { + d2min = d2; + closest_contour = &path; + closest_point = fp; + closest_point_idx = i; + closest_point_t = t; + } } } } - } - if (d2min < sqr(flow.scaled_width() * 3.)) { - // Try to cut an anchor from the closest_contour. - // Both closest_contour and pl are CW oriented. - pl.points.emplace_back(closest_point.cast()); - const ClipperLib_Z::Path &path = *closest_contour; - double remaining_length = anchor_length - (seam_pt - closest_point).norm(); - int i = closest_point_idx; - int j = next_idx_modulo(i, *closest_contour); - Vec2d pi(path[i].x(), path[i].y()); - Vec2d pj(path[j].x(), path[j].y()); - Vec2d v = pj - pi; - double l = v.norm(); - if (remaining_length < (1. - closest_point_t) * l) { - // Just trim the current line. - pl.points.emplace_back((closest_point + v * (remaining_length / l)).cast()); - } else { - // Take the rest of the current line, continue with the other lines. - pl.points.emplace_back(path[j].x(), path[j].y()); - pi = pj; - for (i = j; path[i].z() == idx_loop && remaining_length > 0; i = j, pi = pj) { - j = next_idx_modulo(i, path); - pj = Vec2d(path[j].x(), path[j].y()); - v = pj - pi; - l = v.norm(); - if (i == closest_point_idx) { - // Back at the first segment. Most likely this should not happen and we may end the anchor. - break; - } - if (remaining_length <= l) { - pl.points.emplace_back((pi + v * (remaining_length / l)).cast()); - break; - } + if (d2min < sqr(flow.scaled_width() * 3.)) { + // Try to cut an anchor from the closest_contour. + // Both closest_contour and pl are CW oriented. + pl.points.emplace_back(closest_point.cast()); + const ClipperLib_Z::Path &path = *closest_contour; + double remaining_length = anchor_length - (seam_pt - closest_point).norm(); + int i = closest_point_idx; + int j = next_idx_modulo(i, *closest_contour); + Vec2d pi(path[i].x(), path[i].y()); + Vec2d pj(path[j].x(), path[j].y()); + Vec2d v = pj - pi; + double l = v.norm(); + if (remaining_length < (1. - closest_point_t) * l) { + // Just trim the current line. + pl.points.emplace_back((closest_point + v * (remaining_length / l)).cast()); + } else { + // Take the rest of the current line, continue with the other lines. pl.points.emplace_back(path[j].x(), path[j].y()); - remaining_length -= l; + pi = pj; + for (i = j; path[i].z() == idx_loop && remaining_length > 0; i = j, pi = pj) { + j = next_idx_modulo(i, path); + pj = Vec2d(path[j].x(), path[j].y()); + v = pj - pi; + l = v.norm(); + if (i == closest_point_idx) { + // Back at the first segment. Most likely this should not happen and we may end the anchor. + break; + } + if (remaining_length <= l) { + pl.points.emplace_back((pi + v * (remaining_length / l)).cast()); + break; + } + pl.points.emplace_back(path[j].x(), path[j].y()); + remaining_length -= l; + } } } + // Start with the anchor. + pl.reverse(); + polylines.emplace_back(std::move(pl)); } - // Start with the anchor. - pl.reverse(); - polylines.emplace_back(std::move(pl)); - } - ExtrusionEntitiesPtr &out = eec ? eec->entities : dst; - extrusion_entities_append_paths(out, std::move(polylines), ExtrusionRole::erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), - // Disable reversal of the path, always start with the anchor, always print CCW. - false); + ExtrusionEntitiesPtr &out = eec ? eec->entities : dst; + extrusion_entities_append_paths(out, std::move(polylines), ExtrusionRole::erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), + // Disable reversal of the path, always start with the anchor, always print CCW. + false); + } if (eec) { std::reverse(eec->entities.begin(), eec->entities.end()); dst.emplace_back(eec.release()); @@ -762,20 +773,27 @@ static inline void tree_supports_generate_paths( } } -static inline void fill_expolygons_with_sheath_generate_paths( +void fill_expolygons_with_sheath_generate_paths( ExtrusionEntitiesPtr &dst, const Polygons &polygons, Fill *filler, float density, ExtrusionRole role, const Flow &flow, + const SupportParameters& support_params, bool with_sheath, bool no_sort) { if (polygons.empty()) return; - if (! with_sheath) { + if (with_sheath) { + if (density == 0) { + tree_supports_generate_paths(dst, polygons, flow, support_params); + return; + } + } + else { fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); return; } @@ -819,8 +837,8 @@ struct SupportGeneratorLayerExtruded return layer == nullptr || layer->polygons.empty(); } - void set_polygons_to_extrude(Polygons &&polygons) { - if (m_polygons_to_extrude == nullptr) + void set_polygons_to_extrude(Polygons &&polygons) { + if (m_polygons_to_extrude == nullptr) m_polygons_to_extrude = std::make_unique(std::move(polygons)); else *m_polygons_to_extrude = std::move(polygons); @@ -829,9 +847,9 @@ struct SupportGeneratorLayerExtruded const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } bool could_merge(const SupportGeneratorLayerExtruded &other) const { - return ! this->empty() && ! other.empty() && + return ! this->empty() && ! other.empty() && std::abs(this->layer->height - other.layer->height) < EPSILON && - this->layer->bridging == other.layer->bridging; + this->layer->bridging == other.layer->bridging; } // Merge regions, perform boolean union over the merged polygons. @@ -937,7 +955,7 @@ void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact const Point* operator()(const Point &pt) const { return &pt; } }; typedef ClosestPointInRadiusLookup ClosestPointLookupType; - + Polygons loops0; { // find centerline of the external loop of the contours @@ -1034,7 +1052,7 @@ void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact for (int i = 1; i < n_contact_loops; ++ i) polygons_append(loop_polygons, opening( - loops0, + loops0, i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), 0.5f * flow.scaled_spacing())); // Clip such loops to the side oriented towards the object. @@ -1094,7 +1112,7 @@ void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact // Remove empty lines. remove_degenerate(loop_lines); } - + // add the contact infill area to the interface area // note that growing loops by $circle_radius ensures no tiny // extrusions are left inside the circles; however it creates @@ -1166,7 +1184,7 @@ static void modulate_extrusion_by_overlapping_layers( // Split the extrusions by the overlapping layers, reduce their extrusion rate. // The last path_fragment is from this_layer. std::vector path_fragments( - n_overlapping_layers + 1, + n_overlapping_layers + 1, ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); // Don't use it, it will be released. extrusion_path_template = nullptr; @@ -1218,7 +1236,7 @@ static void modulate_extrusion_by_overlapping_layers( #endif /* SLIC3R_DEBUG */ // End points of the original paths. - std::vector> path_ends; + std::vector> path_ends; // Collect the paths of this_layer. { Polylines &polylines = path_fragments.back().polylines; @@ -1399,6 +1417,10 @@ SupportGeneratorLayersPtr generate_support_layers( append(layers_sorted, intermediate_layers); append(layers_sorted, interface_layers); append(layers_sorted, base_interface_layers); + // remove dupliated layers + std::sort(layers_sorted.begin(), layers_sorted.end()); + layers_sorted.erase(std::unique(layers_sorted.begin(), layers_sorted.end()), layers_sorted.end()); + // Sort the layers lexicographically by a raising print_z and a decreasing height. std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); int layer_id = 0; @@ -1435,7 +1457,7 @@ SupportGeneratorLayersPtr generate_support_layers( height_min = std::min(height_min, layer.height); } if (! empty) { - // Here the upper_layer and lower_layer pointers are left to null at the support layers, + // Here the upper_layer and lower_layer pointers are left to null at the support layers, // as they are never used. These pointers are candidates for removal. bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; size_t this_layer_id_interface = layer_id_interface; @@ -1510,7 +1532,7 @@ void generate_support_toolpaths( // Print the support base below the support columns, or the support base for the support columns plus the contacts. if (support_layer_id > 0) { - const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ? + const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ? raft_layer.polygons : //FIXME misusing contact_polygons for support columns. ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); @@ -1531,7 +1553,7 @@ void generate_support_toolpaths( filler, float(support_params.support_density), // Extrusion parameters ExtrusionRole::erSupportMaterial, flow, - support_params.with_sheath, false); + support_params, support_params.with_sheath, false); } if (! tree_polygons.empty()) tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow, support_params); @@ -1558,15 +1580,15 @@ void generate_support_toolpaths( filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); fill_expolygons_with_sheath_generate_paths( // Destination - support_layer.support_fills.entities, + support_layer.support_fills.entities, // Regions to fill tree_polygons.empty() ? raft_layer.polygons : diff(raft_layer.polygons, tree_polygons), // Filler and its parameters filler, density, // Extrusion parameters - (support_layer_id < slicing_params.base_raft_layers) ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, flow, + (support_layer_id < slicing_params.base_raft_layers) ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, flow, // sheath at first layer - support_layer_id == 0, support_layer_id == 0); + support_params, support_layer_id == 0, support_layer_id == 0); } }); @@ -1610,12 +1632,12 @@ void generate_support_toolpaths( // Pointer to the 1st layer interface filler. auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); // Filler for the 1st layer interface, if different from filler_interface. - auto filler_raft_contact_ptr = std::unique_ptr(range.begin() == n_raft_layers && config.support_interface_top_layers.value == 0 ? + auto filler_raft_contact_ptr = std::unique_ptr(range.begin() == n_raft_layers && config.support_interface_top_layers.value == 0 ? Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr); // Pointer to the 1st layer interface filler. auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get(); // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). - auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : + auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase)); auto filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); filler_interface->set_bounding_box(bbox_object); @@ -1630,7 +1652,7 @@ void generate_support_toolpaths( { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; - const float support_interface_angle = config.support_style.value == smsGrid ? + const float support_interface_angle = (support_params.support_style == smsGrid || config.support_interface_pattern == smipRectilinear) ? support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()); // Find polygons with the same print_z. @@ -1678,7 +1700,7 @@ void generate_support_toolpaths( // to trim other layers. if (top_contact_layer.could_merge(interface_layer) && ! raft_layer) top_contact_layer.merge(std::move(interface_layer)); - } + } if ((config.support_interface_top_layers == 0 || config.support_interface_bottom_layers == 0) && support_params.can_merge_support_regions) { if (base_layer.could_merge(bottom_contact_layer)) base_layer.merge(std::move(bottom_contact_layer)); @@ -1712,14 +1734,14 @@ void generate_support_toolpaths( auto *filler = raft_contact ? filler_raft_contact : filler_interface.get(); auto interface_flow = layer_ex.layer->bridging ? Flow::bridging_flow(layer_ex.layer->height, support_params.support_material_bottom_interface_flow.nozzle_diameter()) : - (raft_contact ? &support_params.raft_interface_flow : + (raft_contact ? &support_params.raft_interface_flow : interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow) ->with_height(float(layer_ex.layer->height)); filler->angle = interface_as_base ? // If zero interface layers are configured, use the same angle as for the base layers. angles[support_layer_id % angles.size()] : // Use interface angle for the interface layers. - raft_contact ? + raft_contact ? support_params.raft_interface_angle(support_layer.interface_id()) : support_interface_angle; double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density; @@ -1728,13 +1750,13 @@ void generate_support_toolpaths( filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); fill_expolygons_generate_paths( // Destination - layer_ex.extrusions, + layer_ex.extrusions, // Regions to fill union_safety_offset_ex(layer_ex.polygons_to_extrude()), // Filler and its parameters filler, float(density), // Extrusion parameters - ExtrusionRole::erSupportMaterialInterface, interface_flow); + interface_as_base ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, interface_flow); } }; const bool top_interfaces = config.support_interface_top_layers.value != 0; @@ -1755,7 +1777,7 @@ void generate_support_toolpaths( filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density)); fill_expolygons_generate_paths( // Destination - base_interface_layer.extrusions, + base_interface_layer.extrusions, //base_layer_interface.extrusions, // Regions to fill union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), @@ -1767,7 +1789,7 @@ void generate_support_toolpaths( // Base support or flange. if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { - Fill *filler = filler_support.get(); + Fill *filler = filler_support.get(); filler->angle = angles[support_layer_id % angles.size()]; // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. @@ -1792,10 +1814,12 @@ void generate_support_toolpaths( filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); sheath = true; no_sort = true; - } else if (config.support_style == SupportMaterialStyle::smsOrganic || - // Orca: use organic as default - config.support_style == smsDefault) { - tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow, support_params); + } else if (support_params.support_style == SupportMaterialStyle::smsTreeOrganic) { + // if the tree supports are too tall, use double wall to make it stronger + SupportParameters support_params2 = support_params; + if (support_layer.print_z > 100.0) + support_params2.tree_branch_diameter_double_wall_area_scaled = 0.1; + tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow, support_params2); done = true; } if (! done) @@ -1808,7 +1832,7 @@ void generate_support_toolpaths( filler, density, // Extrusion parameters ExtrusionRole::erSupportMaterial, flow, - sheath, no_sort); + support_params, sheath, no_sort); } // Merge base_interface_layers to base_layers to avoid unneccessary retractions @@ -1912,7 +1936,7 @@ void PrintObjectSupportMaterial::clip_by_pillars( coord_t pillar_size = scale_(PILLAR_SIZE); coord_t pillar_spacing = scale_(PILLAR_SPACING); - + // A regular grid of pillars, filling the 2D bounding box. Polygons grid; { @@ -1922,7 +1946,7 @@ void PrintObjectSupportMaterial::clip_by_pillars( pillar.points.push_back(Point(pillar_size, 0)); pillar.points.push_back(Point(pillar_size, pillar_size)); pillar.points.push_back(Point(0, pillar_size)); - + // 2D bounding box of the projection of all contact polygons. BoundingBox bbox; for (LayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) @@ -1936,30 +1960,30 @@ void PrintObjectSupportMaterial::clip_by_pillars( } } } - + // add pillars to every layer for my $i (0..n_support_z) { $shape->[$i] = [ @$grid ]; } - + // build capitals for my $i (0..n_support_z) { my $z = $support_z->[$i]; - + my $capitals = intersection( $grid, $contact->{$z} // [], ); - + // work on one pillar at time (if any) to prevent the capitals from being merged - // but store the contact area supported by the capital because we need to make + // but store the contact area supported by the capital because we need to make // sure nothing is left my $contact_supported_by_capitals = []; foreach my $capital (@$capitals) { // enlarge capital tops $capital = offset([$capital], +($pillar_spacing - $pillar_size)/2); push @$contact_supported_by_capitals, @$capital; - + for (my $j = $i-1; $j >= 0; $j--) { my $jz = $support_z->[$j]; $capital = offset($capital, -$self->interface_flow->scaled_width/2); @@ -1967,7 +1991,7 @@ void PrintObjectSupportMaterial::clip_by_pillars( push @{ $shape->[$j] }, @$capital; } } - + // Capitals will not generally cover the whole contact area because there will be // remainders. For now we handle this situation by projecting such unsupported // areas to the ground, just like we would do with a normal support. @@ -1985,10 +2009,10 @@ void PrintObjectSupportMaterial::clip_by_pillars( sub clip_with_shape { my ($self, $support, $shape) = @_; - + foreach my $i (keys %$support) { - // don't clip bottom layer with shape so that we - // can generate a continuous base flange + // don't clip bottom layer with shape so that we + // can generate a continuous base flange // also don't clip raft layers next if $i == 0; next if $i < $self->object_config->raft_layers; diff --git a/src/libslic3r/Support/SupportCommon.hpp b/src/libslic3r/Support/SupportCommon.hpp index cdaa43c5dbf..6f5894fc1d8 100644 --- a/src/libslic3r/Support/SupportCommon.hpp +++ b/src/libslic3r/Support/SupportCommon.hpp @@ -50,6 +50,11 @@ SupportGeneratorLayersPtr generate_raft_base( const SupportGeneratorLayersPtr &base_layers, SupportGeneratorLayerStorage &layer_storage); +void tree_supports_generate_paths(ExtrusionEntitiesPtr &dst, const Polygons &polygons, const Flow &flow, const SupportParameters &support_params); + +void fill_expolygons_with_sheath_generate_paths( + ExtrusionEntitiesPtr &dst, const Polygons &polygons, Fill *filler, float density, ExtrusionRole role, const Flow &flow, const SupportParameters& support_params, bool with_sheath, bool no_sort); + // returns sorted layers SupportGeneratorLayersPtr generate_support_layers( PrintObject &object, diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index 3050bd7f6e0..61be8d3912b 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -329,100 +329,13 @@ static Polygons contours_simplified(const Vec2i32 &grid_size, const double pixel } #endif // SUPPORT_USE_AGG_RASTERIZER -static std::string get_svg_filename(std::string layer_nr_or_z, std::string tag = "bbl_ts") -{ - static bool rand_init = false; - - if (!rand_init) { - srand(time(NULL)); - rand_init = true; - } - - int rand_num = rand() % 1000000; - //makedir("./SVG"); - std::string prefix = "./SVG/"; - std::string suffix = ".svg"; - return prefix + tag + "_" + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix; -} - PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : - m_object (object), m_print_config (&object->print()->config()), m_object_config (&object->config()), - m_slicing_params (slicing_params) + m_slicing_params (slicing_params), + m_support_params (*object), + m_object (object) { - m_support_params.first_layer_flow = support_material_1st_layer_flow(object, float(slicing_params.first_print_layer_height)); - m_support_params.support_material_flow = support_material_flow(object, float(slicing_params.layer_height)); - m_support_params.support_material_interface_flow = support_material_interface_flow(object, float(slicing_params.layer_height)); - m_support_params.support_layer_height_min = 0.01; - - // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. - m_support_params.support_layer_height_min = 1000000.; - for (auto lh : m_print_config->min_layer_height.values) - m_support_params.support_layer_height_min = std::min(m_support_params.support_layer_height_min, std::max(0.01, lh)); - for (auto layer : m_object->layers()) - m_support_params.support_layer_height_min = std::min(m_support_params.support_layer_height_min, std::max(0.01, layer->height)); - - if (m_object_config->support_interface_top_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - m_support_params.support_material_interface_flow = m_support_params.support_material_flow; - } - - // Evaluate the XY gap between the object outer perimeters and the support structures. - // Evaluate the XY gap between the object outer perimeters and the support structures. - coordf_t external_perimeter_width = 0.; - coordf_t bridge_flow = 0; - for (size_t region_id = 0; region_id < object->num_printing_regions(); ++ region_id) { - const PrintRegion ®ion = object->printing_region(region_id); - external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width())); - bridge_flow += region.config().bridge_flow; - } - m_support_params.gap_xy = m_object_config->support_object_xy_distance.value; - bridge_flow /= object->num_printing_regions(); - - m_support_params.support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ? - m_support_params.support_material_interface_flow.with_flow_ratio(bridge_flow) : - Flow::bridging_flow(bridge_flow * m_support_params.support_material_interface_flow.nozzle_diameter(), m_support_params.support_material_interface_flow.nozzle_diameter()); - - m_support_params.can_merge_support_regions = m_object_config->support_filament.value == m_object_config->support_interface_filament.value; - if (!m_support_params.can_merge_support_regions && (m_object_config->support_filament.value == 0 || m_object_config->support_interface_filament.value == 0)) { - // One of the support extruders is of "don't care" type. - auto object_extruders = m_object->object_extruders(); - if (object_extruders.size() == 1 && - *object_extruders.begin() == std::max(m_object_config->support_filament.value, m_object_config->support_interface_filament.value)) - // Object is printed with the same extruder as the support. - m_support_params.can_merge_support_regions = true; - } - - - m_support_params.base_angle = Geometry::deg2rad(float(m_object_config->support_angle.value)); - m_support_params.interface_angle = Geometry::deg2rad(float(m_object_config->support_angle.value + 90.)); - m_support_params.interface_spacing = m_object_config->support_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); - m_support_params.interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / m_support_params.interface_spacing); - m_support_params.support_spacing = m_object_config->support_base_pattern_spacing.value + m_support_params.support_material_flow.spacing(); - m_support_params.support_density = std::min(1., m_support_params.support_material_flow.spacing() / m_support_params.support_spacing); - if (m_object_config->support_interface_top_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - m_support_params.interface_spacing = m_support_params.support_spacing; - m_support_params.interface_density = m_support_params.support_density; - } - - SupportMaterialPattern support_pattern = m_object_config->support_base_pattern; - m_support_params.with_sheath = support_with_sheath; - m_support_params.base_fill_pattern = - support_pattern == smpHoneycomb ? ipHoneycomb : - m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase; - m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); - if (m_object_config->support_interface_pattern == smipGrid) - m_support_params.contact_fill_pattern = ipGrid; - else if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) - m_support_params.contact_fill_pattern = ipRectilinear; - else - m_support_params.contact_fill_pattern = - (m_object_config->support_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || - m_object_config->support_interface_pattern == smipConcentric ? - ipConcentric : - (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); } // Using the std::deque as an allocator. @@ -563,14 +476,15 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Propagate top / bottom contact layers to generate interface layers // and base interface layers (for soluble interface / non souble base only) - auto [interface_layers, base_interface_layers] = this->generate_interface_layers(bottom_contacts, top_contacts, intermediate_layers, layer_storage); + SupportGeneratorLayersPtr empty_layers; + auto [interface_layers, base_interface_layers] = generate_interface_layers(*m_object_config, m_support_params, bottom_contacts, top_contacts, empty_layers, empty_layers, intermediate_layers, layer_storage); BOOST_LOG_TRIVIAL(info) << "Support generator - Creating raft"; // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled. // There is also a 1st intermediate layer containing bases of support columns. // Inflate the bases of the support columns and create the raft base under the object. - SupportGeneratorLayersPtr raft_layers = this->generate_raft_base(object, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); + SupportGeneratorLayersPtr raft_layers = generate_raft_base(object, m_support_params, m_slicing_params, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); if (object.print()->canceled()) return; @@ -637,20 +551,8 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) } #endif /* SLIC3R_DEBUG */ -#if 0 // #ifdef SLIC3R_DEBUG - // check bounds - std::ofstream out; - out.open("./SVG/ns_support_layers.txt"); - if (out.is_open()) { - out << "### Support Layers ###" << std::endl; - for (auto& i : object.support_layers()) { - out << i->print_z << std::endl; - } - } -#endif /* SLIC3R_DEBUG */ - // Generate the actual toolpaths and save them into each layer. - this->generate_toolpaths(object.support_layers(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); + generate_support_toolpaths(object.support_layers(), *m_object_config, m_support_params, m_slicing_params, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); #ifdef SLIC3R_DEBUG { @@ -747,7 +649,9 @@ class SupportGridPattern m_extrusion_width(params.extrusion_width), m_support_material_closing_radius(params.support_closing_radius) { - if (m_style != smsSnug) m_style = smsGrid; + if (m_style == smsDefault) m_style = smsGrid; + if (std::set{smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsTreeOrganic}.count(m_style)) + m_style = smsGrid; switch (m_style) { case smsGrid: { @@ -839,6 +743,13 @@ class SupportGridPattern ) { switch (m_style) { + case smsTreeSlim: + case smsTreeStrong: + case smsTreeHybrid: + case smsTreeOrganic: + assert(false); + //[[fallthrough]]; + return Polygons(); case smsGrid: { #ifdef SUPPORT_USE_AGG_RASTERIZER @@ -1473,6 +1384,7 @@ static inline ExPolygons detect_overhangs( const coordf_t max_bridge_length = scale_(object_config.max_bridge_length.value); const bool bridge_no_support = object_config.bridge_no_support.value; const coordf_t xy_expansion = scale_(object_config.support_expansion.value); + float lower_layer_offset = 0; if (layer_id == 0) { @@ -1485,7 +1397,7 @@ static inline ExPolygons detect_overhangs( !(bbox_size.x() > length_thresh_well_supported && bbox_size.y() > length_thresh_well_supported)) { layer.sharp_tails.push_back(slice); - layer.sharp_tails_height.insert({ &slice, layer.height }); + layer.sharp_tails_height.push_back(layer.height); } } } @@ -1505,7 +1417,6 @@ static inline ExPolygons detect_overhangs( } } - float lower_layer_offset = 0; for (LayerRegion *layerm : layer.regions()) { // Extrusion width accounts for the roundings of the extrudates. // It is the maximum widh of the extrudate. @@ -1546,7 +1457,6 @@ static inline ExPolygons detect_overhangs( // This is done to increase size of the supporting columns below, as they are calculated by // propagating these contact surfaces downwards. diff_polygons = diff(intersection(expand(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), lower_layer_polygons); - if (xy_expansion != 0) { diff_polygons = expand(diff_polygons, xy_expansion, SUPPORT_SURFACES_OFFSET_PARAMETERS); } } //FIXME add user defined filtering here based on minimal area or minimum radius or whatever. @@ -1567,7 +1477,7 @@ static inline ExPolygons detect_overhangs( if (is_sharp_tail) { ExPolygons overhang = diff_ex({ expoly }, lower_layer_expolys); layer.sharp_tails.push_back(expoly); - layer.sharp_tails_height.insert({ &expoly, accum_height }); + layer.sharp_tails_height.push_back(accum_height); overhang = offset_ex(overhang, 0.05 * fw); polygons_append(diff_polygons, to_polygons(overhang)); } @@ -1584,8 +1494,9 @@ static inline ExPolygons detect_overhangs( // spanning just the projection between the two slices. // Subtracting them as they are may leave unwanted narrow // residues of diff_polygons that would then be supported. - diff_polygons = diff(diff_polygons, - expand(union_(annotations.blockers_layers[layer_id]), float(1000. * SCALED_EPSILON))); + auto blocker = expand(union_(annotations.blockers_layers[layer_id]), float(1000. * SCALED_EPSILON)); + diff_polygons = diff(diff_polygons, blocker); + layer.sharp_tails = diff_ex(layer.sharp_tails, blocker); } if (bridge_no_support) { @@ -1597,6 +1508,8 @@ static inline ExPolygons detect_overhangs( if (diff_polygons.empty() || offset(diff_polygons, -0.1 * fw).empty()) continue; + if (xy_expansion != 0) { diff_polygons = expand(diff_polygons, xy_expansion, SUPPORT_SURFACES_OFFSET_PARAMETERS); } + polygons_append(overhang_polygons, diff_polygons); } // for each layer.region } @@ -1606,7 +1519,7 @@ static inline ExPolygons detect_overhangs( if (layer.lower_layer) { for (ExPolygon& poly : overhang_areas) { float fw = float(layer.regions().front()->flow(frExternalPerimeter).scaled_width()); - auto cluster_boundary_ex = intersection_ex(poly, offset_ex(layer.lower_layer->lslices, scale_(0.5))); + auto cluster_boundary_ex = intersection_ex(poly, offset_ex(layer.lower_layer->lslices, std::max(fw, lower_layer_offset) + scale_(0.1))); Polygons cluster_boundary = to_polygons(cluster_boundary_ex); if (cluster_boundary.empty()) continue; double dist_max = 0; @@ -1755,6 +1668,50 @@ static inline std::tuple detect_contacts( return std::make_tuple(std::move(contact_polygons), std::move(enforcer_polygons), no_interface_offset); } +// find the object layer that is closest to the {layer.bottom_z-gap_support_object} for top contact, +// or {layer.print_z+gap_object_support} for bottom contact +Layer* sync_gap_with_object_layer(const Layer& layer, const coordf_t gap_support_object, bool is_top_contact) +{ + // sync gap with the object layer height + float gap_synced = 0; + if (is_top_contact) { + Layer* lower_layer = layer.lower_layer, * last_valid_gap_layer = layer.lower_layer; + while (lower_layer && gap_synced < gap_support_object) { + last_valid_gap_layer = lower_layer; + gap_synced += lower_layer->height; + lower_layer = lower_layer->lower_layer; + + } + // maybe gap_synced is too large, find the nearest object layer (one layer above may be better) + if (std::abs(gap_synced - last_valid_gap_layer->height - gap_support_object) < std::abs(gap_synced - gap_support_object)) { + gap_synced -= last_valid_gap_layer->height; + last_valid_gap_layer = last_valid_gap_layer->upper_layer; + } + lower_layer = last_valid_gap_layer; // layer just below the last valid gap layer + if (last_valid_gap_layer->lower_layer) + lower_layer = last_valid_gap_layer->lower_layer; + return lower_layer; + }else{ + Layer* upper_layer = layer.upper_layer, * last_valid_gap_layer = layer.upper_layer; + while (upper_layer && gap_synced < gap_support_object) { + last_valid_gap_layer = upper_layer; + gap_synced += upper_layer->height; + upper_layer = upper_layer->upper_layer; + } + // maybe gap_synced is too large, find the nearest object layer (one layer above may be better) + if (std::abs(gap_synced - last_valid_gap_layer->height - gap_support_object) < std::abs(gap_synced - gap_support_object)) { + gap_synced -= last_valid_gap_layer->height; + last_valid_gap_layer = last_valid_gap_layer->lower_layer; + } + if (gap_support_object > 0) { + upper_layer = last_valid_gap_layer; // layer just above the last valid gap layer + if (last_valid_gap_layer->upper_layer) + upper_layer = last_valid_gap_layer->upper_layer; + } + return upper_layer; + } +} + // Allocate one, possibly two support contact layers. // For "thick" overhangs, one support layer will be generated to support normal extrusions, the other to support the "thick" extrusions. static inline std::pair new_contact_layer( @@ -1782,9 +1739,18 @@ static inline std::pair new_cont print_z = layer.bottom_z(); height = layer.lower_layer->height; bottom_z = (layer_id == 1) ? slicing_params.object_print_z_min : layer.lower_layer->lower_layer->print_z; - } else { - print_z = layer.bottom_z() - slicing_params.gap_support_object; - height = print_config.independent_support_layer_height ? 0. : layer.lower_layer->height/*object_config.layer_height*/; // BBS: need to consider adaptive layer heights + } + else { + // BBS: need to consider adaptive layer heights + if (print_config.independent_support_layer_height) { + print_z = layer.bottom_z() - slicing_params.gap_support_object; + height = 0; + } + else { + Layer* synced_layer = sync_gap_with_object_layer(layer, slicing_params.gap_support_object, true); + print_z = synced_layer->print_z; + height = synced_layer->height; + } bottom_z = print_z - height; // Ignore this contact area if it's too low. // Don't want to print a layer below the first layer height as it may not stick well. @@ -1809,7 +1775,7 @@ static inline std::pair new_cont // Contact layer will be printed with a normal flow, but // it will support layers printed with a bridging flow. - if (object_config.thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer)) { + if (object_config.thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer) && print_config.independent_support_layer_height) { coordf_t bridging_height = 0.; for (const LayerRegion* region : layer.regions()) bridging_height += region->region().bridging_height_avg(print_config); @@ -2223,9 +2189,9 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( } // 2.3 check whether sharp tail exceed the max height - for (const auto& lower_sharp_tail_height : lower_layer_sharptails_height) { - if (lower_sharp_tail_height.first->overlaps(expoly)) { - accum_height += lower_sharp_tail_height.second; + for (size_t i = 0; i < lower_layer_sharptails_height.size();i++) { + if (lower_layer_sharptails[i].overlaps(expoly)) { + accum_height += lower_layer_sharptails_height[i]; break; } } @@ -2250,12 +2216,8 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( if (is_sharp_tail) { ExPolygons overhang = diff_ex({ expoly }, lower_layer->lslices); layer->sharp_tails.push_back(expoly); - layer->sharp_tails_height.insert({ &expoly, accum_height }); + layer->sharp_tails_height.push_back( accum_height ); append(overhangs_per_layers[layer_nr], overhang); -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - SVG svg(get_svg_filename(std::to_string(layer->print_z), "sharp_tail"), object.bounding_box()); - if (svg.is_opened()) svg.draw(overhang, "yellow"); -#endif } } @@ -2396,7 +2358,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( // Find the bottom contact layers above the top surfaces of this layer. static inline SupportGeneratorLayer* detect_bottom_contacts( const SlicingParameters &slicing_params, - const PrintObjectSupportMaterial::SupportParams &support_params, + const SupportParameters &support_params, const PrintObject &object, const Layer &layer, // Existing top contact layers, to which this newly created bottom contact layer will be snapped to guarantee a minimum layer height. @@ -2443,15 +2405,22 @@ static inline SupportGeneratorLayer* detect_bottom_contacts( // Grow top surfaces so that interface and support generation are generated // with some spacing from object - it looks we don't need the actual // top shapes so this can be done here - //FIXME calculate layer height based on the actual thickness of the layer: - // If the layer is extruded with no bridging flow, support just the normal extrusions. - layer_new.height = slicing_params.soluble_interface || !object.print()->config().independent_support_layer_height ? - // Align the interface layer with the object's layer height. - layer.upper_layer->height : - // Place a bridge flow interface layer or the normal flow interface layer over the top surface. - support_params.support_material_bottom_interface_flow.height(); - layer_new.print_z = slicing_params.soluble_interface ? layer.upper_layer->print_z : - layer.print_z + layer_new.height + slicing_params.gap_object_support; + Layer* upper_layer = layer.upper_layer; + if (object.print()->config().independent_support_layer_height) { + // If the layer is extruded with no bridging flow, support just the normal extrusions. + layer_new.height = slicing_params.soluble_interface ? + // Align the interface layer with the object's layer height. + upper_layer->height : + // Place a bridge flow interface layer or the normal flow interface layer over the top surface. + support_params.support_material_bottom_interface_flow.height(); + layer_new.print_z = slicing_params.soluble_interface ? upper_layer->print_z : + layer.print_z + layer_new.height + slicing_params.gap_object_support; + } + else { + upper_layer = sync_gap_with_object_layer(layer, slicing_params.gap_object_support, false); + layer_new.height = upper_layer->height; + layer_new.print_z = upper_layer->print_z; + } layer_new.bottom_z = layer.print_z; layer_new.idx_object_layer_below = layer_id; layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges; @@ -2959,36 +2928,6 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp for (size_t i = 0; i < top_contacts.size(); ++i) assert(top_contacts[i]->height > 0.); #endif /* _DEBUG */ - -#if 0 // #ifdef SLIC3R_DEBUG - // check bounds - std::ofstream out; - out.open("./SVG/ns_bounds.txt"); - if (out.is_open()) { - if (!top_contacts.empty()) { - out << "### Top Contacts ###" << std::endl; - for (auto& t : top_contacts) { - out << t->print_z << std::endl; - } - } - if (!bottom_contacts.empty()) { - out << "### Bottome Contacts ###" << std::endl; - for (auto& b : bottom_contacts) { - out << b->print_z << std::endl; - } - } - if (!intermediate_layers.empty()) { - out << "### Intermediate Layers ###" << std::endl; - for (auto& i : intermediate_layers) { - out << i->print_z << std::endl; - } - } - out << "### Slice Layers ###" << std::endl; - for (size_t j = 0; j < object.layers().size(); ++j) { - out << object.layers()[j]->print_z << std::endl; - } - } -#endif /* SLIC3R_DEBUG */ return intermediate_layers; } @@ -3249,1482 +3188,6 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; } -SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base( - const PrintObject &object, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers, - const SupportGeneratorLayersPtr &base_layers, - SupportGeneratorLayerStorage &layer_storage) const -{ - // If there is brim to be generated, calculate the trimming regions. - Polygons brim; - if (object.has_brim()) { - // Calculate the area covered by the brim. - const BrimType brim_type = object.config().brim_type; - const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; - const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; - // BBS: the pattern of raft and brim are the same, thus the brim can be serpated by support raft. - const auto brim_object_gap = scaled(object.config().brim_object_gap.value); - //const auto brim_object_gap = scaled(object.config().brim_object_gap.value + object.config().brim_width.value); - for (const ExPolygon &ex : object.layers().front()->lslices) { - if (brim_outer && brim_inner) - polygons_append(brim, offset(ex, brim_object_gap)); - else { - if (brim_outer) - polygons_append(brim, offset(ex.contour, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1)))); - else - brim.emplace_back(ex.contour); - if (brim_inner) { - Polygons holes = ex.holes; - polygons_reverse(holes); - holes = shrink(holes, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1))); - polygons_reverse(holes); - polygons_append(brim, std::move(holes)); - } else - polygons_append(brim, ex.holes); - } - } - brim = union_(brim); - } - - // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. - const float inflate_factor_fine = float(scale_((m_slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); - const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); - SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); - SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); - SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); - SupportGeneratorLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); - if (contacts != nullptr && contacts->print_z > std::max(m_slicing_params.first_print_layer_height, m_slicing_params.raft_contact_top_z) + EPSILON) - // This is not the raft contact layer. - contacts = nullptr; - if (interfaces != nullptr && interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft column base layer. - interfaces = nullptr; - if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft column base layer. - base_interfaces = nullptr; - if (columns_base != nullptr && columns_base->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft interface layer. - columns_base = nullptr; - - Polygons interface_polygons; - if (contacts != nullptr && ! contacts->polygons.empty()) - polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (interfaces != nullptr && ! interfaces->polygons.empty()) - polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) - polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - - // Output vector. - SupportGeneratorLayersPtr raft_layers; - - if (m_slicing_params.raft_layers() > 1) { - Polygons base; - Polygons columns; - if (columns_base != nullptr) { - base = columns_base->polygons; - columns = base; - if (! interface_polygons.empty()) - // Trim the 1st layer columns with the inflated interface polygons. - columns = diff(columns, interface_polygons); - } - if (! interface_polygons.empty()) { - // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. - base = union_(base, interface_polygons); - } - // Do not add the raft contact layer, only add the raft layers below the contact layer. - // Insert the 1st layer. - { - SupportGeneratorLayer &new_layer = layer_storage.allocate((m_slicing_params.base_raft_layers > 0) ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface); - raft_layers.push_back(&new_layer); - new_layer.print_z = m_slicing_params.first_print_layer_height; - new_layer.height = m_slicing_params.first_print_layer_height; - new_layer.bottom_z = 0.; - new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base; - } - // Insert the base layers. - for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) { - coordf_t print_z = raft_layers.back()->print_z; - SupportGeneratorLayer &new_layer = layer_storage.allocate(SupporLayerType::RaftBase); - raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + m_slicing_params.base_raft_layer_height; - new_layer.height = m_slicing_params.base_raft_layer_height; - new_layer.bottom_z = print_z; - new_layer.polygons = base; - } - // Insert the interface layers. - for (size_t i = 1; i < m_slicing_params.interface_raft_layers; ++ i) { - coordf_t print_z = raft_layers.back()->print_z; - SupportGeneratorLayer &new_layer = layer_storage.allocate(SupporLayerType::RaftInterface); - raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + m_slicing_params.interface_raft_layer_height; - new_layer.height = m_slicing_params.interface_raft_layer_height; - new_layer.bottom_z = print_z; - new_layer.polygons = interface_polygons; - //FIXME misusing contact_polygons for support columns. - new_layer.contact_polygons = std::make_unique(columns); - } - } else { - if (columns_base != nullptr) { - // Expand the bases of the support columns in the 1st layer. - Polygons &raft = columns_base->polygons; - Polygons trimming; - // BBS: if first layer of support is intersected with object island, it must have the same function as brim unless in nobrim mode. - if (object.has_brim()) - trimming = offset(m_object->layers().front()->lslices, (float)scale_(object.config().brim_object_gap.value), SUPPORT_SURFACES_OFFSET_PARAMETERS); - else - trimming = offset(m_object->layers().front()->lslices, (float)scale_(m_support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (inflate_factor_1st_layer > SCALED_EPSILON) { - // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. - auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width()))); - float step = inflate_factor_1st_layer / nsteps; - for (int i = 0; i < nsteps; ++ i) - raft = diff(expand(raft, step), trimming); - } else - raft = diff(raft, trimming); - if (! interface_polygons.empty()) - columns_base->polygons = diff(columns_base->polygons, interface_polygons); - } - if (! brim.empty()) { - if (columns_base) - columns_base->polygons = diff(columns_base->polygons, brim); - if (contacts) - contacts->polygons = diff(contacts->polygons, brim); - if (interfaces) - interfaces->polygons = diff(interfaces->polygons, brim); - if (base_interfaces) - base_interfaces->polygons = diff(base_interfaces->polygons, brim); - } - } - - return raft_layers; -} - -// Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers. -std::pair PrintObjectSupportMaterial::generate_interface_layers( - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage) const -{ -// my $area_threshold = $self->interface_flow->scaled_spacing ** 2; - - std::pair base_and_interface_layers; - SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first; - SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second; - - // distinguish between interface and base interface layers - // Contact layer is considered an interface layer, therefore run the following block only if support_interface_top_layers > 1. - // Contact layer needs a base_interface layer, therefore run the following block if support_interface_top_layers > 0, has soluble support and extruders are different. - bool soluble_interface_non_soluble_base = - // Zero z-gap between the overhangs and the support interface. - m_slicing_params.soluble_interface && - // Interface extruder soluble. - m_object_config->support_interface_filament.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_interface_filament.value - 1) && - // Base extruder: Either "print with active extruder" not soluble. - (m_object_config->support_filament.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_filament.value - 1)); - bool snug_supports = m_object_config->support_style.value == smsSnug; - // BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion - bool differnt_support_interface_filament = m_object_config->support_interface_filament.value != m_object_config->support_filament.value; - int num_base_interface_layers_top = differnt_support_interface_filament ? 1 : 0; - int num_base_interface_layers_bottom = differnt_support_interface_filament ? 1 : 0; - int num_interface_layers_top = m_object_config->support_interface_top_layers + num_base_interface_layers_top; - int num_interface_layers_bottom = m_object_config->support_interface_bottom_layers + num_base_interface_layers_bottom; - if (num_interface_layers_bottom < 0) - num_interface_layers_bottom = num_interface_layers_top; - - if (! intermediate_layers.empty() && (num_interface_layers_top > 1 || num_interface_layers_bottom > 1)) { - // For all intermediate layers, collect top contact surfaces, which are not further than support_interface_top_layers. - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; - // Since the intermediate layer index starts at zero the number of interface layer needs to be reduced by 1. - -- num_interface_layers_top; - -- num_interface_layers_bottom; - int num_interface_layers_only_top = num_interface_layers_top - num_base_interface_layers_top; - int num_interface_layers_only_bottom = num_interface_layers_bottom - num_base_interface_layers_bottom; - interface_layers.assign(intermediate_layers.size(), nullptr); - if (num_base_interface_layers_top || num_base_interface_layers_bottom) - base_interface_layers.assign(intermediate_layers.size(), nullptr); - auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5; - auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density; - auto closing_distance = smoothing_distance; // scaled(m_object_config->support_closing_radius.value); - // Insert a new layer into base_interface_layers, if intersection with base exists. - auto insert_layer = [&layer_storage, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( - SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { - assert(! bottom.empty() || ! top.empty()); - // Merge top into bottom, unite them with a safety offset. - append(bottom, std::move(top)); - // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners). - bottom = intersection( - snug_supports ? - smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : - union_safety_offset(std::move(bottom)), - intermediate_layer.polygons); - if (! bottom.empty()) { - //FIXME Remove non-printable tiny islands, let them be printed using the base support. - //bottom = opening(std::move(bottom), minimum_island_radius); - if (! bottom.empty()) { - SupportGeneratorLayer &layer_new = layer_storage.allocate(type); - layer_new.polygons = std::move(bottom); - layer_new.print_z = intermediate_layer.print_z; - layer_new.bottom_z = intermediate_layer.bottom_z; - layer_new.height = intermediate_layer.height; - layer_new.bridging = intermediate_layer.bridging; - // Subtract the interface from the base regions. - intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); - if (subtract) - // Trim the base interface layer with the interface layer. - layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); - //FIXME filter layer_new.polygons islands by a minimum area? - // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; - return &layer_new; - } - } - return nullptr; - }; - tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), - [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, - num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom, - snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { - // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below - // this intermediate layer. - // Index of the first top contact layer intersecting the current intermediate layer. - auto idx_top_contact_first = -1; - // Index of the first bottom contact layer intersecting the current intermediate layer. - auto idx_bottom_contact_first = -1; - auto num_intermediate = int(intermediate_layers.size()); - for (int idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) { - SupportGeneratorLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer]; - Polygons polygons_top_contact_projected_interface; - Polygons polygons_top_contact_projected_base; - Polygons polygons_bottom_contact_projected_interface; - Polygons polygons_bottom_contact_projected_base; - if (num_interface_layers_top > 0) { - // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces - coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_top - 1)]->print_z; - coordf_t top_inteface_z = std::numeric_limits::max(); - if (num_base_interface_layers_top > 0) - // Some top base interface layers will be generated. - top_inteface_z = num_interface_layers_only_top == 0 ? - // Only base interface layers to generate. - - std::numeric_limits::max() : - intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only_top - 1)]->print_z; - // Move idx_top_contact_first up until above the current print_z. - idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON - // Collect the top contact areas above this intermediate layer, below top_z. - for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { - const SupportGeneratorLayer &top_contact_layer = *top_contacts[idx_top_contact]; - //FIXME maybe this adds one interface layer in excess? - if (top_contact_layer.bottom_z - EPSILON > top_z) - break; - polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, - // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers. - // For grid supports, merging of support regions will be performed by the projection into grid. - snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); - } - } - if (num_interface_layers_bottom > 0) { - // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces - coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_bottom + 1)]->bottom_z; - coordf_t bottom_interface_z = - std::numeric_limits::max(); - if (num_base_interface_layers_bottom > 0) - // Some bottom base interface layers will be generated. - bottom_interface_z = num_interface_layers_only_bottom == 0 ? - // Only base interface layers to generate. - std::numeric_limits::max() : - intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only_bottom)]->bottom_z; - // Move idx_bottom_contact_first up until touching bottom_z. - idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const SupportGeneratorLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); - // Collect the top contact areas above this intermediate layer, below top_z. - for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { - const SupportGeneratorLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; - if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) - break; - polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); - } - } - SupportGeneratorLayer *interface_layer = nullptr; - if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty()) { - interface_layer = insert_layer( - intermediate_layer, polygons_bottom_contact_projected_interface, std::move(polygons_top_contact_projected_interface), nullptr, - polygons_top_contact_projected_interface.empty() ? SupporLayerType::BottomInterface : SupporLayerType::TopInterface); - interface_layers[idx_intermediate_layer] = interface_layer; - } - if (! polygons_bottom_contact_projected_base.empty() || ! polygons_top_contact_projected_base.empty()) - base_interface_layers[idx_intermediate_layer] = insert_layer( - intermediate_layer, polygons_bottom_contact_projected_base, std::move(polygons_top_contact_projected_base), - interface_layer ? &interface_layer->polygons : nullptr, SupporLayerType::Base); - } - }); - - // Compress contact_out, remove the nullptr items. - remove_nulls(interface_layers); - remove_nulls(base_interface_layers); - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end"; - } - - return base_and_interface_layers; -} - -static inline void fill_expolygon_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygon &&expolygon, - Fill *filler, - const FillParams &fill_params, - ExtrusionRole role, - const Flow &flow) -{ - Surface surface(stInternal, std::move(expolygon)); - Polylines polylines; - try { - polylines = filler->fill_surface(&surface, fill_params); - } catch (InfillFailedException &) { - } - extrusion_entities_append_paths( - dst, - std::move(polylines), - role, - flow.mm3_per_mm(), flow.width(), flow.height()); -} - -static inline void fill_expolygons_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, - Fill *filler, - const FillParams &fill_params, - ExtrusionRole role, - const Flow &flow) -{ - for (ExPolygon &expoly : expolygons) - fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, role, flow); -} - -static inline void fill_expolygons_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, - Fill *filler, - float density, - ExtrusionRole role, - const Flow &flow) -{ - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, role, flow); -} - -static inline void fill_expolygons_with_sheath_generate_paths( - ExtrusionEntitiesPtr &dst, - const Polygons &polygons, - Fill *filler, - float density, - ExtrusionRole role, - const Flow &flow, - bool with_sheath, - bool no_sort) -{ - if (polygons.empty()) - return; - - if (! with_sheath) { - fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); - return; - } - - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - - double spacing = flow.scaled_spacing(); - // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. - double clip_length = spacing * 0.15; - - for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { - // Don't reorder the skirt and its infills. - std::unique_ptr eec; - if (no_sort) { - eec = std::make_unique(); - eec->no_sort = true; - } - ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; - // Draw the perimeters. - Polylines polylines; - polylines.reserve(expoly.holes.size() + 1); - for (size_t i = 0; i <= expoly.holes.size(); ++ i) { - Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); - pl.points.emplace_back(pl.points.front()); - pl.clip_end(clip_length); - polylines.emplace_back(std::move(pl)); - } - extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); - // Fill in the rest. - fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, role, flow); - if (no_sort && ! eec->empty()) - dst.emplace_back(eec.release()); - } -} - -// Support layers, partially processed. -struct MyLayerExtruded -{ - MyLayerExtruded& operator=(MyLayerExtruded &&rhs) { - this->layer = rhs.layer; - this->extrusions = std::move(rhs.extrusions); - m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); - rhs.layer = nullptr; - return *this; - } - - bool empty() const { - return layer == nullptr || layer->polygons.empty(); - } - - void set_polygons_to_extrude(Polygons &&polygons) { - if (m_polygons_to_extrude == nullptr) - m_polygons_to_extrude = std::make_unique(std::move(polygons)); - else - *m_polygons_to_extrude = std::move(polygons); - } - Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } - const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } - - bool could_merge(const MyLayerExtruded &other) const { - return ! this->empty() && ! other.empty() && - std::abs(this->layer->height - other.layer->height) < EPSILON && - this->layer->bridging == other.layer->bridging; - } - - // Merge regions, perform boolean union over the merged polygons. - void merge(MyLayerExtruded &&other) { - assert(this->could_merge(other)); - // 1) Merge the rest polygons to extrude, if there are any. - if (other.m_polygons_to_extrude != nullptr) { - if (m_polygons_to_extrude == nullptr) { - // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). - assert(this->extrusions.empty()); - m_polygons_to_extrude = std::make_unique(this->layer->polygons); - } - Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); - *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); - other.m_polygons_to_extrude.reset(); - } else if (m_polygons_to_extrude != nullptr) { - assert(other.m_polygons_to_extrude == nullptr); - // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). - assert(other.extrusions.empty()); - Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons); - *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); - } - // 2) Merge the extrusions. - this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); - other.extrusions.clear(); - // 3) Merge the infill polygons. - Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); - this->layer->polygons = union_safety_offset(this->layer->polygons); - other.layer->polygons.clear(); - } - - void polygons_append(Polygons &dst) const { - if (layer != NULL && ! layer->polygons.empty()) - Slic3r::polygons_append(dst, layer->polygons); - } - - // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). - SupportGeneratorLayer *layer { nullptr }; - // Collect extrusions. They will be exported sorted by the bottom height. - ExtrusionEntitiesPtr extrusions; - -private: - // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. - // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. - std::unique_ptr m_polygons_to_extrude; -}; - -typedef std::vector MyLayerExtrudedPtrs; - -struct LoopInterfaceProcessor -{ - LoopInterfaceProcessor(coordf_t circle_r) : - n_contact_loops(0), - circle_radius(circle_r), - circle_distance(circle_r * 3.) - { - // Shape of the top contact area. - circle.points.reserve(6); - for (size_t i = 0; i < 6; ++ i) { - double angle = double(i) * M_PI / 3.; - circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); - } - } - - // Generate loop contacts at the top_contact_layer, - // trim the top_contact_layer->polygons with the areas covered by the loops. - void generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; - - int n_contact_loops; - coordf_t circle_radius; - coordf_t circle_distance; - Polygon circle; -}; - -void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const -{ - if (n_contact_loops == 0 || top_contact_layer.empty()) - return; - - Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); - - Polygons overhang_polygons; - if (top_contact_layer.layer->overhang_polygons != nullptr) - overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons); - - // Generate the outermost loop. - // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) - ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width()); - - // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation. - coord_t circle_grid_resolution = 1; - coord_t circle_grid_powerof2 = 0; - { - // epsilon to account for rounding errors - coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.); - while (circle_grid_resolution < circle_grid_resolution_non_powerof2) { - circle_grid_resolution <<= 1; - ++ circle_grid_powerof2; - } - } - - struct PointAccessor { - const Point* operator()(const Point &pt) const { return &pt; } - }; - typedef ClosestPointInRadiusLookup ClosestPointLookupType; - - Polygons loops0; - { - // find centerline of the external loop of the contours - // Only consider the loops facing the overhang. - Polygons external_loops; - // Holes in the external loops. - Polygons circles; - Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width()); - for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) { - // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers. - ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON)); - Points circle_centers; - Point center_last; - // For each contour of the expolygon, start with the outer contour, continue with the holes. - for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) { - Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; - const Point *seg_current_pt = nullptr; - coordf_t seg_current_t = 0.; - if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { - // The contour is below the overhang at least to some extent. - //FIXME ideally one would place the circles below the overhang only. - // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. - if (circle_centers.empty()) { - // Place the first circle. - seg_current_pt = &contour.points.front(); - seg_current_t = 0.; - center_last = *seg_current_pt; - circle_centers_lookup.insert(center_last); - circle_centers.push_back(center_last); - } - for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) { - // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour? - const Point &p1 = *(it-1); - const Point &p2 = *it; - // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. - const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1))); - const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1))); - coordf_t a = v_seg.squaredNorm(); - coordf_t b = 2. * v_seg.dot(v_cntr); - coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance; - coordf_t disc = b * b - 4. * a * c; - if (disc > 0.) { - // The circle intersects a ray. Avoid the parts of the segment inside the circle. - coordf_t t1 = (-b - sqrt(disc)) / (2. * a); - coordf_t t2 = (-b + sqrt(disc)) / (2. * a); - coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.; - // Take the lowest t in , excluding . - coordf_t t; - if (t0 <= t1) - t = t0; - else if (t2 <= 1.) - t = t2; - else { - // Try the following segment. - seg_current_pt = nullptr; - continue; - } - seg_current_pt = &p1; - seg_current_t = t; - center_last = Point(p1(0) + coord_t(v_seg(0) * t), p1(1) + coord_t(v_seg(1) * t)); - // It has been verified that the new point is far enough from center_last. - // Ensure, that it is far enough from all the centers. - std::pair circle_closest = circle_centers_lookup.find(center_last); - if (circle_closest.first != nullptr) { - -- it; - continue; - } - } else { - // All of the segment is outside the circle. Take the first point. - seg_current_pt = &p1; - seg_current_t = 0.; - center_last = p1; - } - // Place the first circle. - circle_centers_lookup.insert(center_last); - circle_centers.push_back(center_last); - } - external_loops.push_back(std::move(contour)); - for (const Point ¢er : circle_centers) { - circles.push_back(circle); - circles.back().translate(center); - } - } - } - } - // Apply a pattern to the external loops. - loops0 = diff(external_loops, circles); - } - - Polylines loop_lines; - { - // make more loops - Polygons loop_polygons = loops0; - for (int i = 1; i < n_contact_loops; ++ i) - polygons_append(loop_polygons, - opening( - loops0, - i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), - 0.5f * flow.scaled_spacing())); - // Clip such loops to the side oriented towards the object. - // Collect split points, so they will be recognized after the clipping. - // At the split points the clipped pieces will be stitched back together. - loop_lines.reserve(loop_polygons.size()); - std::unordered_map map_split_points; - for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) { - assert(map_split_points.find(it->first_point()) == map_split_points.end()); - map_split_points[it->first_point()] = -1; - loop_lines.push_back(it->split_at_first_point()); - } - loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); - // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. - // Try to connect them. - for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { - Polyline &polyline = loop_lines[i_line]; - auto it = map_split_points.find(polyline.first_point()); - if (it != map_split_points.end()) { - // This is a stitching point. - // If this assert triggers, multiple source polygons likely intersected at this point. - assert(it->second != -2); - if (it->second < 0) { - // First occurence. - it->second = i_line; - } else { - // Second occurence. Join the lines. - Polyline &polyline_1st = loop_lines[it->second]; - assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); - if (polyline_1st.first_point() == it->first) - polyline_1st.reverse(); - polyline_1st.append(std::move(polyline)); - it->second = -2; - } - continue; - } - it = map_split_points.find(polyline.last_point()); - if (it != map_split_points.end()) { - // This is a stitching point. - // If this assert triggers, multiple source polygons likely intersected at this point. - assert(it->second != -2); - if (it->second < 0) { - // First occurence. - it->second = i_line; - } else { - // Second occurence. Join the lines. - Polyline &polyline_1st = loop_lines[it->second]; - assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); - if (polyline_1st.first_point() == it->first) - polyline_1st.reverse(); - polyline.reverse(); - polyline_1st.append(std::move(polyline)); - it->second = -2; - } - } - } - // Remove empty lines. - remove_degenerate(loop_lines); - } - - // add the contact infill area to the interface area - // note that growing loops by $circle_radius ensures no tiny - // extrusions are left inside the circles; however it creates - // a very large gap between loops and contact_infill_polygons, so maybe another - // solution should be found to achieve both goals - // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for - // "modulate by layer thickness". - top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1)))); - - // Transform loops into ExtrusionPath objects. - extrusion_entities_append_paths( - top_contact_layer.extrusions, - std::move(loop_lines), - erSupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); -} - -#ifdef SLIC3R_DEBUG -static std::string dbg_index_to_color(int idx) -{ - if (idx < 0) - return "yellow"; - idx = idx % 3; - switch (idx) { - case 0: return "red"; - case 1: return "green"; - default: return "blue"; - } -} -#endif /* SLIC3R_DEBUG */ - -// When extruding a bottom interface layer over an object, the bottom interface layer is extruded in a thin air, therefore -// it is being extruded with a bridging flow to not shrink excessively (the die swell effect). -// Tiny extrusions are better avoided and it is always better to anchor the thread to an existing support structure if possible. -// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, -// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers -// to stick too firmly to the object. -// -// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer -// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z. -void modulate_extrusion_by_overlapping_layers( - // Extrusions generated for this_layer. - ExtrusionEntitiesPtr &extrusions_in_out, - const SupportGeneratorLayer &this_layer, - // Multiple layers overlapping with this_layer, sorted bottom up. - const SupportGeneratorLayersPtr &overlapping_layers) -{ - size_t n_overlapping_layers = overlapping_layers.size(); - if (n_overlapping_layers == 0 || extrusions_in_out.empty()) - // The extrusions do not overlap with any other extrusion. - return; - - // Get the initial extrusion parameters. - ExtrusionPath *extrusion_path_template = dynamic_cast(extrusions_in_out.front()); - assert(extrusion_path_template != nullptr); - ExtrusionRole extrusion_role = extrusion_path_template->role(); - float extrusion_width = extrusion_path_template->width; - - struct ExtrusionPathFragment - { - ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; - ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {}; - - Polylines polylines; - double mm3_per_mm; - float width; - float height; - }; - - // Split the extrusions by the overlapping layers, reduce their extrusion rate. - // The last path_fragment is from this_layer. - std::vector path_fragments( - n_overlapping_layers + 1, - ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); - // Don't use it, it will be released. - extrusion_path_template = nullptr; - -#ifdef SLIC3R_DEBUG - static int iRun = 0; - ++ iRun; - BoundingBox bbox; - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - bbox.merge(get_extents(overlapping_layer.polygons)); - } - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); - assert(path != nullptr); - bbox.merge(get_extents(path->polyline)); - } - SVG svg(debug_out_path("support-fragments-%d-%lf.svg", iRun, this_layer.print_z).c_str(), bbox); - const float transparency = 0.5f; - // Filled polygons for the overlapping regions. - svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency); - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency); - } - // Contours of the overlapping regions. - svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2)); - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1)); - } - // Fill extrusion, the source. - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); - std::string color_name; - switch ((it - extrusions_in_out.begin()) % 9) { - case 0: color_name = "magenta"; break; - case 1: color_name = "deepskyblue"; break; - case 2: color_name = "coral"; break; - case 3: color_name = "goldenrod"; break; - case 4: color_name = "orange"; break; - case 5: color_name = "olivedrab"; break; - case 6: color_name = "blueviolet"; break; - case 7: color_name = "brown"; break; - default: color_name = "orchid"; break; - } - svg.draw(path->polyline, color_name, scale_(0.2)); - } -#endif /* SLIC3R_DEBUG */ - - // End points of the original paths. - std::vector> path_ends; - // Collect the paths of this_layer. - { - Polylines &polylines = path_fragments.back().polylines; - for (ExtrusionEntity *ee : extrusions_in_out) { - ExtrusionPath *path = dynamic_cast(ee); - assert(path != nullptr); - polylines.emplace_back(Polyline(std::move(path->polyline))); - path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); - } - } - // Destroy the original extrusion paths, their polylines were moved to path_fragments already. - // This will be the destination for the new paths. - extrusions_in_out.clear(); - - // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z. - // Trim by the highest overlapping layer first. - for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) { - const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; - Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); - frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming); - path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming); - // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). - assert(this_layer.print_z > overlapping_layer.print_z); - frag.height = float(this_layer.print_z - overlapping_layer.print_z); - frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); -#ifdef SLIC3R_DEBUG - svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); -#endif /* SLIC3R_DEBUG */ - } - -#ifdef SLIC3R_DEBUG - svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1)); - svg.Close(); -#endif /* SLIC3R_DEBUG */ - - // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments. - // Create a single ExtrusionPath or ExtrusionEntityCollection per source ExtrusionPath. - // Map of fragment start/end points to a pair of - // Because a non-exact matching is used for the end points, a multi-map is used. - // As the clipper library may reverse the order of some clipped paths, store both ends into the map. - struct ExtrusionPathFragmentEnd - { - ExtrusionPathFragmentEnd(size_t alayer_idx, size_t apolyline_idx, bool ais_start) : - layer_idx(alayer_idx), polyline_idx(apolyline_idx), is_start(ais_start) {} - size_t layer_idx; - size_t polyline_idx; - bool is_start; - }; - class ExtrusionPathFragmentEndPointAccessor { - public: - ExtrusionPathFragmentEndPointAccessor(const std::vector &path_fragments) : m_path_fragments(path_fragments) {} - // Return an end point of a fragment, or nullptr if the fragment has been consumed already. - const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const { - const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; - return polyline.points.empty() ? nullptr : - (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); - } - private: - ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) { - return *this; - } - - const std::vector &m_path_fragments; - }; - const coord_t search_radius = 7; - ClosestPointInRadiusLookup map_fragment_starts( - search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments)); - for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) { - const Polylines &polylines = path_fragments[i_overlapping_layer].polylines; - for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) { - // Map a starting point of a polyline to a pair of - if (polylines[i_polyline].points.size() >= 2) { - map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true)); - map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false)); - } - } - } - - // For each source path: - for (size_t i_path = 0; i_path < path_ends.size(); ++ i_path) { - const Point &pt_start = path_ends[i_path].first; - const Point &pt_end = path_ends[i_path].second; - Point pt_current = pt_start; - // Find a chain of fragments with the original / reduced print height. - ExtrusionMultiPath multipath; - for (;;) { - // Find a closest end point to pt_current. - std::pair end_and_dist2 = map_fragment_starts.find(pt_current); - // There may be a bug in Clipper flipping the order of two last points in a fragment? - // assert(end_and_dist2.first != nullptr); - assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius); - if (end_and_dist2.first == nullptr) { - // New fragment connecting to pt_current was not found. - // Verify that the last point found is close to the original end point of the unfragmented path. - //const double d2 = (pt_end - pt_current).cast.squaredNorm(); - //assert(d2 < coordf_t(search_radius * search_radius)); - // End of the path. - break; - } - const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first; - // Fragment to consume. - ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx]; - Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx]; - // Path to append the fragment to. - ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); - if (path != nullptr) { - // Verify whether the path is compatible with the current fragment. - assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); - if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { - path = nullptr; - } - // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. - } - if (path == nullptr) { - // Allocate a new path. - multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height)); - path = &multipath.paths.back(); - } - // The Clipper library may flip the order of the clipped polylines arbitrarily. - // Reverse the source polyline, if connecting to the end. - if (! fragment_end_min.is_start) - frag_polyline.reverse(); - // Enforce exact overlap of the end points of successive fragments. - assert(frag_polyline.points.front() == pt_current); - frag_polyline.points.front() = pt_current; - // Don't repeat the first point. - if (! path->polyline.points.empty()) - path->polyline.points.pop_back(); - // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time. - path->polyline.append(std::move(frag_polyline)); - frag_polyline.points.clear(); - pt_current = path->polyline.points.back(); - if (pt_current == pt_end) { - // End of the path. - break; - } - } - if (!multipath.paths.empty()) { - if (multipath.paths.size() == 1) { - // This path was not fragmented. - extrusions_in_out.push_back(new ExtrusionPath(std::move(multipath.paths.front()))); - } else { - // This path was fragmented. Copy the collection as a whole object, so the order inside the collection will not be changed - // during the chaining of extrusions_in_out. - extrusions_in_out.push_back(new ExtrusionMultiPath(std::move(multipath))); - } - } - } - // If there are any non-consumed fragments, add them separately. - //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. - for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) - extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); -} - -void PrintObjectSupportMaterial::generate_toolpaths( - SupportLayerPtrs &support_layers, - const SupportGeneratorLayersPtr &raft_layers, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &intermediate_layers, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers) const -{ - // loop_interface_processor with a given circle radius. - LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width()); - loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; - - std::vector angles { m_support_params.base_angle }; - if (m_object_config->support_base_pattern == smpRectilinearGrid) - angles.push_back(m_support_params.interface_angle); - - BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); - -// const coordf_t link_max_length_factor = 3.; - const coordf_t link_max_length_factor = 0.; - - float raft_angle_1st_layer = 0.f; - float raft_angle_base = 0.f; - float raft_angle_interface = 0.f; - if (m_slicing_params.base_raft_layers > 1) { - // There are all raft layer types (1st layer, base, interface & contact layers) available. - raft_angle_1st_layer = m_support_params.interface_angle; - raft_angle_base = m_support_params.base_angle; - raft_angle_interface = m_support_params.interface_angle; - } else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) { - // 1st layer, interface & contact layers available. - raft_angle_1st_layer = m_support_params.base_angle; - if (this->has_support()) - // Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer. - raft_angle_1st_layer += 0.7854f; - raft_angle_interface = m_support_params.interface_angle; - } else if (m_slicing_params.interface_raft_layers == 1) { - // Only the contact raft layer is non-empty, which will be printed as the 1st layer. - assert(m_slicing_params.base_raft_layers == 0); - assert(m_slicing_params.interface_raft_layers == 1); - assert(m_slicing_params.raft_layers() == 1 && raft_layers.size() == 0); - } else { - // No raft. - assert(m_slicing_params.base_raft_layers == 0); - assert(m_slicing_params.interface_raft_layers == 0); - assert(m_slicing_params.raft_layers() == 0 && raft_layers.size() == 0); - } - - // Insert the raft base layers. - size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); - tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), - [this, &support_layers, &raft_layers, - &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] - (const tbb::blocked_range& range) { - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) - { - assert(support_layer_id < raft_layers.size()); - SupportLayer &support_layer = *support_layers[support_layer_id]; - assert(support_layer.support_fills.entities.empty()); - SupportGeneratorLayer &raft_layer = *raft_layers[support_layer_id]; - - std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.interface_fill_pattern)); - std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); - filler_interface->set_bounding_box(bbox_object); - filler_support->set_bounding_box(bbox_object); - - // Print the support base below the support columns, or the support base for the support columns plus the contacts. - if (support_layer_id > 0) { - const Polygons &to_infill_polygons = (support_layer_id < m_slicing_params.base_raft_layers) ? - raft_layer.polygons : - //FIXME misusing contact_polygons for support columns. - ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); - if (! to_infill_polygons.empty()) { - assert(! raft_layer.bridging); - Flow flow(float(m_support_params.support_material_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); - Fill * filler = filler_support.get(); - filler->angle = raft_angle_base; - filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); - fill_expolygons_with_sheath_generate_paths( - // Destination - support_layer.support_fills.entities, - // Regions to fill - to_infill_polygons, - // Filler and its parameters - filler, float(m_support_params.support_density), - // Extrusion parameters - erSupportMaterial, flow, - m_support_params.with_sheath, false); - } - } - - Fill *filler = filler_interface.get(); - Flow flow = m_support_params.first_layer_flow; - float density = 0.f; - if (support_layer_id == 0) { - // Base flange. - filler->angle = raft_angle_1st_layer; - filler->spacing = m_support_params.first_layer_flow.spacing(); - density = float(m_object_config->raft_first_layer_density.value * 0.01); - } else if (support_layer_id >= m_slicing_params.base_raft_layers) { - filler->angle = raft_angle_interface; - // We don't use $base_flow->spacing because we need a constant spacing - // value that guarantees that all layers are correctly aligned. - filler->spacing = m_support_params.support_material_flow.spacing(); - assert(! raft_layer.bridging); - flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); - density = float(m_support_params.interface_density); - } else - continue; - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - fill_expolygons_with_sheath_generate_paths( - // Destination - support_layer.support_fills.entities, - // Regions to fill - raft_layer.polygons, - // Filler and its parameters - filler, density, - // Extrusion parameters - (support_layer_id < m_slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow, - // sheath at first layer - support_layer_id == 0, support_layer_id == 0); - } - }); - - struct LayerCacheItem { - LayerCacheItem(MyLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} - MyLayerExtruded *layer_extruded; - std::vector overlapping; - }; - struct LayerCache { - MyLayerExtruded bottom_contact_layer; - MyLayerExtruded top_contact_layer; - MyLayerExtruded base_layer; - MyLayerExtruded interface_layer; - MyLayerExtruded base_interface_layer; - boost::container::static_vector nonempty; - - void add_nonempty_and_sort() { - for (MyLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) - if (! item->empty()) - this->nonempty.emplace_back(item); - // Sort the layers with the same print_z coordinate by their heights, thickest first. - std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); - } - }; - std::vector layer_caches(support_layers.size()); - - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [this, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, - &bbox_object, &angles, link_max_length_factor] - (const tbb::blocked_range& range) { - // Indices of the 1st layer in their respective container at the support layer height. - size_t idx_layer_bottom_contact = size_t(-1); - size_t idx_layer_top_contact = size_t(-1); - size_t idx_layer_intermediate = size_t(-1); - size_t idx_layer_interface = size_t(-1); - size_t idx_layer_base_interface = size_t(-1); - // BBS - const auto fill_type_first_layer = ipConcentric; - auto filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.contact_fill_pattern)); - // Filler for the 1st layer interface, if different from filler_interface. - auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && m_support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); - // Pointer to the 1st layer interface filler. - auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); - // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). - auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : - Fill::new_from_type(m_support_params.interface_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase)); - auto filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); - filler_interface->set_bounding_box(bbox_object); - if (filler_first_layer_ptr) - filler_first_layer_ptr->set_bounding_box(bbox_object); - if (filler_base_interface) - filler_base_interface->set_bounding_box(bbox_object); - filler_support->set_bounding_box(bbox_object); - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) - { - SupportLayer &support_layer = *support_layers[support_layer_id]; - LayerCache &layer_cache = layer_caches[support_layer_id]; - float interface_angle_delta = m_object_config->support_style.value == smsSnug ? - (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : - 0; - - // Find polygons with the same print_z. - MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; - MyLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; - MyLayerExtruded &base_layer = layer_cache.base_layer; - MyLayerExtruded &interface_layer = layer_cache.interface_layer; - MyLayerExtruded &base_interface_layer = layer_cache.base_interface_layer; - // Increment the layer indices to find a layer at support_layer.print_z. - { - auto fun = [&support_layer](const SupportGeneratorLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; - idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun); - idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun); - idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun); - idx_layer_interface = idx_higher_or_equal(interface_layers, idx_layer_interface, fun); - idx_layer_base_interface = idx_higher_or_equal(base_interface_layers, idx_layer_base_interface,fun); - } - // Copy polygons from the layers. - if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) - bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact]; - if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) - top_contact_layer.layer = top_contacts[idx_layer_top_contact]; - if (idx_layer_interface < interface_layers.size() && interface_layers[idx_layer_interface]->print_z < support_layer.print_z + EPSILON) - interface_layer.layer = interface_layers[idx_layer_interface]; - if (idx_layer_base_interface < base_interface_layers.size() && base_interface_layers[idx_layer_base_interface]->print_z < support_layer.print_z + EPSILON) - base_interface_layer.layer = base_interface_layers[idx_layer_base_interface]; - if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) - base_layer.layer = intermediate_layers[idx_layer_intermediate]; - - if (m_object_config->support_interface_top_layers == 0) { - // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. - if (m_support_params.can_merge_support_regions) { - if (base_layer.could_merge(top_contact_layer)) - base_layer.merge(std::move(top_contact_layer)); - else if (base_layer.empty()) - base_layer = std::move(top_contact_layer); - } - } else { - loop_interface_processor.generate(top_contact_layer, m_support_params.support_material_interface_flow); - // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. - // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used - // to trim other layers. - if (top_contact_layer.could_merge(interface_layer)) - top_contact_layer.merge(std::move(interface_layer)); - } - if ((m_object_config->support_interface_top_layers == 0 || m_object_config->support_interface_bottom_layers == 0) && m_support_params.can_merge_support_regions) { - if (base_layer.could_merge(bottom_contact_layer)) - base_layer.merge(std::move(bottom_contact_layer)); - else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) - base_layer = std::move(bottom_contact_layer); - } else if (bottom_contact_layer.could_merge(top_contact_layer)) - top_contact_layer.merge(std::move(bottom_contact_layer)); - else if (bottom_contact_layer.could_merge(interface_layer)) - bottom_contact_layer.merge(std::move(interface_layer)); - -#if 0 - if ( ! interface_layer.empty() && ! base_layer.empty()) { - // turn base support into interface when it's contained in our holes - // (this way we get wider interface anchoring) - //FIXME The intention of the code below is unclear. One likely wanted to just merge small islands of base layers filling in the holes - // inside interface layers, but the code below fills just too much, see GH #4570 - Polygons islands = top_level_islands(interface_layer.layer->polygons); - polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands)); - base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); - } -#endif - - // Calculate top interface angle - float angle_of_biggest_bridge = -1.f; - do - { - // Currently only works when thick_bridges is off - if (m_object->config().thick_bridges) - break; - - coordf_t object_layer_bottom_z = support_layer.print_z + m_slicing_params.gap_support_object; - const Layer* object_layer = m_object->get_layer_at_bottomz(object_layer_bottom_z, 10.0 * EPSILON); - if (object_layer == nullptr) - break; - - if (object_layer != nullptr) { - float biggest_bridge_area = 0.f; - const Polygons& top_contact_polys = top_contact_layer.polygons_to_extrude(); - for (auto layerm : object_layer->regions()) { - for (auto bridge_surface : layerm->fill_surfaces.filter_by_type(stBottomBridge)) { - float bs_area = bridge_surface->area(); - if (bs_area <= biggest_bridge_area || bridge_surface->bridge_angle < 0.f) - continue; - - angle_of_biggest_bridge = bridge_surface->bridge_angle; - biggest_bridge_area = bs_area; - } - } - } - } while (0); - - auto calc_included_angle_degree = [](int degree_a, int degree_b) { - int iad = std::abs(degree_b - degree_a); - return std::min(iad, 180 - iad); - }; - - // Top and bottom contacts, interface layers. - for (size_t i = 0; i < 3; ++ i) { - MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); - if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) - continue; - bool interface_as_base = m_object_config->support_interface_top_layers.value == 0 || - (m_object_config->support_interface_bottom_layers == 0 && &layer_ex == &bottom_contact_layer); - //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore - // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - Flow interface_flow; - if (layer_ex.layer->bridging) - interface_flow = Flow::bridging_flow(layer_ex.layer->height, m_support_params.support_material_bottom_interface_flow.nozzle_diameter()); - else if (layer_ex.layer->bottom_z < EPSILON) { - interface_flow = m_support_params.first_layer_flow; - }else - interface_flow = (interface_as_base ? &m_support_params.support_material_flow : &m_support_params.support_material_interface_flow)->with_height(float(layer_ex.layer->height)); - filler_interface->angle = interface_as_base ? - // If zero interface layers are configured, use the same angle as for the base layers. - angles[support_layer_id % angles.size()] : - // Use interface angle for the interface layers. - m_support_params.interface_angle + interface_angle_delta; - - // BBS - bool can_adjust_top_interface_angle = (m_object_config->support_interface_top_layers.value > 1 && &layer_ex == &top_contact_layer); - if (can_adjust_top_interface_angle && angle_of_biggest_bridge >= 0.f) { - int bridge_degree = (int)Geometry::rad2deg(angle_of_biggest_bridge); - int support_intf_degree = (int)Geometry::rad2deg(filler_interface->angle); - int max_included_degree = 0; - int step = 90; - for (int add_on_degree = 0; add_on_degree < 180; add_on_degree += step) { - int degree_to_try = support_intf_degree + add_on_degree; - int included_degree = calc_included_angle_degree(bridge_degree, degree_to_try); - if (included_degree > max_included_degree) { - max_included_degree = included_degree; - filler_interface->angle = Geometry::deg2rad((float)degree_to_try); - } - } - } - double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density; - filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing(); - filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); - // BBS support more interface patterns - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - if (m_object_config->support_interface_pattern == smipGrid) { - filler_interface->angle = Geometry::deg2rad(m_support_params.base_angle); - fill_params.dont_sort = true; - } - if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) - filler_interface->layer_id = support_layer.interface_id(); - fill_expolygons_generate_paths( - // Destination - layer_ex.extrusions, - // Regions to fill - union_safety_offset_ex(layer_ex.polygons_to_extrude()), - // Filler and its parameters - filler_interface.get(), fill_params, - // Extrusion parameters - interface_as_base ? erSupportMaterial : erSupportMaterialInterface, interface_flow); - } - - // Base interface layers under soluble interfaces - if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { - Fill *filler = filler_base_interface.get(); - //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore - // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - assert(! base_interface_layer.layer->bridging); - Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); - filler->angle = m_support_params.interface_angle + interface_angle_delta; - filler->spacing = m_support_params.support_material_interface_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.interface_density)); - fill_expolygons_generate_paths( - // Destination - base_interface_layer.extrusions, - //base_layer_interface.extrusions, - // Regions to fill - union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), - // Filler and its parameters - filler, float(m_support_params.interface_density), - // Extrusion parameters - erSupportMaterial, interface_flow); - } - - // Base support or flange. - if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { - Fill *filler = filler_support.get(); - filler->angle = angles[support_layer_id % angles.size()]; - // We don't use $base_flow->spacing because we need a constant spacing - // value that guarantees that all layers are correctly aligned. - assert(! base_layer.layer->bridging); - auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height)); - filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); - float density = float(m_support_params.support_density); - bool sheath = m_support_params.with_sheath; - bool no_sort = false; - if (base_layer.layer->bottom_z < EPSILON) { - // Base flange (the 1st layer). - filler = filler_first_layer; - // BBS: the 1st layer use the same fill direction as other layers(in rectilinear) to avoid - // that 2nd layer detaches from the 1st layer. - //filler->angle = Geometry::deg2rad(float(m_object_config->support_angle.value + 90.)); - density = float(m_object_config->raft_first_layer_density.value * 0.01); - flow = m_support_params.first_layer_flow; - // use the proper spacing for first layer as we don't need to align - // its pattern to the other layers - //FIXME When paralellizing, each thread shall have its own copy of the fillers. - filler->spacing = flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - sheath = true; - no_sort = true; - } - fill_expolygons_with_sheath_generate_paths( - // Destination - base_layer.extrusions, - // Regions to fill - base_layer.polygons_to_extrude(), - // Filler and its parameters - filler, density, - // Extrusion parameters - erSupportMaterial, flow, - sheath, no_sort); - - } - - // Merge base_interface_layers to base_layers to avoid unneccessary retractions - if (! base_layer.empty() && ! base_interface_layer.empty() && ! base_layer.polygons_to_extrude().empty() && ! base_interface_layer.polygons_to_extrude().empty() && - base_layer.could_merge(base_interface_layer)) - base_layer.merge(std::move(base_interface_layer)); - - layer_cache.add_nonempty_and_sort(); - - // Collect the support areas with this print_z into islands, as there is no need - // for retraction over these islands. - Polygons polys; - // Collect the extrusions, sorted by the bottom extrusion height. - for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { - // Collect islands to polys. - layer_cache_item.layer_extruded->polygons_append(polys); - // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" - // in a sense that they are not synchronized with other support layers. As the top and bottom contact surfaces - // are inflated to achieve a better anchoring, it may happen, that these surfaces will at least partially - // overlap in Z with another support layers, leading to over-extrusion. - // Mitigate the over-extrusion by modulating the extrusion rate over these regions. - // The print head will follow the same print_z, but the layer thickness will be reduced - // where it overlaps with another support layer. - //FIXME When printing a briging path, what is an equivalent height of the squished extrudate of the same width? - // Collect overlapping top/bottom surfaces. - layer_cache_item.overlapping.reserve(20); - coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; - auto add_overlapping = [&layer_cache_item, bottom_z](const SupportGeneratorLayersPtr &layers, size_t idx_top) { - for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(layers[i]); - }; - add_overlapping(top_contacts, idx_layer_top_contact); - if (layer_cache_item.layer_extruded->layer->layer_type == SupporLayerType::BottomContact) { - // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. - add_overlapping(intermediate_layers, idx_layer_intermediate); - add_overlapping(interface_layers, idx_layer_interface); - add_overlapping(base_interface_layers, idx_layer_base_interface); - } - // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. - std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); - } - if (! polys.empty()) - expolygons_append(support_layer.support_islands, union_ex(polys)); - } // for each support_layer_id - }); - - // Now modulate the support layer height in parallel. - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [&support_layers, &layer_caches] - (const tbb::blocked_range& range) { - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { - SupportLayer &support_layer = *support_layers[support_layer_id]; - LayerCache &layer_cache = layer_caches[support_layer_id]; - // For all extrusion types at this print_z, ordered by decreasing layer height: - for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { - // Trim the extrusion height from the bottom by the overlapping layers. - modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); - support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); - } - } - }); - -#ifndef NDEBUG - struct Test { - static bool verify_nonempty(const ExtrusionEntityCollection *collection) { - for (const ExtrusionEntity *ee : collection->entities) { - if (const ExtrusionPath *path = dynamic_cast(ee)) - assert(! path->empty()); - else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) - assert(! multipath->empty()); - else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { - assert(! eecol->empty()); - return verify_nonempty(eecol); - } else - assert(false); - } - return true; - } - }; - for (const SupportLayer *support_layer : support_layers) - assert(Test::verify_nonempty(&support_layer->support_fills)); -#endif // NDEBUG -} - /* void PrintObjectSupportMaterial::clip_by_pillars( const PrintObject &object, diff --git a/src/libslic3r/Support/SupportMaterial.hpp b/src/libslic3r/Support/SupportMaterial.hpp index 809f9b16112..e489c2374aa 100644 --- a/src/libslic3r/Support/SupportMaterial.hpp +++ b/src/libslic3r/Support/SupportMaterial.hpp @@ -6,6 +6,7 @@ #include "Slicing.hpp" #include "Fill/FillBase.hpp" #include "SupportLayer.hpp" +#include "SupportParameters.hpp" namespace Slic3r { class PrintObject; @@ -18,35 +19,6 @@ class PrintObjectConfig; // the parameters of the raft to determine the 1st layer height and thickness. class PrintObjectSupportMaterial { -public: - - struct SupportParams { - Flow first_layer_flow; - Flow support_material_flow; - Flow support_material_interface_flow; - Flow support_material_bottom_interface_flow; - // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? - bool can_merge_support_regions; - - coordf_t support_layer_height_min; - // coordf_t support_layer_height_max; - - coordf_t gap_xy; - - float base_angle; - float interface_angle; - coordf_t interface_spacing; - coordf_t support_expansion; - coordf_t interface_density; - coordf_t support_spacing; - coordf_t support_density; - - InfillPattern base_fill_pattern; - InfillPattern interface_fill_pattern; - InfillPattern contact_fill_pattern; - bool with_sheath; - }; - public: PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params); @@ -97,25 +69,7 @@ class PrintObjectSupportMaterial SupportGeneratorLayersPtr &intermediate_layers, const std::vector &layer_support_areas) const; - // Generate raft layers, also expand the 1st support layer - // in case there is no raft layer to improve support adhesion. - SupportGeneratorLayersPtr generate_raft_base( - const PrintObject &object, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers, - const SupportGeneratorLayersPtr &base_layers, - SupportGeneratorLayerStorage &layer_storage) const; - - // Turn some of the base layers into base interface layers. - // For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base - // extruder to improve adhesion of the soluble filament to the base. - std::pair generate_interface_layers( - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage) const; - + // Trim support layers by an object to leave a defined gap between // the support volume and the object. @@ -131,16 +85,6 @@ class PrintObjectSupportMaterial void clip_with_shape(); */ - // Produce the actual G-code. - void generate_toolpaths( - SupportLayerPtrs &support_layers, - const SupportGeneratorLayersPtr &raft_layers, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &intermediate_layers, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers) const; - // Following objects are not owned by SupportMaterial class. const PrintObject *m_object; const PrintConfig *m_print_config; @@ -149,7 +93,7 @@ class PrintObjectSupportMaterial // carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc. SlicingParameters m_slicing_params; // Various precomputed support parameters to be shared with external functions. - SupportParams m_support_params; + SupportParameters m_support_params; }; } // namespace Slic3r diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index e240504c23c..bcc85e2202b 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -1,53 +1,53 @@ #ifndef slic3r_SupportParameters_hpp_ #define slic3r_SupportParameters_hpp_ +#include #include "../libslic3r.h" #include "../Flow.hpp" namespace Slic3r { - -class PrintObject; -enum InfillPattern : int; - struct SupportParameters { - SupportParameters(const PrintObject &object) + SupportParameters() = delete; + SupportParameters(const PrintObject& object) { - const PrintConfig &print_config = object.print()->config(); - const PrintObjectConfig &object_config = object.config(); - const SlicingParameters &slicing_params = object.slicing_parameters(); - - this->soluble_interface = slicing_params.soluble_interface; - this->soluble_interface_non_soluble_base = - // Zero z-gap between the overhangs and the support interface. - slicing_params.soluble_interface && - // Interface extruder soluble. - object_config.support_interface_filament.value > 0 && print_config.filament_soluble.get_at(object_config.support_interface_filament.value - 1) && - // Base extruder: Either "print with active extruder" not soluble. - (object_config.support_filament.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_filament.value - 1)); - - { - int num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value); - int num_bottom_interface_layers = object_config.support_interface_bottom_layers < 0 ? - num_top_interface_layers : object_config.support_interface_bottom_layers; - this->has_top_contacts = num_top_interface_layers > 0; - this->has_bottom_contacts = num_bottom_interface_layers > 0; - this->num_top_interface_layers = this->has_top_contacts ? size_t(num_top_interface_layers - 1) : 0; - this->num_bottom_interface_layers = this->has_bottom_contacts ? size_t(num_bottom_interface_layers - 1) : 0; - if (this->soluble_interface_non_soluble_base) { - // Try to support soluble dense interfaces with non-soluble dense interfaces. - this->num_top_base_interface_layers = size_t(std::min(num_top_interface_layers / 2, 2)); - this->num_bottom_base_interface_layers = size_t(std::min(num_bottom_interface_layers / 2, 2)); - } else { - this->num_top_base_interface_layers = 0; - this->num_bottom_base_interface_layers = 0; - } - } - - this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height)); - this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height)); - this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height)); - this->raft_interface_flow = support_material_interface_flow; - + const PrintConfig& print_config = object.print()->config(); + const PrintObjectConfig& object_config = object.config(); + const SlicingParameters& slicing_params = object.slicing_parameters(); + + this->soluble_interface = slicing_params.soluble_interface; + this->soluble_interface_non_soluble_base = + // Zero z-gap between the overhangs and the support interface. + slicing_params.soluble_interface && + // Interface extruder soluble. + object_config.support_interface_filament.value > 0 && print_config.filament_soluble.get_at(object_config.support_interface_filament.value - 1) && + // Base extruder: Either "print with active extruder" not soluble. + (object_config.support_filament.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_filament.value - 1)); + + { + this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value); + this->num_bottom_interface_layers = object_config.support_interface_bottom_layers < 0 ? + num_top_interface_layers : object_config.support_interface_bottom_layers; + this->has_top_contacts = num_top_interface_layers > 0; + this->has_bottom_contacts = num_bottom_interface_layers > 0; + if (this->soluble_interface_non_soluble_base) { + // Try to support soluble dense interfaces with non-soluble dense interfaces. + this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2)); + this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2)); + } else { + // BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion + // Note: support materials (such as Supp.W) can't be used as support base now, so support interface and base are still using different filaments even if + // support_filament==0 + bool differnt_support_interface_filament = object_config.support_interface_filament != 0 && + object_config.support_interface_filament != object_config.support_filament; + this->num_top_base_interface_layers = differnt_support_interface_filament ? 1 : 0; + this->num_bottom_base_interface_layers = differnt_support_interface_filament ? 1 : 0; + } + } + this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height)); + this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height)); + this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height)); + this->raft_interface_flow = support_material_interface_flow; + // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. this->support_layer_height_min = scaled(0.01); for (auto lh : print_config.min_layer_height.values) @@ -69,10 +69,11 @@ struct SupportParameters { external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(object, frExternalPerimeter, slicing_params.layer_height).width())); bridge_flow_ratio += region.config().bridge_flow; } - this->gap_xy = object_config.support_object_xy_distance;//.get_abs_value(external_perimeter_width); + this->gap_xy = object_config.support_object_xy_distance.value; + this->gap_xy_first_layer = object_config.support_object_first_layer_gap.value; bridge_flow_ratio /= object.num_printing_regions(); - - this->support_material_bottom_interface_flow = slicing_params.soluble_interface || ! object_config.thick_bridges ? + + this->support_material_bottom_interface_flow = slicing_params.soluble_interface || !object_config.thick_bridges ? this->support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : Flow::bridging_flow(bridge_flow_ratio * this->support_material_interface_flow.nozzle_diameter(), this->support_material_interface_flow.nozzle_diameter()); @@ -85,33 +86,40 @@ struct SupportParameters { // Object is printed with the same extruder as the support. this->can_merge_support_regions = true; } - - double interface_spacing = object_config.support_interface_spacing.value + this->support_material_interface_flow.spacing(); - this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing); + + + this->base_angle = Geometry::deg2rad(float(object_config.support_angle.value)); + this->interface_angle = Geometry::deg2rad(float(object_config.support_angle.value + 90.)); + this->interface_spacing = object_config.support_interface_spacing.value + this->support_material_interface_flow.spacing(); + this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / this->interface_spacing); double raft_interface_spacing = object_config.support_interface_spacing.value + this->raft_interface_flow.spacing(); this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing); - double support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing(); - this->support_density = std::min(1., this->support_material_flow.spacing() / support_spacing); + this->support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing(); + this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_spacing); if (object_config.support_interface_top_layers.value == 0) { // No interface layers allowed, print everything with the base support pattern. + this->interface_spacing = this->support_spacing; this->interface_density = this->support_density; } - + SupportMaterialPattern support_pattern = object_config.support_base_pattern; - this->with_sheath = false;//object_config.support_material_with_sheath; - this->base_fill_pattern = + this->with_sheath = object_config.tree_support_wall_count > 0; + this->base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase; - this->contact_fill_pattern = + if (object_config.support_interface_pattern == smipGrid) + this->contact_fill_pattern = ipGrid; + else if (object_config.support_interface_pattern == smipRectilinearInterlaced) + this->contact_fill_pattern = ipRectilinear; + else + this->contact_fill_pattern = (object_config.support_interface_pattern == smipAuto && slicing_params.soluble_interface) || object_config.support_interface_pattern == smipConcentric ? ipConcentric : (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); - - this->base_angle = Geometry::deg2rad(float(object_config.support_angle.value)); - this->interface_angle = Geometry::deg2rad(float(object_config.support_angle.value + 90.)); + this->raft_angle_1st_layer = 0.f; this->raft_angle_base = 0.f; this->raft_angle_interface = 0.f; @@ -142,11 +150,36 @@ struct SupportParameters { assert(slicing_params.interface_raft_layers == 0); assert(slicing_params.raft_layers() == 0); } - - this->tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled(object_config.tree_support_branch_diameter_double_wall.value)) * M_PI; - } - // Both top / bottom contacts and interfaces are soluble. + const auto nozzle_diameter = print_config.nozzle_diameter.get_at(object_config.support_interface_filament - 1); + const coordf_t extrusion_width = object_config.line_width.get_abs_value(nozzle_diameter); + support_extrusion_width = object_config.support_line_width.get_abs_value(nozzle_diameter); + support_extrusion_width = support_extrusion_width > 0 ? support_extrusion_width : extrusion_width; + + independent_layer_height = print_config.independent_support_layer_height; + + // force double walls everywhere if wall count is larger than 1 + tree_branch_diameter_double_wall_area_scaled = object_config.tree_support_wall_count.value > 1 ? 0.1 : + object_config.tree_support_wall_count.value == 0 ? 0.25 * sqr(scaled(5.0)) * M_PI : + std::numeric_limits::max(); + + support_style = object_config.support_style; + if (support_style != smsDefault) { + if ((support_style == smsSnug || support_style == smsGrid) && is_tree(object_config.support_type)) support_style = smsDefault; + if ((support_style == smsTreeSlim || support_style == smsTreeStrong || support_style == smsTreeHybrid || support_style == smsTreeOrganic) && + !is_tree(object_config.support_type)) + support_style = smsDefault; + } + if (support_style == smsDefault) { + if (is_tree(object_config.support_type)) { + // Orca: use organic as default + support_style = smsTreeOrganic; + } else { + support_style = smsGrid; + } + } + } + // Both top / bottom contacts and interfaces are soluble. bool soluble_interface; // Support contact & interface are soluble, but support base is non-soluble. bool soluble_interface_non_soluble_base; @@ -181,23 +214,28 @@ struct SupportParameters { Flow support_material_bottom_interface_flow; // Flow at raft inteface & contact layers. Flow raft_interface_flow; + coordf_t support_extrusion_width; // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? bool can_merge_support_regions; coordf_t support_layer_height_min; // coordf_t support_layer_height_max; - coordf_t gap_xy; + coordf_t gap_xy; + coordf_t gap_xy_first_layer; float base_angle; float interface_angle; - + coordf_t interface_spacing; + coordf_t support_expansion=0; // Density of the top / bottom interface and contact layers. coordf_t interface_density; // Density of the raft interface and contact layers. coordf_t raft_interface_density; + coordf_t support_spacing; // Density of the base support layers. coordf_t support_density; + SupportMaterialStyle support_style = smsDefault; // Pattern of the sparse infill including sparse raft layers. InfillPattern base_fill_pattern; @@ -210,7 +248,7 @@ struct SupportParameters { // Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness? bool with_sheath; // Branches of organic supports with area larger than this threshold will be extruded with double lines. - double tree_branch_diameter_double_wall_area_scaled; + double tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled(5.0)) * M_PI;; float raft_angle_1st_layer; float raft_angle_base; @@ -219,6 +257,9 @@ struct SupportParameters { // Produce a raft interface angle for a given SupportLayer::interface_id() float raft_interface_angle(size_t interface_id) const { return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); } + + bool independent_layer_height = false; + const double thresh_big_overhang = Slic3r::sqr(scale_(10)); }; } // namespace Slic3r diff --git a/src/libslic3r/Support/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp index 9c49c813a90..9cf8b3df373 100644 --- a/src/libslic3r/Support/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -33,7 +33,7 @@ using namespace std::literals; // or warning // had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL() -#define error_level_not_in_cache error +#define error_level_not_in_cache debug //FIXME Machine border is currently ignored. static Polygons calculateMachineBorderCollision(Polygon machine_border) diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 6d6e25ef2b5..139ae42597b 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -1,25 +1,30 @@ +#include #include +#include "format.hpp" +#include "ClipperUtils.hpp" +#include "Fill/FillBase.hpp" +#include "I18N.hpp" +#include "Layer.hpp" #include "MinimumSpanningTree.hpp" -#include "TreeSupport.hpp" #include "Print.hpp" -#include "Layer.hpp" -#include "Fill/FillBase.hpp" -#include "Fill/FillConcentric.hpp" -#include "CurveAnalyzer.hpp" -#include "SVG.hpp" #include "ShortestPath.hpp" -#include "I18N.hpp" -#include +#include "SupportCommon.hpp" +#include "SVG.hpp" +#include "TreeSupportCommon.hpp" +#include "TreeSupport.hpp" #include "TreeSupport3D.hpp" +#include +#include + -#include #include +#include +#include #include +#include -#define _L(s) Slic3r::I18N::translate(s) - -#define USE_PLAN_LAYER_HEIGHTS 1 +#include #ifndef M_PI #define M_PI 3.1415926535897932384626433832795 @@ -29,8 +34,9 @@ #endif #define TAU (2.0 * M_PI) #define NO_INDEX (std::numeric_limits::max()) +#define USE_SUPPORT_3D 0 -// #define SUPPORT_TREE_DEBUG_TO_SVG +//#define SUPPORT_TREE_DEBUG_TO_SVG #ifdef SUPPORT_TREE_DEBUG_TO_SVG #include "nlohmann/json.hpp" @@ -39,15 +45,6 @@ namespace Slic3r { #define unscale_(val) ((val) * SCALING_FACTOR) -inline unsigned int round_divide(unsigned int dividend, unsigned int divisor) //!< Return dividend divided by divisor rounded to the nearest integer -{ - return (dividend + divisor / 2) / divisor; -} -inline unsigned int round_up_divide(unsigned int dividend, unsigned int divisor) //!< Return dividend divided by divisor rounded to the nearest integer -{ - return (dividend + divisor - 1) / divisor; -} - inline double dot_with_unscale(const Point a, const Point b) { return unscale_(a(0)) * unscale_(b(0)) + unscale_(a(1)) * unscale_(b(1)); @@ -70,6 +67,9 @@ inline Point turn90_ccw(const Point pt) inline Point normal(Point pt, double scale) { double length = scale_(sqrt(vsize2_with_unscale(pt))); + if (length < SCALED_EPSILON) { + return pt; + } return pt * (scale / length); } @@ -92,7 +92,7 @@ enum TreeSupportStage { class TreeSupportProfiler { public: - uint32_t stage_durations[NUM_STAGES]; + uint32_t stage_durations[NUM_STAGES] = { 0 }; uint32_t stage_index = 0; boost::posix_time::ptime tic_time; boost::posix_time::ptime toc_time; @@ -122,14 +122,15 @@ class TreeSupportProfiler } void tic() { tic_time = boost::posix_time::microsec_clock::local_time(); } - void toc() { toc_time = boost::posix_time::microsec_clock::local_time(); } - void stage_add(TreeSupportStage stage, bool do_toc = false) + uint32_t toc() { + toc_time = boost::posix_time::microsec_clock::local_time(); + return (toc_time - tic_time).total_milliseconds(); + } + void stage_add(TreeSupportStage stage) { if (stage > NUM_STAGES) return; - if(do_toc) - toc_time = boost::posix_time::microsec_clock::local_time(); - stage_durations[stage] += (toc_time - tic_time).total_milliseconds(); + stage_durations[stage] += toc(); } std::string report() @@ -184,39 +185,23 @@ Lines spanning_tree_to_lines(const std::vector& spanning_tr #ifdef SUPPORT_TREE_DEBUG_TO_SVG -static std::string get_svg_filename(std::string layer_nr_or_z, std::string tag = "bbl_ts") -{ - static bool rand_init = false; - - if (!rand_init) { - srand(time(NULL)); - rand_init = true; - } - - int rand_num = rand() % 1000000; - //makedir("./SVG"); - std::string prefix = "./SVG/"; - std::string suffix = ".svg"; - return prefix + tag + "_" + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix; -} - static void draw_contours_and_nodes_to_svg ( - std::string layer_nr_or_z, + std::string fname, const ExPolygons &overhangs, const ExPolygons &overhangs_after_offset, const ExPolygons &outlines_below, - const std::vector &layer_nodes, - const std::vector &lower_layer_nodes, - std::string name_prefix, - std::vector legends = { "overhang","avoid","outlines" }, std::vector colors = { "blue","red","yellow" } + const std::vector& layer_nodes, + const std::vector& lower_layer_nodes, + std::vector legends = { "overhang","avoid","outlines" }, + std::vector colors = { "blue","red","yellow" } ) { BoundingBox bbox = get_extents(overhangs); bbox.merge(get_extents(overhangs_after_offset)); bbox.merge(get_extents(outlines_below)); Points layer_pts; - for (TreeSupport::Node* node : layer_nodes) { + for (SupportNode* node : layer_nodes) { layer_pts.push_back(node->position); } bbox.merge(get_extents(layer_pts)); @@ -225,53 +210,45 @@ static void draw_contours_and_nodes_to_svg bbox.max.y() = std::max(bbox.max.y(), (coord_t)scale_(10)); SVG svg; - if(!layer_nr_or_z.empty()) - svg.open(get_svg_filename(layer_nr_or_z, name_prefix), bbox); - else - svg.open(name_prefix, bbox); + svg.open(fname, bbox); if (!svg.is_opened()) return; // draw grid svg.draw_grid(bbox, "gray", coord_t(scale_(0.05))); // draw overhang areas - svg.draw_outline(union_ex(overhangs), colors[0]); - svg.draw_outline(union_ex(overhangs_after_offset), colors[1]); + svg.draw_outline(overhangs, colors[0]); + svg.draw_outline(overhangs_after_offset, colors[1]); svg.draw_outline(outlines_below, colors[2]); // draw legend - if (!lower_layer_nodes.empty()) { - svg.draw_text(bbox.min + Point(scale_(0), scale_(0)), format("nPoints: %1%->%2%",layer_nodes.size(), lower_layer_nodes.size()).c_str(), "green", 2); - } - else { - svg.draw_text(bbox.min + Point(scale_(0), scale_(0)), ("nPoints: " + std::to_string(layer_nodes.size())).c_str(), "green", 2); - } + svg.draw_text(bbox.min + Point(scale_(0), scale_(0)), format("nPoints: %1%->%2%",layer_nodes.size(), lower_layer_nodes.size()).c_str(), "green", 2); svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), legends[0].c_str(), colors[0].c_str(), 2); svg.draw_text(bbox.min + Point(scale_(0), scale_(4)), legends[1].c_str(), colors[1].c_str(), 2); svg.draw_text(bbox.min + Point(scale_(0), scale_(6)), legends[2].c_str(), colors[2].c_str(), 2); // draw layer nodes svg.draw(layer_pts, "green", coord_t(scale_(0.1))); -#if 0 + for (SupportNode *node : layer_nodes) { svg.draw({node->overhang}, "green", 0.5); } + // lower layer points layer_pts.clear(); - for (TreeSupport::Node *node : lower_layer_nodes) { + for (SupportNode* node : lower_layer_nodes) { layer_pts.push_back(node->position); } svg.draw(layer_pts, "black", coord_t(scale_(0.1))); - // higher layer points - layer_pts.clear(); - for (TreeSupport::Node* node : layer_nodes) { - if(node->parent) - layer_pts.push_back(node->parent->position); - } - svg.draw(layer_pts, "blue", coord_t(scale_(0.1))); -#endif + //// higher layer points + //layer_pts.clear(); + //for (SupportNode* node : layer_nodes) { + // if(node->parent) + // layer_pts.push_back(node->parent->position); + //} + //svg.draw(layer_pts, "blue", coord_t(scale_(0.1))); } static void draw_layer_mst -(const std::string &layer_nr_or_z, +(const std::string &fname, const std::vector &spanning_trees, const ExPolygons& outline ) @@ -284,7 +261,7 @@ static void draw_layer_mst bbox.merge(bb); } - SVG svg(get_svg_filename(layer_nr_or_z, "mstree").c_str(), bbox); + SVG svg(fname, bbox); if (!svg.is_opened()) return; svg.draw(lines, "blue", coord_t(scale_(0.05))); @@ -293,54 +270,13 @@ static void draw_layer_mst svg.draw(spanning_tree.vertices(), "black", coord_t(scale_(0.1))); } -static void draw_two_overhangs_to_svg(SupportLayer* ts_layer, const ExPolygons& overhangs1, const ExPolygons& overhangs2) -{ - if (overhangs1.empty() && overhangs2.empty()) - return; - BoundingBox bbox1 = get_extents(overhangs1); - BoundingBox bbox2 = get_extents(overhangs2); - bbox1.merge(bbox2); - - SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "two_overhangs"), bbox1); - if (!svg.is_opened()) return; - - svg.draw(union_ex(overhangs1), "blue"); - svg.draw(union_ex(overhangs2), "red"); -} - -static void draw_polylines(SupportLayer* ts_layer, Polylines& polylines) -{ - if (polylines.empty()) - return; - BoundingBox bbox = get_extents(polylines); - - SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "lightnings"), bbox); - if (!svg.is_opened()) return; - - int id = 0; - for (Polyline& pline : polylines) - { - int i1, i2; - for (size_t i = 0; i < pline.size() - 1; i++) - { - i1 = i; - i2 = i + 1; - svg.draw(Line(pline.points[i1], pline.points[i2]), "blue"); - svg.draw(pline.points[i1], "red"); - id++; - svg.draw_text(pline.points[i1], std::to_string(id).c_str(), "black", 1); - } - svg.draw(pline.points[i2], "red"); - id++; - svg.draw_text(pline.points[i2], std::to_string(id).c_str(), "black", 1); - } -} #endif // Move point from inside polygon if distance>0, outside if distance<0. // Special case: distance=0 means find the nearest point of from on the polygon contour. // The max move distance should not excceed max_move_distance. -static unsigned int move_inside_expoly(const ExPolygon &polygon, Point& from, double distance = 0, double max_move_distance = std::numeric_limits::max()) +// @return success(true) or not(false) +static bool move_inside_expoly(const ExPolygon &polygon, Point& from, double distance = 0, double max_move_distance = std::numeric_limits::max()) { //TODO: This is copied from the moveInside of Polygons. /* @@ -356,7 +292,7 @@ static unsigned int move_inside_expoly(const ExPolygon &polygon, Point& from, do if (contour.points.size() < 2) { - return 0; + return false; } Point p0 = contour.points[polygon.contour.size() - 2]; Point p1 = contour.points.back(); @@ -453,12 +389,14 @@ static unsigned int move_inside_expoly(const ExPolygon &polygon, Point& from, do { from = ret; } + return true; } else if (bestDist2 < max_move_distance * max_move_distance) { from = ret; + return true; } - return 0; + return false; } /* @@ -631,6 +569,7 @@ static bool is_inside_ex(const ExPolygons &polygons, const Point &pt) return false; } +// use project_onto which is more accurate but more expensive static bool move_out_expolys(const ExPolygons& polygons, Point& from, double distance, double max_move_distance) { Point from0 = from; @@ -667,67 +606,49 @@ static Point bounding_box_middle(const BoundingBox &bbox) } TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_params) - : m_object(&object), m_slicing_params(slicing_params), m_object_config(&object.config()) + : m_object(&object), m_slicing_params(slicing_params), m_support_params(object), m_object_config(&object.config()) { m_print_config = &m_object->print()->config(); m_raft_layers = slicing_params.base_raft_layers + slicing_params.interface_raft_layers; support_type = m_object_config->support_type; - support_style = m_object_config->support_style; - if (support_style == smsDefault) - // Orca: use organic as default - support_style = smsOrganic; + SupportMaterialPattern support_pattern = m_object_config->support_base_pattern; - if (support_style == smsTreeHybrid && support_pattern == smpDefault) + if (m_support_params.support_style == smsTreeHybrid && support_pattern == smpDefault) support_pattern = smpRectilinear; - m_support_params.base_fill_pattern = - support_pattern == smpLightning ? ipLightning : - support_pattern == smpHoneycomb ? ipHoneycomb : - m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase; - - m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); - if (m_object_config->support_interface_pattern == smipGrid) - m_support_params.contact_fill_pattern = ipGrid; - else if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) - m_support_params.contact_fill_pattern = ipRectilinear; - else - m_support_params.contact_fill_pattern = (m_object_config->support_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || - m_object_config->support_interface_pattern == smipConcentric ? - ipConcentric : - (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); - - const auto nozzle_diameter = object.print()->config().nozzle_diameter.get_at(object.config().support_interface_filament-1); - const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(nozzle_diameter); - const coordf_t support_extrusion_width = m_object_config->support_line_width.get_abs_value(nozzle_diameter); - - m_support_params.support_extrusion_width = support_extrusion_width > 0 ? support_extrusion_width : extrusion_width; - is_slim = is_tree_slim(support_type, support_style); - is_strong = is_tree(support_type) && support_style == smsTreeStrong; - MAX_BRANCH_RADIUS = 10.0; - tree_support_branch_diameter_angle = 5.0;//is_slim ? 10.0 : 5.0; + + if(support_pattern == smpLightning) + m_support_params.base_fill_pattern = ipLightning; + diameter_angle_scale_factor = std::clamp(m_object_config->tree_support_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); + is_slim = is_tree_slim(support_type, m_support_params.support_style); + is_strong = is_tree(support_type) && m_support_params.support_style == smsTreeStrong; + base_radius = std::max(MIN_BRANCH_RADIUS, m_object_config->tree_support_branch_diameter.value / 2); // by default tree support needs no infill, unless it's tree hybrid which contains normal nodes. with_infill = support_pattern != smpNone && support_pattern != smpDefault; m_machine_border.contour = get_bed_shape_with_excluded_area(*m_print_config); Vec3d plate_offset = m_object->print()->get_plate_origin(); // align with the centered object in current plate (may not be the 1st plate, so need to add the plate offset) m_machine_border.translate(Point(scale_(plate_offset(0)), scale_(plate_offset(1))) - m_object->instances().front().shift); + top_z_distance = m_object_config->support_top_z_distance.value; + if (top_z_distance > EPSILON) top_z_distance = std::max(top_z_distance, float(m_slicing_params.min_layer_height)); #ifdef SUPPORT_TREE_DEBUG_TO_SVG - SVG svg("SVG/machine_boarder.svg", m_object->bounding_box()); + SVG svg(debug_out_path("machine_boarder.svg"), m_object->bounding_box()); if (svg.is_opened()) svg.draw(m_machine_border, "yellow"); #endif } #define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. -void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) +void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) { - // overhangs are already detected - if (m_object->support_layer_count() >= m_object->layer_count()) + bool tree_support_enable = m_object_config->enable_support.value && is_tree(m_object_config->support_type.value); + if (!tree_support_enable && !check_support_necessity) { + BOOST_LOG_TRIVIAL(info) << "Tree support is disabled."; return; + } // Clear and create Tree Support Layers m_object->clear_support_layers(); m_object->clear_tree_support_preview_cache(); - create_tree_support_layers(); const PrintObjectConfig& config = m_object->config(); SupportType stype = support_type; @@ -738,18 +659,21 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) const coordf_t max_bridge_length = scale_(config.max_bridge_length.value); const bool bridge_no_support = max_bridge_length > 0; const bool support_critical_regions_only = config.support_critical_regions_only.value; - const bool config_remove_small_overhangs = config.support_remove_small_overhang.value; + bool config_remove_small_overhangs = config.support_remove_small_overhang.value; + bool config_detect_sharp_tails = g_config_support_sharp_tails; const int enforce_support_layers = config.enforce_support_layers.value; const double area_thresh_well_supported = SQ(scale_(6)); const double length_thresh_well_supported = scale_(6); static const double sharp_tail_max_support_height = 16.f; // a region is considered well supported if the number of layers below it exceeds this threshold const int thresh_layers_below = 10 / config.layer_height; - double obj_height = m_object->size().z(); // +1 makes the threshold inclusive double thresh_angle = config.support_threshold_angle.value > EPSILON ? config.support_threshold_angle.value + 1 : 30; thresh_angle = std::min(thresh_angle, 89.); // should be smaller than 90 const double threshold_rad = Geometry::deg2rad(thresh_angle); + // FIXME this is a fudge constant! + double support_tree_tip_diameter = 0.8; + auto enforcer_overhang_offset = scaled(support_tree_tip_diameter); // for small overhang removal struct OverhangCluster { @@ -836,11 +760,29 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) return cluster; }; - if (!is_tree(stype)) return; + if (!is_tree(stype)) return; max_cantilever_dist = 0; + m_highest_overhang_layer = 0; + // calc the extrudable expolygons of each layer + tbb::parallel_for(tbb::blocked_range(0, m_object->layer_count()), + [&](const tbb::blocked_range& range) { + for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { + if (m_object->print()->canceled()) + break; + Layer* layer = m_object->get_layer(layer_nr); + // Filter out areas whose diameter that is smaller than extrusion_width, but we don't want to lose any details. + layer->lslices_extrudable = intersection_ex(layer->lslices, offset2_ex(layer->lslices, -extrusion_width_scaled / 2, extrusion_width_scaled)); + layer->loverhangs.clear(); + } + }); + + typedef std::chrono::high_resolution_clock clock_; + typedef std::chrono::duration > second_; + std::chrono::time_point t0{ clock_::now() }; // main part of overhang detection can be parallel + tbb::concurrent_vector overhangs_all_layers(m_object->layer_count()); tbb::parallel_for(tbb::blocked_range(0, m_object->layer_count()), [&](const tbb::blocked_range& range) { for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { @@ -853,12 +795,12 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) Layer* layer = m_object->get_layer(layer_nr); if (layer->lower_layer == nullptr) { - for (auto& slice : layer->lslices) { + for (auto& slice : layer->lslices_extrudable) { auto bbox_size = get_extents(slice).size(); if (!((bbox_size.x() > length_thresh_well_supported && bbox_size.y() > length_thresh_well_supported)) && g_config_support_sharp_tails) { layer->sharp_tails.push_back(slice); - layer->sharp_tails_height.insert({ &slice, layer->height }); + layer->sharp_tails_height.push_back(layer->height); } } continue; @@ -866,85 +808,67 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) Layer* lower_layer = layer->lower_layer; coordf_t lower_layer_offset = layer_nr < enforce_support_layers ? -0.15 * extrusion_width : (float)lower_layer->height / tan(threshold_rad); + //lower_layer_offset = std::min(lower_layer_offset, extrusion_width); coordf_t support_offset_scaled = scale_(lower_layer_offset); - // Filter out areas whose diameter that is smaller than extrusion_width. Do not use offset2() for this purpose! - ExPolygons lower_polys; - for (const ExPolygon& expoly : lower_layer->lslices) { - if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) { - lower_polys.emplace_back(expoly); - } - } - ExPolygons curr_polys; - std::vector curr_poly_ptrs; - for (const ExPolygon& expoly : layer->lslices) { - if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) { - curr_polys.emplace_back(expoly); - curr_poly_ptrs.emplace_back(&expoly); - } - } + ExPolygons& curr_polys = layer->lslices_extrudable; + ExPolygons& lower_polys = lower_layer->lslices_extrudable; // normal overhang ExPolygons lower_layer_offseted = offset_ex(lower_polys, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); - ExPolygons overhang_areas = diff_ex(curr_polys, lower_layer_offseted); - - overhang_areas.erase(std::remove_if(overhang_areas.begin(), overhang_areas.end(), - [extrusion_width_scaled](ExPolygon& area) { return offset_ex(area, -0.1 * extrusion_width_scaled).empty(); }), - overhang_areas.end()); + overhangs_all_layers[layer_nr] = std::move(diff_ex(curr_polys, lower_layer_offseted)); - - if (is_auto(stype) && g_config_support_sharp_tails) + double duration{ std::chrono::duration_cast(clock_::now() - t0).count() }; + if (duration > 30 || overhangs_all_layers[layer_nr].size() > 100) { + BOOST_LOG_TRIVIAL(info) << "detect_overhangs takes more than 30 secs, skip cantilever and sharp tails detection: layer_nr=" << layer_nr << " duration=" << duration; + config_detect_sharp_tails = false; + config_remove_small_overhangs = false; + continue; + } + if (is_auto(stype) && config_detect_sharp_tails) { // BBS detect sharp tail - for (const ExPolygon* expoly : curr_poly_ptrs) { + for (const ExPolygon& expoly : curr_polys) { bool is_sharp_tail = false; // 1. nothing below - // this is a sharp tail region if it's small but non-ignorable - if (!overlaps(offset_ex(*expoly, 0.5 * extrusion_width_scaled), lower_polys)) { - is_sharp_tail = expoly->area() < area_thresh_well_supported && !offset_ex(*expoly, -0.1 * extrusion_width_scaled).empty(); + // this is a sharp tail region if it's floating and non-ignorable + if (!overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), lower_polys)) { + is_sharp_tail = !offset_ex(expoly, -0.1 * extrusion_width_scaled).empty(); } if (is_sharp_tail) { - ExPolygons overhang = diff_ex({ *expoly }, lower_polys); - layer->sharp_tails.push_back(*expoly); - layer->sharp_tails_height.insert({ expoly, layer->height }); - append(overhang_areas, overhang); + layer->sharp_tails.push_back(expoly); + layer->sharp_tails_height.push_back(0); - if (!overhang.empty()) { - has_sharp_tails = true; + has_sharp_tails = true; #ifdef SUPPORT_TREE_DEBUG_TO_SVG - SVG svg(format("SVG/sharp_tail_orig_%.02f.svg", layer->print_z), m_object->bounding_box()); - if (svg.is_opened()) svg.draw(overhang, "red"); + SVG::export_expolygons(debug_out_path("sharp_tail_orig_%.02f.svg", layer->print_z), { expoly }); #endif - } - } + } } } - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - for (ExPolygon& poly : overhang_areas) { - if (offset_ex(poly, -0.1 * extrusion_width_scaled).empty()) continue; - ts_layer->overhang_areas.emplace_back(poly); - // check cantilever - { - auto cluster_boundary_ex = intersection_ex(poly, offset_ex(lower_layer->lslices, scale_(0.5))); - Polygons cluster_boundary = to_polygons(cluster_boundary_ex); - if (cluster_boundary.empty()) continue; - double dist_max = 0; - for (auto& pt : poly.contour.points) { - double dist_pt = std::numeric_limits::max(); - for (auto& ply : cluster_boundary) { - double d = ply.distance_to(pt); - dist_pt = std::min(dist_pt, d); - } - dist_max = std::max(dist_max, dist_pt); - } - if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base - max_cantilever_dist = std::max(max_cantilever_dist, dist_max); - layer->cantilevers.emplace_back(poly); - BOOST_LOG_TRIVIAL(debug) << "found a cantilever cluster. layer_nr=" << layer_nr << dist_max; - has_cantilever = true; + // check cantilever + // lower_layer_offset may be very small, so we need to do max and then add 0.1 + lower_layer_offseted = offset_ex(lower_layer_offseted, scale_(std::max(extrusion_width - lower_layer_offset, 0.) + 0.1)); + for (ExPolygon& poly : overhangs_all_layers[layer_nr]) { + auto cluster_boundary_ex = intersection_ex(poly, lower_layer_offseted); + Polygons cluster_boundary = to_polygons(cluster_boundary_ex); + if (cluster_boundary.empty()) continue; + double dist_max = 0; + for (auto& pt : poly.contour.points) { + double dist_pt = std::numeric_limits::max(); + for (auto& ply : cluster_boundary) { + double d = ply.distance_to(pt); + dist_pt = std::min(dist_pt, d); } + dist_max = std::max(dist_max, dist_pt); + } + if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base + max_cantilever_dist = std::max(max_cantilever_dist, dist_max); + layer->cantilevers.emplace_back(poly); + BOOST_LOG_TRIVIAL(debug) << "found a cantilever cluster. layer_nr=" << layer_nr << dist_max; + has_cantilever = true; } } } @@ -952,15 +876,16 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) ); // end tbb::parallel_for BOOST_LOG_TRIVIAL(info) << "max_cantilever_dist=" << max_cantilever_dist; + if (check_support_necessity) + return; // check if the sharp tails should be extended higher - if (is_auto(stype) && g_config_support_sharp_tails && !detect_first_sharp_tail_only) { + if (is_auto(stype) && config_detect_sharp_tails) { for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { if (m_object->print()->canceled()) break; Layer* layer = m_object->get_layer(layer_nr); - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); Layer* lower_layer = layer->lower_layer; if (!lower_layer) continue; @@ -968,7 +893,7 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) // BBS detect sharp tail const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails; const auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height; - for (ExPolygon& expoly : layer->lslices) { + for (ExPolygon& expoly : layer->lslices_extrudable) { bool is_sharp_tail = false; float accum_height = layer->height; do { @@ -998,9 +923,9 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) } // 2.3 check whether sharp tail exceed the max height - for (const auto& lower_sharp_tail_height : lower_layer_sharptails_height) { - if (lower_sharp_tail_height.first->overlaps(expoly)) { - accum_height += lower_sharp_tail_height.second; + for(size_t i=0;ilslices); layer->sharp_tails.push_back(expoly); - layer->sharp_tails_height.insert({ &expoly, accum_height }); - append(ts_layer->overhang_areas, overhang); - - if (!overhang.empty()) - has_sharp_tails = true; -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - SVG svg(format("SVG/sharp_tail_%.02f.svg",layer->print_z), m_object->bounding_box()); - if (svg.is_opened()) svg.draw(overhang, "red"); -#endif + layer->sharp_tails_height.push_back( accum_height); } - } + } } } - + // group overhang clusters for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { if (m_object->print()->canceled()) break; - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); Layer* layer = m_object->get_layer(layer_nr); - for (auto& overhang : ts_layer->overhang_areas) { + for (auto& overhang : overhangs_all_layers[layer_nr]) { OverhangCluster* cluster = find_and_insert_cluster(overhangClusters, overhang, layer_nr, extrusion_width_scaled); - if (overlaps({ overhang },layer->cantilevers)) + if (overlaps({ overhang }, layer->cantilevers)) cluster->is_cantilever = true; } } auto enforcers = m_object->slice_support_enforcers(); auto blockers = m_object->slice_support_blockers(); - m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers); + m_vertical_enforcer_points.clear(); + m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers, &m_vertical_enforcer_points); m_object->project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); + if (is_auto(stype) && config_remove_small_overhangs) { - if (blockers.size() < m_object->layer_count()) - blockers.resize(m_object->layer_count()); + // remove small overhangs for (auto& cluster : overhangClusters) { // 3. check whether the small overhang is sharp tail cluster.is_sharp_tail = false; @@ -1081,106 +997,127 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) #ifdef SUPPORT_TREE_DEBUG_TO_SVG const Layer* layer1 = m_object->get_layer(cluster.min_layer); - BoundingBox bbox = cluster.merged_bbox; - bbox.merge(get_extents(layer1->lslices)); - SVG svg(format("SVG/overhangCluster_%s-%s_%s-%s_tail=%s_cantilever=%s_small=%s.svg", + std::string fname = debug_out_path("overhangCluster_%d-%d_%.2f-%.2f_tail=%d_cantilever=%d_small=%d.svg", cluster.min_layer, cluster.max_layer, layer1->print_z, m_object->get_layer(cluster.max_layer)->print_z, - cluster.is_sharp_tail, cluster.is_cantilever, cluster.is_small_overhang), bbox); - if (svg.is_opened()) { - svg.draw(layer1->lslices, "red"); - svg.draw(cluster.merged_poly, "blue"); - svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), "lslices", "red", 2); - svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), "overhang", "blue", 2); - } + cluster.is_sharp_tail, cluster.is_cantilever, cluster.is_small_overhang); + SVG::export_expolygons(fname, { + { layer1->lslices, {"min_layer_lslices","red",0.5} }, + { m_object->get_layer(cluster.max_layer)->lslices, {"max_layer_lslices","yellow",0.5} }, + { cluster.merged_poly,{"overhang", "blue", 0.5} }, + { cluster.is_cantilever? layer1->cantilevers: offset_ex(cluster.merged_poly, -1 * extrusion_width_scaled), {cluster.is_cantilever ? "cantilever":"erode1","green",0.5}} }); #endif + } + } - if (!cluster.is_small_overhang) - continue; - - for (auto it = cluster.layer_overhangs.begin(); it != cluster.layer_overhangs.end(); it++) { - int layer_nr = it->first; - auto p_overhang = it->second; - blockers[layer_nr].push_back(p_overhang->contour); - } + for (auto& cluster : overhangClusters) { + if (cluster.is_small_overhang) continue; + // collect overhangs that's not small overhangs + for (auto it = cluster.layer_overhangs.begin(); it != cluster.layer_overhangs.end(); it++) { + int layer_nr = it->first; + auto p_overhang = it->second; + m_object->get_layer(layer_nr)->loverhangs.emplace_back(*p_overhang); } } - has_overhangs = false; + int layers_with_overhangs = 0; + int layers_with_enforcers = 0; for (int layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { if (m_object->print()->canceled()) break; - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); auto layer = m_object->get_layer(layer_nr); auto lower_layer = layer->lower_layer; - if (support_critical_regions_only && is_auto(stype)) { - ts_layer->overhang_areas.clear(); - if (lower_layer == nullptr) - ts_layer->overhang_areas = layer->sharp_tails; - else - ts_layer->overhang_areas = diff_ex(layer->sharp_tails, lower_layer->lslices); - append(ts_layer->overhang_areas, layer->cantilevers); + // add support for every 1mm height for sharp tails + ExPolygons sharp_tail_overhangs; + if (lower_layer == nullptr) + sharp_tail_overhangs = layer->sharp_tails; + else { + ExPolygons lower_layer_expanded = offset_ex(lower_layer->lslices_extrudable, SCALED_RESOLUTION); + for (size_t i = 0; i < layer->sharp_tails_height.size();i++) { + ExPolygons areas = diff_clipped({ layer->sharp_tails[i]}, lower_layer_expanded); + float accum_height = layer->sharp_tails_height[i]; + if (!areas.empty() && int(accum_height * 10) % 5 == 0) { + append(sharp_tail_overhangs, areas); + has_sharp_tails = true; +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + SVG::export_expolygons(debug_out_path("sharp_tail_%.02f.svg", layer->print_z), areas); +#endif + } + } } if (layer_nr < blockers.size()) { - Polygons& blocker = blockers[layer_nr]; - // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, + // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, // which are not valid polygons, and will be removed by offset_ex. union_ can make these polygons right. - ts_layer->overhang_areas = diff_ex(ts_layer->overhang_areas, offset_ex(union_(blocker), scale_(radius_sample_resolution))); + ExPolygons blocker = offset_ex(union_(blockers[layer_nr]), scale_(radius_sample_resolution)); + layer->loverhangs = diff_ex(layer->loverhangs, blocker); + layer->cantilevers = diff_ex(layer->cantilevers, blocker); + sharp_tail_overhangs = diff_ex(sharp_tail_overhangs, blocker); } - if (max_bridge_length > 0 && ts_layer->overhang_areas.size() > 0 && lower_layer) { - // do not break bridge for normal part in TreeHybrid - bool break_bridge = !(support_style == smsTreeHybrid && area(ts_layer->overhang_areas) > m_support_params.thresh_big_overhang); - m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &ts_layer->overhang_areas, max_bridge_length, break_bridge); + if (support_critical_regions_only && is_auto(stype)) { + layer->loverhangs.clear(); // remove oridinary overhangs, only keep cantilevers and sharp tails (added later) + append(layer->loverhangs, layer->cantilevers); } - for (auto &area : ts_layer->overhang_areas) { - ts_layer->overhang_types.emplace(&area, SupportLayer::Detected); + if (max_bridge_length > 0 && layer->loverhangs.size() > 0 && lower_layer) { + // do not break bridge as the interface will be poor, see #4318 + bool break_bridge = false; + m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &layer->loverhangs, max_bridge_length, break_bridge); } + + int nDetected = layer->loverhangs.size(); // enforcers now follow same logic as normal support. See STUDIO-3692 if (layer_nr < enforcers.size() && lower_layer) { - float no_interface_offset = std::accumulate(layer->regions().begin(), layer->regions().end(), FLT_MAX, - [](float acc, const LayerRegion* layerm) { return std::min(acc, float(layerm->flow(frExternalPerimeter).scaled_width())); }); - Polygons lower_layer_polygons = (layer_nr == 0) ? Polygons() : to_polygons(lower_layer->lslices); - Polygons& enforcer = enforcers[layer_nr]; - if (!enforcer.empty()) { - ExPolygons enforcer_polygons = diff_ex(intersection_ex(layer->lslices, enforcer), - // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. - expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - append(ts_layer->overhang_areas, enforcer_polygons); - ts_layer->overhang_types.emplace(&ts_layer->overhang_areas.back(), SupportLayer::Enforced); + ExPolygons enforced_overhangs = intersection_ex(diff_ex(layer->lslices_extrudable, lower_layer->lslices_extrudable), enforcers[layer_nr]); + if (!enforced_overhangs.empty()) { + // FIXME this is a hack to make enforcers work on steep overhangs. See STUDIO-7538. + enforced_overhangs = diff_ex(offset_ex(enforced_overhangs, enforcer_overhang_offset), lower_layer->lslices_extrudable); + append(layer->loverhangs, enforced_overhangs); } } + int nEnforced = layer->loverhangs.size(); + + // add sharp tail overhangs + append(layer->loverhangs, sharp_tail_overhangs); - if (!ts_layer->overhang_areas.empty()) has_overhangs = true; + // fill overhang_types + for (size_t i = 0; i < layer->loverhangs.size(); i++) + overhang_types.emplace(&layer->loverhangs[i], i < nDetected ? OverhangType::Detected : + i < nEnforced ? OverhangType::Enforced : OverhangType::SharpTail); + + if (!layer->loverhangs.empty()) { + layers_with_overhangs++; + m_highest_overhang_layer = std::max(m_highest_overhang_layer, size_t(layer_nr)); + } + if (nEnforced > 0) layers_with_enforcers++; if (!layer->cantilevers.empty()) has_cantilever = true; } + BOOST_LOG_TRIVIAL(info) << "Tree support overhang detection done. " << layers_with_overhangs << " layers with overhangs. nEnforced=" << layers_with_enforcers; + #ifdef SUPPORT_TREE_DEBUG_TO_SVG - for (const SupportLayer* layer : m_object->support_layers()) { - if (layer->overhang_areas.empty() && (blockers.size()<=layer->id() || blockers[layer->id()].empty())) + for (const Layer* layer : m_object->layers()) { + if (layer->loverhangs.empty() && (blockers.size()<=layer->id() || blockers[layer->id()].empty())) continue; - SVG svg(format("SVG/overhang_areas_%s.svg", layer->print_z), m_object->bounding_box()); - if (svg.is_opened()) { - svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow"); - svg.draw(layer->overhang_areas, "orange"); - if (blockers.size() > layer->id()) - svg.draw(blockers[layer->id()], "red"); - } + SVG::export_expolygons(debug_out_path("overhang_areas_%d_%.2f.svg",layer->id(), layer->print_z), { + { m_object->get_layer(layer->id())->lslices_extrudable, {"lslices_extrudable","yellow",0.5} }, + { layer->loverhangs, {"overhang","red",0.5} } + }); + if (enforcers.size() > layer->id()) { SVG svg(format("SVG/enforcer_%s.svg", layer->print_z), m_object->bounding_box()); if (svg.is_opened()) { - svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow"); + svg.draw_outline(m_object->get_layer(layer->id())->lslices_extrudable, "yellow"); svg.draw(enforcers[layer->id()], "red"); } } if (blockers.size() > layer->id()) { SVG svg(format("SVG/blocker_%s.svg", layer->print_z), m_object->bounding_box()); if (svg.is_opened()) { - svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow"); + svg.draw_outline(m_object->get_layer(layer->id())->lslices_extrudable, "yellow"); svg.draw(blockers[layer->id()], "red"); } } @@ -1188,36 +1125,52 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) #endif } +// create support layers for raft and tree support +// if support is disabled, only raft layers are created void TreeSupport::create_tree_support_layers() { int layer_id = 0; - coordf_t raft_print_z = 0.f; - coordf_t raft_slice_z = 0.f; - for (; layer_id < m_slicing_params.base_raft_layers; layer_id++) { - raft_print_z += m_slicing_params.base_raft_layer_height; - raft_slice_z = raft_print_z - m_slicing_params.base_raft_layer_height / 2; - m_object->add_tree_support_layer(layer_id, m_slicing_params.base_raft_layer_height, raft_print_z, raft_slice_z); - } - - for (; layer_id < m_slicing_params.base_raft_layers + m_slicing_params.interface_raft_layers; layer_id++) { - raft_print_z += m_slicing_params.interface_raft_layer_height; - raft_slice_z = raft_print_z - m_slicing_params.interface_raft_layer_height / 2; - m_object->add_tree_support_layer(layer_id, m_slicing_params.base_raft_layer_height, raft_print_z, raft_slice_z); - } - - for (Layer *layer : m_object->layers()) { - SupportLayer* ts_layer = m_object->add_tree_support_layer(layer->id(), layer->height, layer->print_z, layer->slice_z); - if (ts_layer->id() > m_raft_layers) { - SupportLayer* lower_layer = m_object->get_support_layer(ts_layer->id() - 1); - lower_layer->upper_layer = ts_layer; - ts_layer->lower_layer = lower_layer; - } + if (m_raft_layers > 0) { //create raft layers + coordf_t raft_print_z = 0.f; + coordf_t raft_slice_z = 0.f; + { + // Do not add the raft contact layer, 1st layer should use first_print_layer_height + coordf_t height = m_slicing_params.first_print_layer_height; + raft_print_z += height; + raft_slice_z = raft_print_z - height / 2; + m_object->add_tree_support_layer(layer_id++, height, raft_print_z, raft_slice_z); + } + // Insert the base layers. + for (size_t i = 1; i < m_slicing_params.base_raft_layers; i++) { + coordf_t height = m_slicing_params.base_raft_layer_height; + raft_print_z += height; + raft_slice_z = raft_print_z - height / 2; + m_object->add_tree_support_layer(layer_id++, height, raft_print_z, raft_slice_z); + } + // Insert the interface layers. + for (size_t i = 0; i < m_slicing_params.interface_raft_layers; i++) { + coordf_t height = m_slicing_params.interface_raft_layer_height; + raft_print_z += height; + raft_slice_z = raft_print_z - height / 2; + m_object->add_tree_support_layer(layer_id++, height, raft_print_z, raft_slice_z); + } + + // Layers between the raft contacts and bottom of the object. + double dist_to_go = m_slicing_params.object_print_z_min - raft_print_z; + auto nsteps = int(ceil(dist_to_go / m_slicing_params.max_suport_layer_height)); + double height = dist_to_go / nsteps; + for (int i = 0; i < nsteps; ++i) { + raft_print_z += height; + raft_slice_z = raft_print_z - height / 2; + m_object->add_tree_support_layer(layer_id++, height, raft_print_z, raft_slice_z); + } + m_raft_layers = layer_id; } } static inline BoundingBox fill_expolygon_generate_paths( ExtrusionEntitiesPtr &dst, - ExPolygon &&expolygon, + ExPolygon &expolygon, Fill *filler, const FillParams &fill_params, ExtrusionRole role, @@ -1244,7 +1197,7 @@ static inline BoundingBox fill_expolygon_generate_paths( static inline std::vector fill_expolygons_generate_paths( ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, + ExPolygons &expolygons, Fill *filler, const FillParams &fill_params, ExtrusionRole role, @@ -1252,7 +1205,7 @@ static inline std::vector fill_expolygons_generate_paths( { std::vector fill_boxes; for (ExPolygon& expoly : expolygons) { - auto box = fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, role, flow); + auto box = fill_expolygon_generate_paths(dst, expoly, filler, fill_params, role, flow); fill_boxes.emplace_back(box); } return fill_boxes; @@ -1289,50 +1242,9 @@ static void _make_loops(ExtrusionEntitiesPtr& loops_entities, ExPolygons &suppor expoly_list.erase(first_iter); } - // draw connected loops - if (/*wall_count > 1 && wall_count<5*/0) { - // TODO this method may drop some contours - wall_count = std::min(wall_count, loops.size()); - Polylines polylines; - polylines.push_back(Polyline()); - Polyline& polyline = polylines.back(); - Point end_pt; - Point end_dir; - for (int wall_idx = 0; wall_idx < wall_count; wall_idx++) { - Polygon &loop = loops[wall_idx]; - if (loop.size()<3) continue; - // break the closed loop if this is not the last loop, so the next loop can attach to the end_pt - //if (wall_idx != wall_count - 1 && loop.first_point() == loop.last_point()) - // loop.points.pop_back(); - - if (wall_idx == 0) { - polyline.append(loop.points); - } else { - double d = loop.distance_to(end_pt); - if (d < scale_(2)) { // if current loop is close to the previous one - polyline.append(end_pt); - ExtrusionPath expath; - expath.polyline.append(loop.points); - ExtrusionLoop extru_loop(expath); - extru_loop.split_at(end_pt, false); - polyline.append(extru_loop.as_polyline()); - }else{// create a new polylie if they are far away - polylines.push_back(Polyline()); - polyline = polylines.back(); - polyline.append(loop.points); - } - } - end_pt = polyline.points.back(); - end_dir = end_pt - polyline.points[polyline.points.size() - 2]; - Point perpendicular_dir = turn90_ccw(end_dir); - end_pt = end_pt + normal(perpendicular_dir, flow.scaled_spacing()); - } + extrusion_entities_append_loops(loops_entities, std::move(loops), role, float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); - extrusion_entities_append_paths(loops_entities, polylines, role, float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); - } else { - extrusion_entities_append_loops(loops_entities, std::move(loops), role, float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); - } - } +} static void make_perimeter_and_inner_brim(ExtrusionEntitiesPtr &dst, const ExPolygon &support_area, size_t wall_count, const Flow &flow, ExtrusionRole role) { @@ -1341,7 +1253,7 @@ static void make_perimeter_and_inner_brim(ExtrusionEntitiesPtr &dst, const ExPol _make_loops(dst, support_area_new, role, wall_count, flow); } -static void make_perimeter_and_infill(ExtrusionEntitiesPtr& dst, const Print& print, const ExPolygon& support_area, size_t wall_count, const Flow& flow, ExtrusionRole role, Fill* filler_support, double support_density, bool infill_first=true) +static void make_perimeter_and_infill(ExtrusionEntitiesPtr& dst, const ExPolygon& support_area, size_t wall_count, const Flow& flow, ExtrusionRole role, Fill* filler_support, double support_density, bool infill_first=true) { Polygons loops; ExPolygons support_area_new = offset_ex(support_area, -0.5f * float(flow.scaled_spacing()), jtSquare); @@ -1350,8 +1262,8 @@ static void make_perimeter_and_infill(ExtrusionEntitiesPtr& dst, const Print& pr FillParams fill_params; fill_params.density = support_density; fill_params.dont_adjust = true; - ExPolygons to_infill = support_area_new; - std::vector fill_boxes = fill_expolygons_generate_paths(dst, std::move(to_infill), filler_support, fill_params, role, flow); + ExPolygons to_infill = offset_ex(support_area, -float(wall_count) * float(flow.scaled_spacing()), jtSquare); + std::vector fill_boxes = fill_expolygons_generate_paths(dst, to_infill, filler_support, fill_params, role, flow); // allow wall_count to be zero, which means only draw infill if (wall_count == 0) { @@ -1383,11 +1295,12 @@ static void make_perimeter_and_infill(ExtrusionEntitiesPtr& dst, const Print& pr if (infill_first) dst.insert(dst.end(), loops_entities.begin(), loops_entities.end()); - else { // loops first + else { // loops first loops_entities.insert(loops_entities.end(), dst.begin(), dst.end()); dst = std::move(loops_entities); } } + dst.erase(std::remove_if(dst.begin(), dst.end(), [](ExtrusionEntity *entity) { return static_cast(entity)->empty(); }), dst.end()); if (infill_first) { // sort regions to reduce travel Points ordering_points; @@ -1448,60 +1361,85 @@ void TreeSupport::generate_toolpaths() raft_areas = std::move(offset_ex(raft_areas, scale_(object_config.raft_first_layer_expansion))); - // generate raft tool path - if (m_raft_layers > 0) - { - ExtrusionRole raft_contour_er = m_slicing_params.base_raft_layers > 0 ? erSupportMaterial : erSupportMaterialInterface; - SupportLayer *ts_layer = m_object->support_layers().front(); - Flow flow = m_object->print()->brim_flow(); - - Polygons loops; - for (const ExPolygon& expoly : raft_areas) { - loops.push_back(expoly.contour); - loops.insert(loops.end(), expoly.holes.begin(), expoly.holes.end()); - } - extrusion_entities_append_loops(ts_layer->support_fills.entities, std::move(loops), raft_contour_er, - float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); - raft_areas = offset_ex(raft_areas, -flow.scaled_spacing() / 2.); - } - - for (size_t layer_nr = 0; layer_nr < m_slicing_params.base_raft_layers; layer_nr++) { + size_t layer_nr = 0; + for (; layer_nr < m_slicing_params.base_raft_layers; layer_nr++) { SupportLayer *ts_layer = m_object->get_support_layer(layer_nr); - coordf_t expand_offset = (layer_nr == 0 ? 0. : -1.); + coordf_t expand_offset = (layer_nr == 0 ? m_object_config->raft_first_layer_expansion.value : 0.); + auto raft_areas1 = offset_ex(raft_areas, scale_(expand_offset)); - Flow support_flow = layer_nr == 0 ? m_object->print()->brim_flow() : Flow(support_extrusion_width, ts_layer->height, nozzle_diameter); - Fill* filler_interface = Fill::new_from_type(ipRectilinear); - filler_interface->angle = layer_nr == 0 ? 90 : 0; - filler_interface->spacing = support_extrusion_width; + Flow support_flow = Flow(support_extrusion_width, ts_layer->height, nozzle_diameter); + Fill* filler_raft = Fill::new_from_type(ipRectilinear); + filler_raft->angle = layer_nr == 0 ? PI/2 : 0; + filler_raft->spacing = support_flow.spacing(); FillParams fill_params; - fill_params.density = object_config.raft_first_layer_density * 0.01; + coordf_t raft_density = std::min(1., support_flow.spacing() / (object_config.support_base_pattern_spacing.value + support_flow.spacing())); + fill_params.density = layer_nr == 0 ? object_config.raft_first_layer_density * 0.01 : raft_density; fill_params.dont_adjust = true; - fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(offset_ex(raft_areas, scale_(expand_offset))), - filler_interface, fill_params, erSupportMaterial, support_flow); + // wall of first layer raft + if (layer_nr == 0) { + Flow flow = Flow(support_extrusion_width, ts_layer->height, nozzle_diameter); + extrusion_entities_append_loops(ts_layer->support_fills.entities, to_polygons(raft_areas1), erSupportMaterial, + float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); + raft_areas1 = offset_ex(raft_areas1, -flow.scaled_spacing() / 2.); + } + fill_expolygons_generate_paths(ts_layer->support_fills.entities, raft_areas1, + filler_raft, fill_params, erSupportMaterial, support_flow); } - for (size_t layer_nr = m_slicing_params.base_raft_layers; + // subtract the non-raft support bases, otherwise we'll get support base on top of raft interfaces which is not stable + ExPolygons first_non_raft_base; + SupportLayer* first_non_raft_layer = m_object->get_support_layer(m_raft_layers); + if (first_non_raft_layer) { + for (auto& area_group : first_non_raft_layer->area_groups) { + if (area_group.type == SupportLayer::BaseType) + first_non_raft_base.emplace_back(*area_group.area); + } + } + first_non_raft_base = offset_ex(first_non_raft_base, support_extrusion_width); + ExPolygons raft_base_areas = intersection_ex(raft_areas, first_non_raft_base); + ExPolygons raft_interface_areas = diff_ex(raft_areas, raft_base_areas); + + + // raft interfaces + for (layer_nr = m_slicing_params.base_raft_layers; layer_nr < m_slicing_params.base_raft_layers + m_slicing_params.interface_raft_layers; layer_nr++) { SupportLayer *ts_layer = m_object->get_support_layer(layer_nr); - coordf_t expand_offset = (layer_nr == 0 ? 0. : -1.); Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter); Fill* filler_interface = Fill::new_from_type(ipRectilinear); - filler_interface->angle = 0; - filler_interface->spacing = support_extrusion_width; + filler_interface->angle = PI / 2; // interface should be perpendicular to base + filler_interface->spacing = support_flow.spacing(); FillParams fill_params; fill_params.density = interface_density; fill_params.dont_adjust = true; - fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(offset_ex(raft_areas, scale_(expand_offset))), + fill_expolygons_generate_paths(ts_layer->support_fills.entities, raft_interface_areas, filler_interface, fill_params, erSupportMaterialInterface, support_flow); + + fill_params.density = object_config.raft_first_layer_density * 0.01; + fill_expolygons_generate_paths(ts_layer->support_fills.entities, raft_base_areas, + filler_interface, fill_params, erSupportMaterial, support_flow); } + // layers between raft and object + for (; layer_nr < m_raft_layers; layer_nr++) { + SupportLayer *ts_layer = m_object->get_support_layer(layer_nr); + Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter); + Fill* filler_raft = Fill::new_from_type(ipRectilinear); + filler_raft->angle = PI / 2; + filler_raft->spacing = support_flow.spacing(); + for (auto& poly : first_non_raft_base) + make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::min(size_t(1), wall_count), support_flow, erSupportMaterial, filler_raft, interface_density, false); + } + + if (m_object->support_layer_count() <= m_raft_layers) + return; + BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); std::shared_ptr filler_interface = std::shared_ptr(Fill::new_from_type(m_support_params.contact_fill_pattern)); @@ -1520,10 +1458,11 @@ void TreeSupport::generate_toolpaths() if (m_object->print()->canceled()) break; - m_object->print()->set_status(70, (boost::format(_L("Support: generate toolpath at layer %d")) % layer_id).str()); + //m_object->print()->set_status(70, (boost::format(_u8L("Support: generate toolpath at layer %d")) % layer_id).str()); SupportLayer* ts_layer = m_object->get_support_layer(layer_id); Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter); + Flow interface_flow = support_material_interface_flow(m_object, ts_layer->height); // update flow using real support layer height coordf_t support_spacing = object_config.support_base_pattern_spacing.value + support_flow.spacing(); coordf_t support_density = std::min(1., support_flow.spacing() / support_spacing); ts_layer->support_fills.no_sort = false; @@ -1552,10 +1491,10 @@ void TreeSupport::generate_toolpaths() // roof_1st_layer fill_params.density = interface_density; // Note: spacing means the separation between two lines as if they are tightly extruded - filler_Roof1stLayer->spacing = m_support_material_interface_flow.spacing(); + filler_Roof1stLayer->spacing = interface_flow.spacing(); // generate a perimeter first to support interface better ExtrusionEntityCollection* temp_support_fills = new ExtrusionEntityCollection(); - make_perimeter_and_infill(temp_support_fills->entities, *m_object->print(), poly, 1, m_support_material_interface_flow, erSupportMaterial, + make_perimeter_and_infill(temp_support_fills->entities, poly, 1, interface_flow, erSupportMaterial, filler_Roof1stLayer.get(), interface_density, false); temp_support_fills->no_sort = true; // make sure loops are first if (!temp_support_fills->entities.empty()) @@ -1565,21 +1504,22 @@ void TreeSupport::generate_toolpaths() } else if (area_group.type == SupportLayer::FloorType) { // floor_areas fill_params.density = bottom_interface_density; - filler_interface->spacing = m_support_material_interface_flow.spacing(); - fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(polys), - filler_interface.get(), fill_params, erSupportMaterialInterface, m_support_material_interface_flow); + filler_interface->spacing = interface_flow.spacing(); + fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, + filler_interface.get(), fill_params, erSupportMaterialInterface, interface_flow); } else if (area_group.type == SupportLayer::RoofType) { // roof_areas fill_params.density = interface_density; - filler_interface->spacing = m_support_material_interface_flow.spacing(); + filler_interface->spacing = interface_flow.spacing(); if (m_object_config->support_interface_pattern == smipGrid) { filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value); fill_params.dont_sort = true; } if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) - filler_interface->layer_id = round(area_group.dist_to_top / ts_layer->height); - fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(polys), filler_interface.get(), fill_params, erSupportMaterialInterface, - m_support_material_interface_flow); + filler_interface->layer_id = area_group.interface_id; + + fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, filler_interface.get(), fill_params, erSupportMaterialInterface, + interface_flow); } else { // base_areas @@ -1587,29 +1527,33 @@ void TreeSupport::generate_toolpaths() bool need_infill = with_infill; if(m_object_config->support_base_pattern==smpDefault) need_infill &= area_group.need_infill; - if (layer_id>0 && area_group.dist_to_top < 10 && !need_infill && support_style!=smsTreeHybrid) { - if (area_group.dist_to_top < 5) // 1 wall at the top <5mm - make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, 1, flow, erSupportMaterial); - else // at least 2 walls for range [5,10) - make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, std::max(wall_count, size_t(2)), flow, erSupportMaterial); - } - else if (layer_id > 0 && need_infill && m_support_params.base_fill_pattern != ipLightning) { - std::shared_ptr filler_support = std::shared_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); - filler_support->set_bounding_box(bbox_object); - filler_support->spacing = object_config.support_base_pattern_spacing.value * support_density;// constant spacing to align support infill lines - filler_support->angle = Geometry::deg2rad(object_config.support_angle.value); - - // allow infill-only mode if support is thick enough (so min_wall_count is 0); - // otherwise must draw 1 wall - size_t min_wall_count = offset(poly, -scale_(support_spacing * 1.5)).empty() ? 1 : 0; - make_perimeter_and_infill(ts_layer->support_fills.entities, *m_object->print(), poly, std::max(min_wall_count, wall_count), flow, - erSupportMaterial, filler_support.get(), support_density); + std::shared_ptr filler_support = std::shared_ptr(Fill::new_from_type(layer_id == 0 ? ipConcentric : m_support_params.base_fill_pattern)); + filler_support->set_bounding_box(bbox_object); + filler_support->spacing = object_config.support_base_pattern_spacing.value * support_density;// constant spacing to align support infill lines + filler_support->angle = Geometry::deg2rad(object_config.support_angle.value); + + Polygons loops = to_polygons(poly); + if (layer_id == 0) { + float density = float(m_object_config->raft_first_layer_density.value * 0.01); + fill_expolygons_with_sheath_generate_paths(ts_layer->support_fills.entities, loops, filler_support.get(), density, erSupportMaterial, flow, + m_support_params, true, false); } else { - make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, - layer_id > 0 ? wall_count : std::numeric_limits::max(), flow, erSupportMaterial); + if (need_infill && m_support_params.base_fill_pattern != ipLightning) { + // allow infill-only mode if support is thick enough (so min_wall_count is 0); + // otherwise must draw 1 wall + // Don't need extra walls if we have infill. Extra walls may overlap with the infills. + size_t min_wall_count = offset(poly, -scale_(support_spacing * 1.5)).empty() ? 1 : 0; + make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::max(min_wall_count, wall_count), flow, + erSupportMaterial, filler_support.get(), support_density); + } + else { + SupportParameters support_params = m_support_params; + if (area_group.need_extra_wall && object_config.tree_support_wall_count.value == 0) + support_params.tree_branch_diameter_double_wall_area_scaled = 0.1; + tree_supports_generate_paths(ts_layer->support_fills.entities, loops, flow, support_params); + } } - } } if (m_support_params.base_fill_pattern == ipLightning) @@ -1654,7 +1598,7 @@ void TreeSupport::generate_toolpaths() float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); #ifdef SUPPORT_TREE_DEBUG_TO_SVG - std::string name = "./SVG/trees_polyline_" + std::to_string(ts_layer->print_z) /*+ "_" + std::to_string(rand_num)*/ + ".svg"; + std::string name = debug_out_path("trees_polyline_%.2f.svg", ts_layer->print_z); BoundingBox bbox = get_extents(ts_layer->base_areas); SVG svg(name, bbox); if (svg.is_opened()) { @@ -1667,276 +1611,100 @@ void TreeSupport::generate_toolpaths() } // sort extrusions to reduce travel, also make sure walls go before infills - if(ts_layer->support_fills.no_sort==false) + if (ts_layer->support_fills.no_sort == false) { chain_and_reorder_extrusion_entities(ts_layer->support_fills.entities); + } } } ); } -Polygons TreeSupport::spanning_tree_to_polygon(const std::vector& spanning_trees, Polygons layer_contours, int layer_nr) +void deleteDirectoryContents(const std::filesystem::path& dir) { - Polygons polys; - auto& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr]; - for (MinimumSpanningTree mst : spanning_trees) { - std::vector points = mst.vertices(); - if (points.size() == 0) - continue; - std::map visited; - for (int i=0;i to_ignore; - for (int i = 0; i < points.size(); i++) { - if (visited[points[i]] == true) - continue; - - Polygon poly; - bool has_next = true; - Point pt1 = points[i]; - poly.points.push_back(pt1); - visited[pt1] = true; - - while (has_next) { - const std::vector& neighbours = mst.adjacent_nodes(pt1); - if (neighbours.empty()) - { - break; - } - - double min_ccw = std::numeric_limits::max(); - Point pt_selected = neighbours[0]; - has_next = false; - for (Point pt2 : neighbours) { - if (to_ignore.find(Line(pt1, pt2)) == to_ignore.end()) { - auto iter = mst_line_x_layer_contour_cache.find({ pt1,pt2 }); - if (iter != mst_line_x_layer_contour_cache.end()) { - if (iter->second) - continue; - } - else { - Polylines pls; - pls.emplace_back(pt1, pt2); - Polylines pls_intersect = intersection_pl(pls, layer_contours); - mst_line_x_layer_contour_cache.insert({ {pt1, pt2}, !pls_intersect.empty() }); - mst_line_x_layer_contour_cache.insert({ {pt2, pt1}, !pls_intersect.empty() }); - if (!pls_intersect.empty()) - continue; - } - - if (poly.points.size() < 2 || visited[pt2]==false) - { - pt_selected = pt2; - has_next = true; - break; - } - double curr_ccw = pt2.ccw(pt1, poly.points.back()); - if (curr_ccw < min_ccw) - { - min_ccw = curr_ccw; - pt_selected = pt2; - has_next = true; - } - } - } - if (has_next) { - poly.points.push_back(pt_selected); - to_ignore.insert(Line(pt1, pt_selected)); - visited[pt_selected] = true; - pt1 = pt_selected; - } - } - polys.emplace_back(std::move(poly)); - } - } - return polys; + for (const auto& entry : std::filesystem::directory_iterator(dir)) + std::filesystem::remove_all(entry.path()); } -Polygons TreeSupport::contact_nodes_to_polygon(const std::vector& contact_nodes, Polygons layer_contours, int layer_nr, std::vector& radiis, std::vector& is_interface) -{ - Polygons polys; - std::vector spanning_trees; - std::vector radiis_mtree; - std::vector is_interface_mtree; - // generate minimum spanning trees - { - std::map visited; - for (int i = 0; i < contact_nodes.size(); i++) - visited.emplace(contact_nodes[i], false); - std::unordered_set to_ignore; - - // generate minimum spaning trees - for (int i = 0; i < contact_nodes.size(); i++) { - Node* node = contact_nodes[i]; - if (visited[node]) - continue; - std::vector points_to_mstree; - double radius = 0; - Point pt1 = node->position; - points_to_mstree.push_back(pt1); - visited[node] = true; - radius += node->radius; - - for (int j = i + 1; j < contact_nodes.size(); j++) { - Node* node2 = contact_nodes[j]; - Point pt2 = node2->position; - // connect to this neighbor if: - // 1) both are interface or both are not - // 3) not readly added - // 4) won't cross perimeters: this is not right since we need to check all possible connections - if ((node->support_roof_layers_below > 0) == (node2->support_roof_layers_below > 0) - && to_ignore.find(Line(pt1, pt2)) == to_ignore.end()) - { - points_to_mstree.emplace_back(pt2); - visited[node2] = true; - radius += node2->radius; +void TreeSupport::move_bounds_to_contact_nodes(std::vector &move_bounds, + PrintObject &print_object, + const TreeSupport3D::TreeSupportSettings &config) +{ + m_ts_data = print_object.alloc_tree_support_preview_cache(); + // convert move_bounds back to Support Nodes for tree skeleton preview + this->contact_nodes.resize(move_bounds.size()); + for (int layer_nr = move_bounds.size() - 1; layer_nr >= 0; layer_nr--) { + TreeSupport3D::SupportElements &elements = move_bounds[layer_nr]; + auto &contact_nodes_layer = this->contact_nodes[layer_nr]; + for (size_t i = 0; i < elements.size(); i++) { + auto &elem = elements[i]; + auto &state = elem.state; + state.print_z = layer_z(print_object.slicing_parameters(), config, layer_nr); + auto node = this->create_node(state.result_on_layer, state.distance_to_top, layer_nr, 0, state.to_buildplate, nullptr, state.print_z, config.layer_height); + contact_nodes_layer.push_back(node); + if (layer_nr < move_bounds.size() - 1 && !elem.parents.empty()) { + node->parent = contact_nodes[layer_nr + 1][elem.parents.front()]; + for (int j : elem.parents) { + contact_nodes[layer_nr + 1][j]->child = node; + node->parents.push_back(contact_nodes[layer_nr + 1][j]); } } - spanning_trees.emplace_back(points_to_mstree); - radiis_mtree.push_back(radius / points_to_mstree.size()); - is_interface_mtree.push_back(node->support_roof_layers_below > 0); - } - } - auto lines = spanning_tree_to_lines(spanning_trees); -#if 1 - // convert mtree to polygon - for (int k = 0; k < spanning_trees.size(); k++) { - auto& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr]; - MinimumSpanningTree mst = spanning_trees[k]; - std::vector points = mst.vertices(); - std::map visited; - for (int i = 0; i < points.size(); i++) - visited.emplace(points[i], false); - - std::unordered_set to_ignore; - for (int i = 0; i < points.size(); i++) { - if (visited[points[i]]) - continue; - - Polygon poly; - Point pt1 = points[i]; - poly.points.push_back(pt1); - visited[pt1] = true; - - bool has_next = true; - while (has_next) - { - const std::vector& neighbours = mst.adjacent_nodes(pt1); - double min_ccw = -std::numeric_limits::max(); - Point pt_selected; - has_next = false; - for (Point pt2 : neighbours) { - if (to_ignore.find(Line(pt1, pt2)) == to_ignore.end()) { - auto iter = mst_line_x_layer_contour_cache.find({ pt1,pt2 }); - if (iter != mst_line_x_layer_contour_cache.end()) { - if (iter->second) - continue; - } - else { - Polylines pls; - pls.emplace_back(pt1, pt2); - Polylines pls_intersect = intersection_pl(pls, layer_contours); - mst_line_x_layer_contour_cache.insert({ {pt1, pt2}, !pls_intersect.empty() }); - mst_line_x_layer_contour_cache.insert({ {pt2, pt1}, !pls_intersect.empty() }); - if (!pls_intersect.empty()) - continue; - } - if (poly.points.size() < 2) - { - pt_selected = pt2; - has_next = true; - break; - } - double curr_ccw = pt2.ccw(pt1, poly.points.rbegin()[1]); - if (curr_ccw > min_ccw) - { - has_next = true; - min_ccw = curr_ccw; - pt_selected = pt2; - } - } - } - if (!has_next) - break; - - poly.points.push_back(pt_selected); - to_ignore.insert(Line(pt1, pt_selected)); - visited[pt_selected] = true; - pt1 = pt_selected; - } - polys.emplace_back(std::move(poly)); - radiis.push_back(radiis_mtree[k]); - is_interface.push_back(is_interface_mtree[k]); + if (i == 0) { m_ts_data->layer_heights.emplace_back(state.print_z, config.layer_height, std::max(0, layer_nr - 1)); } } } -#else - polys = spanning_tree_to_polygon(spanning_trees, layer_contours, layer_nr, radiis); -#endif - return polys; } - void TreeSupport::generate() { - if (support_style == smsOrganic) { + if (!is_tree(m_object_config->support_type.value)) return; + + if (m_support_params.support_style == smsTreeOrganic) { generate_tree_support_3D(*m_object, this, this->throw_on_cancel); return; } - std::vector> contact_nodes(m_object->layers().size()); - profiler.stage_start(STAGE_total); // Generate overhang areas profiler.stage_start(STAGE_DETECT_OVERHANGS); - m_object->print()->set_status(55, _L("Support: detect overhangs")); + m_object->print()->set_status(55, _u8L("Generating support")); detect_overhangs(); profiler.stage_finish(STAGE_DETECT_OVERHANGS); - if (!has_overhangs) return; - + create_tree_support_layers(); m_ts_data = m_object->alloc_tree_support_preview_cache(); m_ts_data->is_slim = is_slim; + // // get the ring of outside plate + // auto tmp= diff_ex(offset_ex(m_machine_border, scale_(100)), m_machine_border); + // if (!tmp.empty()) m_ts_data->m_machine_border = tmp[0]; - // Generate contact points of tree support + std::vector move_bounds(m_highest_overhang_layer + 1); profiler.stage_start(STAGE_GENERATE_CONTACT_NODES); - m_object->print()->set_status(56, _L("Support: generate contact points")); - generate_contact_points(contact_nodes); + m_object->print()->set_status(56, _u8L("Support: generate contact points")); + generate_contact_points(); profiler.stage_finish(STAGE_GENERATE_CONTACT_NODES); + m_ts_data->layer_heights = plan_layer_heights(); + //Drop nodes to lower layers. profiler.stage_start(STAGE_DROP_DOWN_NODES); - m_object->print()->set_status(60, _L("Support: propagate branches")); - drop_nodes(contact_nodes); + m_object->print()->set_status(60, _u8L("Generating support")); + drop_nodes(); profiler.stage_finish(STAGE_DROP_DOWN_NODES); - smooth_nodes(contact_nodes); - -if (!m_object->config().tree_support_adaptive_layer_height) - // Adjust support layer heights - adjust_layer_heights(contact_nodes); - + smooth_nodes();// , tree_support_3d_config); //Generate support areas. profiler.stage_start(STAGE_DRAW_CIRCLES); - m_object->print()->set_status(65, _L("Support: draw polygons")); - draw_circles(contact_nodes); + m_object->print()->set_status(65, _u8L("Generating support")); + draw_circles(); profiler.stage_finish(STAGE_DRAW_CIRCLES); - for (auto& layer : contact_nodes) - { - for (Node* p_node : layer) - { - delete p_node; - } - layer.clear(); - } - contact_nodes.clear(); + profiler.stage_start(STAGE_GENERATE_TOOLPATHS); - m_object->print()->set_status(69, _L("Support: generate toolpath")); + m_object->print()->set_status(70, _u8L("Generating support")); generate_toolpaths(); profiler.stage_finish(STAGE_GENERATE_TOOLPATHS); @@ -1959,13 +1727,12 @@ coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, size_t layers_to_ } else { radius = base_radius * (layers_to_top + 1) / (tip_layers * 2); } - radius = std::max(radius, MIN_BRANCH_RADIUS); } - radius = std::min(radius, MAX_BRANCH_RADIUS); + radius = std::clamp(radius, MIN_BRANCH_RADIUS, MAX_BRANCH_RADIUS); return radius; } -coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor) +coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor, bool use_min_distance) { double radius; coordf_t tip_height = base_radius;// this is a 45 degree tip @@ -1977,31 +1744,92 @@ coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, coordf_t mm_to_to { radius = mm_to_top;// this is a 45 degree tip } - - radius = std::max(radius, MIN_BRANCH_RADIUS); - radius = std::min(radius, MAX_BRANCH_RADIUS); + radius = std::clamp(radius, MIN_BRANCH_RADIUS, MAX_BRANCH_RADIUS); // if have interface layers, radius should be larger if (m_object_config->support_interface_top_layers.value > 0) radius = std::max(radius, base_radius); return radius; } -template // RegionType could be ExPolygons or Polygons -ExPolygons avoid_object_remove_extra_small_parts(ExPolygons &expolys, const RegionType&avoid_region) { +coordf_t TreeSupport::calc_radius(coordf_t mm_to_top) +{ + return calc_branch_radius(base_radius, mm_to_top, diameter_angle_scale_factor); +} + +coordf_t TreeSupport::get_radius(const SupportNode* node) +{ + if (node->radius == 0) + node->radius = calc_radius(node->dist_mm_to_top); + return node->radius; +} + +ExPolygons TreeSupport::get_avoidance(coordf_t radius, size_t obj_layer_nr) +{ +#if USE_SUPPORT_3D + if (m_model_volumes) { + bool on_build_plate = m_object_config->support_on_build_plate_only.value; + const Polygons& avoid_polys = m_model_volumes->getAvoidance(radius, obj_layer_nr, TreeSupport3D::TreeModelVolumes::AvoidanceType::FastSafe, on_build_plate, true); + ExPolygons expolys; + for (auto& poly : avoid_polys) + expolys.emplace_back(std::move(poly)); + return expolys; + } + return ExPolygons(); +#else + return m_ts_data->get_avoidance(radius, obj_layer_nr); +#endif +} +ExPolygons TreeSupport::get_collision(coordf_t radius, size_t layer_nr) +{ +#if USE_SUPPORT_3D + if (m_model_volumes) { + bool on_build_plate = m_object_config->support_on_build_plate_only.value; + const Polygons& collision_polys = m_model_volumes->getCollision(radius, layer_nr, true); + ExPolygons expolys; + for (auto& poly : collision_polys) + expolys.emplace_back(std::move(poly)); + return expolys; + } +#else + return m_ts_data->get_collision(radius, layer_nr); +#endif + return ExPolygons(); +} +Polygons TreeSupport::get_collision_polys(coordf_t radius, size_t layer_nr) +{ +#if USE_SUPPORT_3D + if (m_model_volumes) { + bool on_build_plate = m_object_config->support_on_build_plate_only.value; + const Polygons& collision_polys = m_model_volumes->getCollision(radius, layer_nr, true); + return collision_polys; + } +#else + ExPolygons expolys = m_ts_data->get_collision(radius, layer_nr); + Polygons polys; + for (auto& expoly : expolys) + for (int i = 0; i < expoly.num_contours(); i++) + polys.emplace_back(std::move(expoly.contour_or_hole(i))); + return polys; +#endif + return Polygons(); +} + +ExPolygons avoid_object_remove_extra_small_parts(const ExPolygon &expoly, const ExPolygons& avoid_region) { ExPolygons expolys_out; - for (auto expoly : expolys) { - auto expolys_avoid = diff_ex(expoly, avoid_region); - int idx_max_area = -1; - float max_area = 0; - for (int i = 0; i < expolys_avoid.size(); ++i) { - auto a = expolys_avoid[i].area(); - if (a > max_area) { - max_area = a; - idx_max_area = i; - } + if(expoly.empty()) return expolys_out; + auto clipped_avoid_region=ClipperUtils::clip_clipper_polygons_with_subject_bbox(avoid_region, get_extents(expoly)); + auto expolys_avoid = diff_ex(expoly, clipped_avoid_region); + int idx_max_area = -1; + float max_area = 0; + for (int i = 0; i < expolys_avoid.size(); ++i) { + auto a = expolys_avoid[i].area(); + if (a > max_area) { + max_area = a; + idx_max_area = i; } - if (idx_max_area >= 0) expolys_out.emplace_back(std::move(expolys_avoid[idx_max_area])); } + if (idx_max_area >= 0) expolys_out.emplace_back(std::move(expolys_avoid[idx_max_area])); + return expolys_out; } @@ -2081,7 +1909,7 @@ Polygons TreeSupport::get_trim_support_regions( return polygons_trimming; } -void TreeSupport::draw_circles(const std::vector>& contact_nodes) +void TreeSupport::draw_circles() { const PrintObjectConfig &config = m_object->config(); const Print* print = m_object->print(); @@ -2089,12 +1917,12 @@ void TreeSupport::draw_circles(const std::vector>& contact_no int bottom_gap_layers = round(m_slicing_params.gap_object_support / m_slicing_params.layer_height); const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2; const coordf_t branch_radius_scaled = scale_(branch_radius); - bool on_buildplate_only = config.support_on_build_plate_only.value; + bool on_buildplate_only = m_object_config->support_on_build_plate_only.value; Polygon branch_circle; //Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time. // Use square support if there are too many nodes per layer because circle support needs much longer time to compute // Hower circle support can be printed faster, so we prefer circle for fewer nodes case. - const bool SQUARE_SUPPORT = avg_node_per_layer > 200; + const bool SQUARE_SUPPORT = avg_node_per_layer > 200; const int CIRCLE_RESOLUTION = SQUARE_SUPPORT ? 4 : 100; // The number of vertices in each circle. @@ -2121,21 +1949,18 @@ void TreeSupport::draw_circles(const std::vector>& contact_no const coordf_t layer_height = config.layer_height.value; const size_t top_interface_layers = config.support_interface_top_layers.value; const size_t bottom_interface_layers = config.support_interface_bottom_layers.value; - const double diameter_angle_scale_factor = tan(tree_support_branch_diameter_angle * M_PI / 180.);// * layer_height / branch_radius; //Scale factor per layer to produce the desired angle. const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0); const coordf_t line_width = config.get_abs_value("support_line_width", nozzle_diameter); const coordf_t line_width_scaled = scale_(line_width); - const bool with_lightning_infill = m_support_params.base_fill_pattern == ipLightning; coordf_t support_extrusion_width = m_support_params.support_extrusion_width; - const size_t wall_count = config.tree_support_wall_count.value; + const float tree_brim_width = config.tree_support_brim_width.value; - const PrintObjectConfig& object_config = m_object->config(); + if (m_object->support_layer_count() <= m_raft_layers) + return; BOOST_LOG_TRIVIAL(info) << "draw_circles for object: " << m_object->model_object()->name; - // coconut: previously std::unordered_map in m_collision_cache is not multi-thread safe which may cause programs stuck, here we change to tbb::concurrent_unordered_map - tbb::parallel_for( - tbb::blocked_range(0, m_object->layer_count()), + tbb::parallel_for(tbb::blocked_range(0, m_ts_data->layer_heights.size()), [&](const tbb::blocked_range& range) { for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) @@ -2143,7 +1968,7 @@ void TreeSupport::draw_circles(const std::vector>& contact_no if (print->canceled()) break; - const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; + const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); assert(ts_layer != nullptr); @@ -2154,9 +1979,9 @@ void TreeSupport::draw_circles(const std::vector>& contact_no continue; } - Node* first_node = curr_layer_nodes.front(); - ts_layer->print_z = first_node->print_z; - ts_layer->height = first_node->height; + ts_layer->print_z = m_ts_data->layer_heights[layer_nr].print_z; + ts_layer->height = m_ts_data->layer_heights[layer_nr].height; + size_t obj_layer_nr = m_ts_data->layer_heights[layer_nr].obj_layer_nr; if (ts_layer->height < EPSILON) { continue; } @@ -2169,89 +1994,113 @@ void TreeSupport::draw_circles(const std::vector>& contact_no coordf_t max_layers_above_base = 0; coordf_t max_layers_above_roof = 0; coordf_t max_layers_above_roof1 = 0; - bool has_polygon_node = false; + int interface_id = 0; bool has_circle_node = false; + bool need_extra_wall = false; + ExPolygons collision_sharp_tails; + ExPolygons collision_base; + auto get_collision = [&](bool sharp_tail) -> ExPolygons &{ + ExPolygons &collision = sharp_tail ? collision_sharp_tails : collision_base; + if (collision.empty()) { + collision = offset_ex(m_ts_data->m_layer_outlines[obj_layer_nr], + sharp_tail ? scale_(top_z_distance) : + scale_((obj_layer_nr == 0) ? config.support_object_first_layer_gap : m_ts_data->m_xy_distance)); + // the top layers may be too close to interface with adaptive layer heights and very small overhang angle + if (top_z_distance > EPSILON) { + float accum_height = 0; + for (size_t layer_id = obj_layer_nr + 1; + layer_id < m_ts_data->m_layer_outlines.size() && (accum_height += m_object->get_layer(layer_id)->height) && accum_height <= top_z_distance; + layer_id++) { + collision = union_ex(collision, offset_ex(m_ts_data->m_layer_outlines[layer_id], scale_(top_z_distance))); + } + } + } + return collision; + }; - BOOST_LOG_TRIVIAL(debug) << "circles at layer " << layer_nr << " contact nodes size=" << contact_nodes[layer_nr].size(); + BOOST_LOG_TRIVIAL(debug) << "circles at layer " << layer_nr << " contact nodes size=" << curr_layer_nodes.size(); //Draw the support areas and add the roofs appropriately to the support roof instead of normal areas. - ts_layer->lslices.reserve(contact_nodes[layer_nr].size()); - for (const Node* p_node : contact_nodes[layer_nr]) + ts_layer->lslices.reserve(curr_layer_nodes.size()); + ExPolygons area_poly; // the polygon node area which will be printed as normal support + for (const SupportNode* p_node : curr_layer_nodes) { if (print->canceled()) break; - const Node& node = *p_node; + const SupportNode& node = *p_node; ExPolygons area; // Generate directly from overhang polygon if one of the following is true: // 1) node is a normal part of hybrid support // 2) node is virtual - if (node.type == ePolygon || node.distance_to_top<0) { - if (node.overhang->contour.size() > 100 || node.overhang->holes.size()>1) - area.emplace_back(*node.overhang); + if (node.type == ePolygon || (node.distance_to_top<0 && !node.is_sharp_tail)) { + if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1) + area.emplace_back(node.overhang); else { - area = offset_ex({ *node.overhang }, scale_(m_ts_data->m_xy_distance)); + area = offset_ex({ node.overhang }, scale_(m_ts_data->m_xy_distance)); } - if (node.type == ePolygon) - has_polygon_node = true; + area = diff_clipped(area, get_collision(node.is_sharp_tail && node.distance_to_top <= 0)); + if (node.type == ePolygon) append(area_poly, area); } else { - Polygon circle; - size_t layers_to_top = node.distance_to_top; - double scale = calc_branch_radius(branch_radius, node.dist_mm_to_top, diameter_angle_scale_factor) / branch_radius; + Polygon circle(branch_circle); + double scale = node.radius / branch_radius; + double moveX = node.movement.x() / (scale * branch_radius_scaled); + double moveY = node.movement.y() / (scale * branch_radius_scaled); + //BOOST_LOG_TRIVIAL(debug) << format("scale,moveX,moveY: %.3f,%.3f,%.3f", scale, moveX, moveY); - if (/*is_slim*/1) { // draw ellipse along movement direction - double moveX = node.movement.x() / (scale * branch_radius_scaled); - double moveY = node.movement.y() / (scale * branch_radius_scaled); + if (!SQUARE_SUPPORT && std::abs(moveX)>0.001 && std::abs(moveY)>0.001) { // draw ellipse along movement direction const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); double matrix[2*2] = { scale * (1 + moveX * moveX * vsize_inv),scale * (0 + moveX * moveY * vsize_inv), scale * (0 + moveX * moveY * vsize_inv),scale * (1 + moveY * moveY * vsize_inv), }; + int i = 0; for (auto vertex: branch_circle.points) { vertex = Point(matrix[0] * vertex.x() + matrix[1] * vertex.y(), matrix[2] * vertex.x() + matrix[3] * vertex.y()); - circle.append(node.position + vertex); + circle.points[i++] = node.position + vertex; } } else { - for (auto iter = branch_circle.points.begin(); iter != branch_circle.points.end(); iter++) { - Point corner = (*iter) * scale; - circle.append(node.position + corner); + for (int i = 0;i< circle.points.size(); i++) { + circle.points[i] = circle.points[i] * scale + node.position; } } - if (layer_nr == 0 && m_raft_layers == 0) { - double brim_width = - config.tree_support_auto_brim - ? layers_to_top * layer_height / - (scale * branch_radius) * 0.5 - : config.tree_support_brim_width; - circle = offset(circle, scale_(brim_width))[0]; + if (obj_layer_nr == 0 && m_raft_layers == 0) { + double brim_width = !config.tree_support_auto_brim ? tree_brim_width : std::max(MIN_BRANCH_RADIUS_FIRST_LAYER, std::min(node.radius + node.dist_mm_to_top / (scale * branch_radius) * 0.5, MAX_BRANCH_RADIUS_FIRST_LAYER) - node.radius); + auto tmp=offset(circle, scale_(brim_width)); + if(!tmp.empty()) + circle = tmp[0]; } - area.emplace_back(ExPolygon(circle)); + area = avoid_object_remove_extra_small_parts(ExPolygon(circle), get_collision(node.is_sharp_tail && node.distance_to_top <= 0)); + // area = diff_clipped({ ExPolygon(circle) }, get_collision(node.is_sharp_tail && node.distance_to_top <= 0)); + + if (!area.empty()) has_circle_node = true; + if (node.need_extra_wall) need_extra_wall = true; + // merge overhang to get a smoother interface surface // Do not merge when buildplate_only is on, because some underneath nodes may have been deleted. - if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !on_buildplate_only) { + if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !on_buildplate_only && !node.is_sharp_tail) { ExPolygons overhang_expanded; - if (node.overhang->contour.size() > 100 || node.overhang->holes.size()>1) - overhang_expanded.emplace_back(*node.overhang); + if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1) + overhang_expanded.emplace_back(node.overhang); else { - // 对于有缺陷的模型,overhang膨胀以后可能是空的! - overhang_expanded = offset_ex({ *node.overhang }, scale_(m_ts_data->m_xy_distance)); + overhang_expanded = offset_ex({ node.overhang }, scale_(m_ts_data->m_xy_distance)); } append(area, overhang_expanded); } - has_circle_node = true; } - if (node.distance_to_top < 0) + if (obj_layer_nr>0 && node.distance_to_top < 0) append(roof_gap_areas, area); - else if (node.support_roof_layers_below == 1) + else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 && node.is_sharp_tail==false) { append(roof_1st_layer, area); max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top); } - else if (node.support_roof_layers_below > 0) + else if (obj_layer_nr > 0 && node.support_roof_layers_below > 0 && node.is_sharp_tail == false) { append(roof_areas, area); max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top); + interface_id = node.obj_layer_nr % top_interface_layers; } else { @@ -2259,49 +2108,21 @@ void TreeSupport::draw_circles(const std::vector>& contact_no max_layers_above_base = std::max(max_layers_above_base, node.dist_mm_to_top); } - if (layer_nr < brim_skirt_layers) - append(ts_layer->lslices, area); } - ts_layer->lslices = std::move(union_ex(ts_layer->lslices)); - - //Must update bounding box which is used in avoid crossing perimeter - ts_layer->lslices_bboxes.clear(); - ts_layer->lslices_bboxes.reserve(ts_layer->lslices.size()); - for (const ExPolygon &expoly : ts_layer->lslices) - ts_layer->lslices_bboxes.emplace_back(get_extents(expoly)); - ts_layer->backup_untyped_slices(); - - m_object->print()->set_status(65, (boost::format( _L("Support: generate polygons at layer %d")) % layer_nr).str()); + //m_object->print()->set_status(65, (boost::format( _u8L("Support: generate polygons at layer %d")) % layer_nr).str()); // join roof segments - double contact_dist_scaled = scale_(0.5);// scale_(m_slicing_params.gap_support_object); - roof_areas = std::move(offset2_ex(roof_areas, contact_dist_scaled, -contact_dist_scaled)); - roof_1st_layer = std::move(offset2_ex(roof_1st_layer, contact_dist_scaled, -contact_dist_scaled)); - - // avoid object - //ExPolygons avoid_region_interface = m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr); - Polygons avoid_region_interface = get_trim_support_regions(*m_object, ts_layer, m_slicing_params.gap_object_support, m_slicing_params.gap_support_object, m_ts_data->m_xy_distance); - if (has_circle_node) { - roof_areas = avoid_object_remove_extra_small_parts(roof_areas, avoid_region_interface); - roof_1st_layer = avoid_object_remove_extra_small_parts(roof_1st_layer, avoid_region_interface); - } - else { - roof_areas = std::move(diff_ex(roof_areas, avoid_region_interface)); - roof_1st_layer = std::move(diff_ex(roof_1st_layer, avoid_region_interface)); - } - roof_areas = intersection_ex(roof_areas, m_machine_border); + roof_areas = diff_clipped(offset2_ex(roof_areas, line_width_scaled, -line_width_scaled), get_collision(false)); + roof_areas = intersection_ex(roof_areas, m_machine_border); + roof_1st_layer = diff_clipped(offset2_ex(roof_1st_layer, line_width_scaled, -line_width_scaled), get_collision(false)); // roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer - roof_1st_layer = std::move(diff_ex(roof_1st_layer, roof_areas)); + roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer))); roof_1st_layer = intersection_ex(roof_1st_layer, m_machine_border); - // let supports touch objects when brim is on - auto avoid_region = m_ts_data->get_collision((layer_nr == 0 && has_brim) ? config.brim_object_gap : m_ts_data->m_xy_distance, layer_nr); - base_areas = avoid_object_remove_extra_small_parts(base_areas, avoid_region); - base_areas = std::move(diff_ex(base_areas, roof_areas)); - base_areas = std::move(diff_ex(base_areas, roof_1st_layer)); - base_areas = std::move(diff_ex(base_areas, roof_gap_areas)); + ExPolygons roofs; append(roofs, roof_1st_layer); append(roofs, roof_areas);append(roofs, roof_gap_areas); + base_areas = diff_ex(base_areas, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roofs, get_extents(base_areas))); base_areas = intersection_ex(base_areas, m_machine_border); if (SQUARE_SUPPORT) { @@ -2316,34 +2137,50 @@ void TreeSupport::draw_circles(const std::vector>& contact_no { if (layer_nr >= bottom_interface_layers + bottom_gap_layers) { - for (size_t i = 0; i <= bottom_gap_layers; i++) + // find the lowest interface layer + // TODO the gap may not be exact when "independent support layer height" is enabled + size_t layer_nr_next = layer_nr - bottom_interface_layers; + size_t obj_layer_nr_next = m_ts_data->layer_heights[layer_nr_next].obj_layer_nr; + for (size_t i = 0; i <= bottom_gap_layers && i <= obj_layer_nr_next; i++) { - const Layer* below_layer = m_object->get_layer(layer_nr - bottom_interface_layers - i); + const Layer *below_layer = m_object->get_layer(obj_layer_nr_next - i); ExPolygons bottom_interface = intersection_ex(base_areas, below_layer->lslices); floor_areas.insert(floor_areas.end(), bottom_interface.begin(), bottom_interface.end()); } } if (floor_areas.empty() == false) { - floor_areas = std::move(diff_ex(floor_areas, avoid_region_interface)); - floor_areas = std::move(offset2_ex(floor_areas, contact_dist_scaled, -contact_dist_scaled)); + //floor_areas = std::move(diff_ex(floor_areas, avoid_region_interface)); + //floor_areas = std::move(offset2_ex(floor_areas, contact_dist_scaled, -contact_dist_scaled)); base_areas = std::move(diff_ex(base_areas, offset_ex(floor_areas, 10))); } } - if (bottom_gap_layers > 0 && layer_nr > bottom_gap_layers) { - const Layer* below_layer = m_object->get_layer(layer_nr - bottom_gap_layers); + if (bottom_gap_layers > 0 && m_ts_data->layer_heights[layer_nr].obj_layer_nr > bottom_gap_layers) { + const Layer* below_layer = m_object->get_layer(m_ts_data->layer_heights[layer_nr].obj_layer_nr - bottom_gap_layers); ExPolygons bottom_gap_area = intersection_ex(floor_areas, below_layer->lslices); if (!bottom_gap_area.empty()) { floor_areas = std::move(diff_ex(floor_areas, bottom_gap_area)); } } auto &area_groups = ts_layer->area_groups; - for (auto& area : ts_layer->base_areas) { - area_groups.emplace_back(&area, SupportLayer::BaseType, max_layers_above_base); - area_groups.back().need_infill = has_polygon_node; + for (auto& expoly : ts_layer->base_areas) { + //if (area(expoly) < SQ(scale_(1))) continue; + area_groups.emplace_back(&expoly, SupportLayer::BaseType, max_layers_above_base); + area_groups.back().need_infill = overlaps({ expoly }, area_poly); + area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill; + } + for (auto& expoly : ts_layer->roof_areas) { + //if (area(expoly) < SQ(scale_(1))) continue; + area_groups.emplace_back(&expoly, SupportLayer::RoofType, max_layers_above_roof); + area_groups.back().interface_id = interface_id; + } + for (auto &expoly : ts_layer->floor_areas) { + //if (area(expoly) < SQ(scale_(1))) continue; + area_groups.emplace_back(&expoly, SupportLayer::FloorType, 10000); + } + for (auto &expoly : ts_layer->roof_1st_layer) { + //if (area(expoly) < SQ(scale_(1))) continue; + area_groups.emplace_back(&expoly, SupportLayer::Roof1stLayer, max_layers_above_roof1); } - for (auto &area : ts_layer->roof_areas) area_groups.emplace_back(&area, SupportLayer::RoofType, max_layers_above_roof); - for (auto &area : ts_layer->floor_areas) area_groups.emplace_back(&area, SupportLayer::FloorType, 10000); - for (auto &area : ts_layer->roof_1st_layer) area_groups.emplace_back(&area, SupportLayer::Roof1stLayer, max_layers_above_roof1); for (auto &area_group : area_groups) { auto& expoly = area_group.area; @@ -2353,8 +2190,19 @@ void TreeSupport::draw_circles(const std::vector>& contact_no return bbox_size[0] < scale_(2) && bbox_size[1] < scale_(2); }), expoly->holes.end()); + + if (layer_nr < brim_skirt_layers) + ts_layer->lslices.emplace_back(*expoly); } + ts_layer->lslices = std::move(union_ex(ts_layer->lslices)); + //Must update bounding box which is used in avoid crossing perimeter + ts_layer->lslices_bboxes.clear(); + ts_layer->lslices_bboxes.reserve(ts_layer->lslices.size()); + for (const ExPolygon& expoly : ts_layer->lslices) + ts_layer->lslices_bboxes.emplace_back(get_extents(expoly)); + ts_layer->backup_untyped_slices(); + } }); @@ -2365,9 +2213,9 @@ void TreeSupport::draw_circles(const std::vector>& contact_no std::vector contours; std::vector overhangs; - for (int layer_nr = 1; layer_nr < m_object->layer_count(); layer_nr++) { + for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) { if (print->canceled()) break; - const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; + const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); assert(ts_layer != nullptr); @@ -2414,15 +2262,11 @@ void TreeSupport::draw_circles(const std::vector>& contact_no overhangs.emplace_back(to_polygons(overhang)); contours.emplace_back(to_polygons(base_areas_lower)); printZ_to_lightninglayer[lower_layer->print_z] = overhangs.size() - 1; - -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - draw_two_overhangs_to_svg(m_object->get_support_layer(layer_nr_lower + m_raft_layers), base_areas_lower, to_expolygons(overhangs.back())); -#endif } auto m_support_material_flow = support_material_flow(m_object, m_slicing_params.layer_height); - coordf_t support_spacing = object_config.support_base_pattern_spacing.value + m_support_material_flow.spacing(); + coordf_t support_spacing = m_object_config->support_base_pattern_spacing.value + m_support_material_flow.spacing(); coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing * 2); // for lightning infill the density is defined differently, so need to double it generator = std::make_unique(m_object, contours, overhangs, []() {}, support_density); } @@ -2432,6 +2276,8 @@ void TreeSupport::draw_circles(const std::vector>& contact_no // check if poly's contour intersects with expoly's contour auto intersects_contour = [](Polygon poly, ExPolygon expoly, Point& pt_on_poly, Point& pt_on_expoly, Point& pt_far_on_poly, float dist_thresh = 0.01) { + Polylines pl_out = intersection_pl(to_polylines(expoly), ExPolygon(poly)); + if (pl_out.empty()) return false; float min_dist = std::numeric_limits::max(); float max_dist = 0; for (auto from : poly.points) { @@ -2455,11 +2301,11 @@ void TreeSupport::draw_circles(const std::vector>& contact_no // polygon pointer: depth, direction, farPoint std::map> holePropagationInfos; - for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) { + for (int layer_nr = contact_nodes.size() - 1; layer_nr > 0; layer_nr--) { if (print->canceled()) break; - m_object->print()->set_status(66, (boost::format(_L("Support: fix holes at layer %d")) % layer_nr).str()); + //m_object->print()->set_status(66, (boost::format(_u8L("Support: fix holes at layer %d")) % layer_nr).str()); - const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; + const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); assert(ts_layer != nullptr); @@ -2515,7 +2361,7 @@ void TreeSupport::draw_circles(const std::vector>& contact_no { // if roof1 interface is inside a hole, need to expand the interface for (auto& roof1 : ts_layer->roof_1st_layer) { - //if (hole.contains(roof1.contour.points.front()) && hole.contains(roof1.contour.bounding_box().center())) + //if (hole.contains(roof1.contour.points.front()) && hole.contains(roof1.contour.bounding_box().center())) bool is_inside_hole = std::all_of(roof1.contour.points.begin(), roof1.contour.points.end(), [&hole](Point& pt) { return hole.contains(pt); }); if (is_inside_hole) { Polygon hole_reoriented = hole; @@ -2529,7 +2375,7 @@ void TreeSupport::draw_circles(const std::vector>& contact_no // make sure 1) roof1 and object 2) roof1 and roof, won't intersect // Note: We can't replace roof1 directly, as we have recorded its address. // So instead we need to replace its members one by one. - auto tmp1 = diff_ex(roof1, m_ts_data->get_collision((layer_nr == 0 && has_brim) ? config.brim_object_gap : m_ts_data->m_xy_distance, layer_nr)); + auto tmp1 = diff_ex(roof1, get_collision(0, m_ts_data->layer_heights[layer_nr].obj_layer_nr)); tmp1 = diff_ex(tmp1, ts_layer->roof_areas); if (!tmp1.empty()) { roof1.contour = std::move(tmp1[0].contour); @@ -2546,15 +2392,14 @@ void TreeSupport::draw_circles(const std::vector>& contact_no #ifdef SUPPORT_TREE_DEBUG_TO_SVG - for (int layer_nr = m_object->layer_count() - 1; layer_nr >= 0; layer_nr--) { + for (int layer_nr = m_object->support_layer_count() - 1; layer_nr >= 0; layer_nr--) { SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); ExPolygons& base_areas = ts_layer->base_areas; ExPolygons& roof_areas = ts_layer->roof_areas; ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer; - ExPolygons& floor_areas = ts_layer->floor_areas; + ExPolygons roofs = roof_areas; append(roofs, roof_1st_layer); if (base_areas.empty() && roof_areas.empty() && roof_1st_layer.empty()) continue; - char fname[10]; sprintf(fname, "%d_%.2f", layer_nr, ts_layer->print_z); - draw_contours_and_nodes_to_svg("", base_areas, roof_areas, roof_1st_layer, {}, {}, get_svg_filename(fname, "circles"), {"base", "roof", "roof1st"}); + draw_contours_and_nodes_to_svg(debug_out_path("circles_%d_%.2f.svg", layer_nr, ts_layer->print_z), base_areas, roofs, ts_layer->lslices_extrudable, {}, {}, {"base", "roof", "lslices"}, {"blue","red","black"}); } #endif // SUPPORT_TREE_DEBUG_TO_SVG @@ -2567,7 +2412,9 @@ void TreeSupport::draw_circles(const std::vector>& contact_no } } -void TreeSupport::drop_nodes(std::vector>& contact_nodes) +double SupportNode::diameter_angle_scale_factor; + +void TreeSupport::drop_nodes() { const PrintObjectConfig &config = m_object->config(); // Use Minimum Spanning Tree to connect the points on each layer and move them while dropping them down. @@ -2578,90 +2425,72 @@ void TreeSupport::drop_nodes(std::vector>& contact_nodes) double tan_angle = tan(angle); // when nodes are thick, they can move further. this is the max angle const coordf_t max_move_distance = (angle < M_PI / 2) ? (coordf_t)(tan_angle * layer_height)*wall_count : std::numeric_limits::max(); const double max_move_distance2 = max_move_distance * max_move_distance; - const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2; - const size_t tip_layers = branch_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle. - const double diameter_angle_scale_factor = tan(tree_support_branch_diameter_angle * M_PI / 180.);//*layer_height / branch_radius; // Scale factor per layer to produce the desired angle. + const size_t tip_layers = base_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle. const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution; const bool support_on_buildplate_only = config.support_on_build_plate_only.value; const size_t bottom_interface_layers = config.support_interface_bottom_layers.value; const size_t top_interface_layers = config.support_interface_top_layers.value; + SupportNode::diameter_angle_scale_factor = diameter_angle_scale_factor; float DO_NOT_MOVER_UNDER_MM = is_slim ? 0 : 5; // do not move contact points under 5mm - const auto nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(m_object->config().support_interface_filament-1); - const auto support_line_width = config.support_line_width.get_abs_value(nozzle_diameter); - auto get_branch_angle = [this,&config](coordf_t radius) { - if (config.tree_support_branch_angle.value < 30.0) return config.tree_support_branch_angle.value; - return (radius - MIN_BRANCH_RADIUS) / (MAX_BRANCH_RADIUS - MIN_BRANCH_RADIUS) * (config.tree_support_branch_angle.value - 30.0) + 30.0; - }; - auto get_max_move_dist = [this, &config, branch_radius, tip_layers, diameter_angle_scale_factor, wall_count, support_extrusion_width, support_line_width](const Node *node, int power = 1) { - double move_dist = node->max_move_dist; + auto get_max_move_dist = [this, &config, tan_angle, wall_count, support_extrusion_width](const SupportNode *node, int power = 1) { if (node->max_move_dist == 0) { - if (node->radius == 0) node->radius = calc_branch_radius(branch_radius, node->dist_mm_to_top, diameter_angle_scale_factor); - double angle = config.tree_support_branch_angle.value; - if (angle > 30.0 && node->radius > MIN_BRANCH_RADIUS) - angle = (node->radius - MIN_BRANCH_RADIUS) / (MAX_BRANCH_RADIUS - MIN_BRANCH_RADIUS) * (config.tree_support_branch_angle.value - 30.0) + 30.0; - double tan_angle = tan(angle * M_PI / 180); - int wall_count_ = node->radius > 2 * support_line_width ? wall_count : 1; - node->max_move_dist = (angle < 90) ? (coordf_t) (tan_angle * node->height) * wall_count_ : std::numeric_limits::max(); - node->max_move_dist = std::min(node->max_move_dist, support_extrusion_width); - move_dist = node->max_move_dist; + node->radius = get_radius(node); + node->max_move_dist = std::min(tan_angle * node->height, support_extrusion_width); } + double move_dist = node->max_move_dist; if (power == 2) move_dist = SQ(move_dist); return move_dist; }; - m_ts_data->layer_heights = plan_layer_heights(contact_nodes); std::vector &layer_heights = m_ts_data->layer_heights; if (layer_heights.empty()) return; - std::unordered_set to_free_node_set; - m_spanning_trees.resize(contact_nodes.size()); - //m_mst_line_x_layer_contour_caches.resize(contact_nodes.size()); - - if (0) - {// get outlines below and avoidance area using tbb - // This part only takes very little time, so we disable it. + // precalculate avoidance of all possible radii. + // This will cause computing more (radius, layer_nr) pairs, but it's worth to do so since we are doning this in parallel. + if (1) { typedef std::chrono::high_resolution_clock clock_; typedef std::chrono::duration > second_; std::chrono::time_point t0{ clock_::now() }; // get all the possible radiis - std::vector > all_layer_radius(m_highest_overhang_layer+1); - std::vector> all_layer_node_dist(m_highest_overhang_layer + 1); - for (size_t layer_nr = m_highest_overhang_layer; layer_nr > 0; layer_nr--) - { - if (layer_heights[layer_nr].height < EPSILON) continue; + std::vector > all_layer_radius(contact_nodes.size()); + std::vector> all_layer_node_dist(contact_nodes.size()); + for (size_t layer_nr = contact_nodes.size() - 1; layer_nr > 0; layer_nr--) { auto& layer_radius = all_layer_radius[layer_nr]; auto& layer_node_dist = all_layer_node_dist[layer_nr]; - for (Node *p_node : contact_nodes[layer_nr]) { + for (auto* p_node : contact_nodes[layer_nr]) { layer_node_dist.emplace(p_node->dist_mm_to_top); } - size_t layer_nr_next = layer_heights[layer_nr].next_layer_nr; - if (layer_nr_next <= m_highest_overhang_layer && layer_nr_next>0) { + size_t layer_nr_next = layer_nr - 1; + if (layer_nr_next <= contact_nodes.size() - 1 && layer_nr_next > 0) { for (auto node_dist : layer_node_dist) all_layer_node_dist[layer_nr_next].emplace(node_dist + layer_heights[layer_nr].height); } for (auto node_dist : layer_node_dist) { - layer_radius.emplace(calc_branch_radius(branch_radius, node_dist, diameter_angle_scale_factor)); + layer_radius.emplace(calc_radius(node_dist)); } } // parallel pre-compute avoidance - //tbb::parallel_for(tbb::blocked_range(1, m_highest_overhang_layer), [&](const tbb::blocked_range &range) { - //for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { - for (size_t layer_nr = 0; layer_nr < all_layer_radius.size(); layer_nr++) { - BOOST_LOG_TRIVIAL(debug) << "pre calculate_avoidance layer=" << layer_nr; - for (auto node_radius : all_layer_radius[layer_nr]) { - m_ts_data->get_avoidance(0, layer_nr); - m_ts_data->get_avoidance(node_radius, layer_nr); - } + tbb::parallel_for(tbb::blocked_range(0, contact_nodes.size() - 1), [&](const tbb::blocked_range &range) { + for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { + for (auto node_radius : all_layer_radius[layer_nr]) { + size_t obj_layer_nr= layer_heights[layer_nr].obj_layer_nr; + m_ts_data->get_avoidance(node_radius, obj_layer_nr); + get_collision(0, obj_layer_nr); + get_collision(node_radius, obj_layer_nr); } - //}); + } + }); double duration{ std::chrono::duration_cast(clock_::now() - t0).count() }; - BOOST_LOG_TRIVIAL(debug) << "before m_avoidance_cache.size()=" << m_ts_data->m_avoidance_cache.size() + BOOST_LOG_TRIVIAL(debug) << "finish pre calculate_avoidance. before m_avoidance_cache.size()=" << m_ts_data->m_avoidance_cache.size() << ", takes " << duration << " secs."; } + m_spanning_trees.resize(contact_nodes.size()); + //m_mst_line_x_layer_contour_caches.resize(contact_nodes.size()); + for (size_t layer_nr = contact_nodes.size() - 1; layer_nr > 0; layer_nr--) // Skip layer 0, since we can't drop down the vertices there. { if (m_object->print()->canceled()) @@ -2670,19 +2499,21 @@ void TreeSupport::drop_nodes(std::vector>& contact_nodes) auto& layer_contact_nodes = contact_nodes[layer_nr]; if (layer_contact_nodes.empty()) continue; - - int layer_nr_next = layer_heights[layer_nr].next_layer_nr; + + int layer_nr_next = layer_nr - 1; + coordf_t print_z = layer_heights[layer_nr].print_z; coordf_t print_z_next = layer_heights[layer_nr_next].print_z; - coordf_t height_next = layer_heights[layer_nr_next].height; + coordf_t height_next = layer_heights[layer_nr_next].height; + size_t obj_layer_nr = layer_heights[layer_nr].obj_layer_nr; + size_t obj_layer_nr_next = layer_heights[layer_nr_next].obj_layer_nr; - std::deque> unsupported_branch_leaves; // All nodes that are leaves on this layer that would result in unsupported ('mid-air') branches. - const Layer* ts_layer = m_object->get_support_layer(layer_nr); + std::deque> unsupported_branch_leaves; // All nodes that are leaves on this layer that would result in unsupported ('mid-air') branches. - m_object->print()->set_status(60, (boost::format(_L("Support: propagate branches at layer %d")) % layer_nr).str()); + m_object->print()->set_status(60 + int(10 * (1 - float(layer_nr) / contact_nodes.size())), _u8L("Generating support"));// (boost::format(_u8L("Support: propagate branches at layer %d")) % layer_nr).str()); - Polygons layer_contours = m_ts_data->get_contours_with_holes(layer_nr); + Polygons layer_contours = std::move(m_ts_data->get_contours_with_holes(obj_layer_nr)); //std::unordered_map& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr]; - std::unordered_map mst_line_x_layer_contour_cache; + tbb::concurrent_unordered_map mst_line_x_layer_contour_cache; auto is_line_cut_by_contour = [&mst_line_x_layer_contour_cache,&layer_contours](Point a, Point b) { auto iter = mst_line_x_layer_contour_cache.find({ a, b }); @@ -2696,7 +2527,7 @@ void TreeSupport::drop_nodes(std::vector>& contact_nodes) Lines pls_intersect = intersection_ln(ln, layer_contours); mst_line_x_layer_contour_cache.insert({ {a, b}, !pls_intersect.empty() }); mst_line_x_layer_contour_cache.insert({ ln, !pls_intersect.empty() }); - profiler.stage_add(STAGE_intersection_ln, true); + profiler.stage_add(STAGE_intersection_ln); if (!pls_intersect.empty()) return true; } @@ -2704,21 +2535,12 @@ void TreeSupport::drop_nodes(std::vector>& contact_nodes) }; //Group together all nodes for each part. - const ExPolygons& parts = m_ts_data->get_avoidance(0, layer_nr); - std::vector> nodes_per_part(1 + parts.size()); //All nodes that aren't inside a part get grouped together in the 0th part. - for (Node* p_node : layer_contact_nodes) + const ExPolygons& parts = m_ts_data->m_layer_outlines_below[obj_layer_nr]; + std::vector> nodes_per_part(1 + parts.size()); //All nodes that aren't inside a part get grouped together in the 0th part. + for (SupportNode* p_node : layer_contact_nodes) { - const Node& node = *p_node; + const SupportNode& node = *p_node; - if (node.distance_to_top < 0) { - // gap nodes do not merge or move - Node* next_node = new Node(p_node->position, p_node->distance_to_top + 1, layer_nr_next, p_node->support_roof_layers_below - 1, p_node->to_buildplate, p_node, - print_z_next, height_next); - get_max_move_dist(next_node); - next_node->is_merged = false; - contact_nodes[layer_nr_next].emplace_back(next_node); - continue; - } if (support_on_buildplate_only && !node.to_buildplate) //Can't rest on model and unable to reach the build plate. Then we must drop the node and leave parts unsupported. { unsupported_branch_leaves.push_front({ layer_nr, p_node }); @@ -2767,64 +2589,63 @@ void TreeSupport::drop_nodes(std::vector>& contact_nodes) profiler.tic(); //std::vector& spanning_trees = m_spanning_trees[layer_nr]; std::vector spanning_trees; - for (const std::unordered_map& group : nodes_per_part) + for (const std::unordered_map& group : nodes_per_part) { std::vector points_to_buildplate; - for (const std::pair& entry : group) + for (const std::pair& entry : group) { points_to_buildplate.emplace_back(entry.first); //Just the position of the node. } spanning_trees.emplace_back(points_to_buildplate); } - profiler.stage_add(STAGE_MinimumSpanningTree,true); + profiler.stage_add(STAGE_MinimumSpanningTree); #ifdef SUPPORT_TREE_DEBUG_TO_SVG coordf_t branch_radius_temp = 0; coordf_t max_y = std::numeric_limits::min(); - draw_layer_mst(std::to_string(ts_layer->print_z), spanning_trees, m_object->get_layer(layer_nr)->lslices); + draw_layer_mst(debug_out_path("mtree_%.2f.svg", print_z), spanning_trees, m_object->get_layer(obj_layer_nr)->lslices_extrudable); #endif for (size_t group_index = 0; group_index < nodes_per_part.size(); group_index++) { + auto& nodes_this_part = nodes_per_part[group_index]; const MinimumSpanningTree& mst = spanning_trees[group_index]; //In the first pass, merge all nodes that are close together. - std::unordered_set to_delete; - for (const std::pair& entry : nodes_per_part[group_index]) - { - Node* p_node = entry.second; - Node& node = *p_node; - if (to_delete.find(p_node) != to_delete.end()) + std::vector> nodes_vec(nodes_this_part.begin(), nodes_this_part.end()); + tbb::parallel_for_each(nodes_vec.begin(), nodes_vec.end(), [&](const std::pair& entry) { + SupportNode* p_node = entry.second; + SupportNode& node = *p_node; + if (!p_node->valid) { - continue; //Delete this node (don't create a new node for it on the next layer). + return; //Delete this node (don't create a new node for it on the next layer). } const std::vector& neighbours = mst.adjacent_nodes(node.position); if (node.type == ePolygon) { - // Remove all neighbours that are completely inside the polygon and merge them into this node. + // Remove all circle neighbours that are completely inside the polygon and merge them into this node. for (const Point &neighbour : neighbours) { - Node * neighbour_node = nodes_per_part[group_index][neighbour]; - coord_t neighbour_radius = scale_(calc_branch_radius(branch_radius, neighbour_node->dist_mm_to_top, diameter_angle_scale_factor)); + SupportNode * neighbour_node = nodes_this_part[neighbour]; + if (neighbour_node->valid == false) continue; + if (neighbour_node->type == ePolygon) continue; + coord_t neighbour_radius = scale_(neighbour_node->radius); Point pt_north = neighbour + Point(0, neighbour_radius), pt_south = neighbour - Point(0, neighbour_radius), pt_west = neighbour - Point(neighbour_radius, 0), pt_east = neighbour + Point(neighbour_radius, 0); - if (is_inside_ex(*node.overhang, neighbour) && is_inside_ex(*node.overhang, pt_north) && is_inside_ex(*node.overhang, pt_south) - && is_inside_ex(*node.overhang, pt_west) && is_inside_ex(*node.overhang, pt_east)){ + if (is_inside_ex(node.overhang, neighbour) && is_inside_ex(node.overhang, pt_north) && is_inside_ex(node.overhang, pt_south) + && is_inside_ex(node.overhang, pt_west) && is_inside_ex(node.overhang, pt_east)){ node.distance_to_top = std::max(node.distance_to_top, neighbour_node->distance_to_top); node.support_roof_layers_below = std::max(node.support_roof_layers_below, neighbour_node->support_roof_layers_below); node.dist_mm_to_top = std::max(node.dist_mm_to_top, neighbour_node->dist_mm_to_top); node.merged_neighbours.push_front(neighbour_node); node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); - node.is_merged = true; - to_delete.insert(neighbour_node); + neighbour_node->valid = false; } } - } - else if (neighbours.size() == 1 && vsize2_with_unscale(neighbours[0] - node.position) < max_move_distance2 && mst.adjacent_nodes(neighbours[0]).size() == 1 && - nodes_per_part[group_index][neighbours[0]]->type!=ePolygon) // We have just two nodes left, and they're very close, and the only neighbor is not ePolygon + } else if (neighbours.size() == 1 && vsize2_with_unscale(neighbours[0] - node.position) < get_max_move_dist(p_node, 2) && + mst.adjacent_nodes(neighbours[0]).size() == 1 && + nodes_this_part[neighbours[0]]->type!=ePolygon) // We have just two nodes left, and they're very close, and the only neighbor is not ePolygon { //Insert a completely new node and let both original nodes fade. Point next_position = (node.position + neighbours[0]) / 2; //Average position of the two nodes. - - const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.dist_mm_to_top, diameter_angle_scale_factor); - - auto avoid_layer = m_ts_data->get_avoidance(branch_radius_node, layer_nr_next); + coordf_t next_radius = calc_radius(node.dist_mm_to_top+height_next); + auto avoid_layer = get_avoidance(next_radius, obj_layer_nr_next); if (group_index == 0) { //Avoid collisions. @@ -2832,73 +2653,84 @@ void TreeSupport::drop_nodes(std::vector>& contact_nodes) move_out_expolys(avoid_layer, next_position, radius_sample_resolution + EPSILON, max_move_between_samples); } - Node* neighbour = nodes_per_part[group_index][neighbours[0]]; - Node* node_; + SupportNode* neighbour = nodes_this_part[neighbours[0]]; + SupportNode* node_parent; if (p_node->parent && neighbour->parent) - node_ = (node.dist_mm_to_top >= neighbour->dist_mm_to_top && p_node->parent) ? p_node : neighbour; + node_parent = (node.dist_mm_to_top >= neighbour->dist_mm_to_top) ? p_node : neighbour; else - node_ = p_node->parent ? p_node : neighbour; + node_parent = p_node->parent ? p_node : neighbour; // Make sure the next pass doesn't drop down either of these (since that already happened). - node_->merged_neighbours.push_front(node_ == p_node ? neighbour : p_node); - const bool to_buildplate = !is_inside_ex(m_ts_data->get_avoidance(0, layer_nr_next), next_position); - Node * next_node = new Node(next_position, node_->distance_to_top + 1, layer_nr_next, node_->support_roof_layers_below-1, to_buildplate, node_, - print_z_next, height_next); - next_node->movement = next_position - node.position; + node_parent->merged_neighbours.push_front(node_parent == p_node ? neighbour : p_node); + const bool to_buildplate = !is_inside_ex(get_collision(0, obj_layer_nr_next), next_position); + SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next, node_parent->support_roof_layers_below - 1, to_buildplate, node_parent, + print_z_next, height_next); get_max_move_dist(next_node); - next_node->is_merged = true; + m_ts_data->m_mutex.lock(); contact_nodes[layer_nr_next].push_back(next_node); - - - to_delete.insert(neighbour); - to_delete.insert(p_node); + neighbour->valid = false; + p_node->valid = false; + m_ts_data->m_mutex.unlock(); } else if (neighbours.size() > 1) //Don't merge leaf nodes because we would then incur movement greater than the maximum move distance. { //Remove all neighbours that are too close and merge them into this node. for (const Point& neighbour : neighbours) { - if (vsize2_with_unscale(neighbour - node.position) < /*max_move_distance2*/get_max_move_dist(&node,2)) + if (vsize2_with_unscale(neighbour - node.position) < get_max_move_dist(&node,2)) { - Node* neighbour_node = nodes_per_part[group_index][neighbour]; + SupportNode* neighbour_node = nodes_this_part[neighbour]; if (neighbour_node->type == ePolygon) continue; - - node.distance_to_top = std::max(node.distance_to_top, neighbour_node->distance_to_top); - node.support_roof_layers_below = std::max(node.support_roof_layers_below, neighbour_node->support_roof_layers_below); - node.dist_mm_to_top = std::max(node.dist_mm_to_top, neighbour_node->dist_mm_to_top); - node.merged_neighbours.push_front(neighbour_node); - node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); - node.is_merged = true; - to_delete.insert(neighbour_node); + // only allow bigger node to merge smaller nodes. See STUDIO-6326 + if(node.dist_mm_to_top < neighbour_node->dist_mm_to_top) continue; + + m_ts_data->m_mutex.lock(); + if (p_node->valid) + { // since we are processing all nodes in parallel, p_node may have been deleted by another thread. In this case, we should not delete neighbour_node. + node.merged_neighbours.push_front(neighbour_node); + node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); + neighbour_node->valid = false; + } + m_ts_data->m_mutex.unlock(); } } } } + ); //In the second pass, move all middle nodes. - for (const std::pair& entry : nodes_per_part[group_index]) - { - Node* p_node = entry.second; - const Node& node = *p_node; - if (to_delete.find(p_node) != to_delete.end()) + tbb::parallel_for_each(nodes_vec.begin(), nodes_vec.end(), [&](const std::pair& entry) { + + SupportNode* p_node = entry.second; + const SupportNode& node = *p_node; + if (!p_node->valid) { - continue; + return; } if (node.type == ePolygon) { // polygon node do not merge or move - const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], p_node->position); - Node * next_node = new Node(p_node->position, p_node->distance_to_top + 1, layer_nr_next, p_node->support_roof_layers_below - 1, to_buildplate, - p_node, print_z_next, height_next); - next_node->max_move_dist = 0; - next_node->is_merged = false; - contact_nodes[layer_nr_next].emplace_back(next_node); - continue; + const bool to_buildplate = true; + // keep only the part that won't be removed by the next layer + ExPolygons overhangs_next = diff_clipped({ node.overhang }, get_collision(0, obj_layer_nr_next)); + for(auto& overhang:overhangs_next) { + Point next_pt = overhang.contour.centroid(); + SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next, p_node->support_roof_layers_below - 1, + to_buildplate, p_node, print_z_next, height_next); + next_node->max_move_dist = 0; + next_node->overhang = std::move(overhang); + m_ts_data->m_mutex.lock(); + contact_nodes[layer_nr_next].emplace_back(next_node); + m_ts_data->m_mutex.unlock(); + + } + return; } //If the branch falls completely inside a collision area (the entire branch would be removed by the X/Y offset), delete it. - if (group_index > 0 && is_inside_ex(m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr), node.position)) + if (group_index > 0 && is_inside_ex(get_collision(0, obj_layer_nr), node.position)) { - const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.dist_mm_to_top, diameter_angle_scale_factor); - Point to_outside = projection_onto(m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr), node.position); + std::scoped_lock lock(m_ts_data->m_mutex); + const coordf_t branch_radius_node = get_radius(p_node); + Point to_outside = projection_onto(get_collision(0, obj_layer_nr), node.position); double dist2_to_outside = vsize2_with_unscale(node.position - to_outside); if (dist2_to_outside >= branch_radius_node * branch_radius_node) //Too far inside. { @@ -2907,48 +2739,42 @@ void TreeSupport::drop_nodes(std::vector>& contact_nodes) unsupported_branch_leaves.push_front({ layer_nr, p_node }); } else { - Node* pn = p_node; - for (int i = 0; i <= bottom_interface_layers && pn; i++, pn = pn->parent) - pn->support_floor_layers_above = bottom_interface_layers - i + 1; // +1 so the parent node has support_floor_layers_above=2 - to_delete.insert(p_node); + p_node->valid = false; } - continue; + return; } // if the link between parent and current is cut by contours, mark current as bottom contact node if (p_node->parent && intersection_ln({p_node->position, p_node->parent->position}, layer_contours).empty()==false) { - Node* pn = p_node->parent; - for (int i = 0; i <= bottom_interface_layers && pn; i++, pn = pn->parent) - pn->support_floor_layers_above = bottom_interface_layers - i + 1; - to_delete.insert(p_node); - continue; + p_node->valid = false; + return; } } Point next_layer_vertex = node.position; Point move_to_neighbor_center; std::vector moves; std::vector weights; - const std::vector neighbours = mst.adjacent_nodes(node.position); + const std::vector& neighbours = mst.adjacent_nodes(node.position); // 1. do not merge neighbors under 5mm // 2. Only merge node with single neighbor in distance between [max_move_distance, 10mm/layer_height] float dist2_to_first_neighbor = neighbours.empty() ? 0 : vsize2_with_unscale(neighbours[0] - node.position); - if (ts_layer->print_z > DO_NOT_MOVER_UNDER_MM && - (neighbours.size() > 1 || (neighbours.size() == 1 && dist2_to_first_neighbor >= max_move_distance2))) // Only nodes that aren't about to collapse. + if (node.print_z > DO_NOT_MOVER_UNDER_MM && + (neighbours.size() > 1 || (neighbours.size() == 1 && dist2_to_first_neighbor >= get_max_move_dist(p_node, 2)))) // Only nodes that aren't about to collapse. { // Move towards the average position of all neighbours. Point sum_direction(0, 0); for (const Point &neighbour : neighbours) { // do not move to the neighbor to be deleted - Node *neighbour_node = nodes_per_part[group_index][neighbour]; - if (to_delete.find(neighbour_node) != to_delete.end()) continue; + SupportNode *neighbour_node = nodes_this_part[neighbour]; + if (!neighbour_node->valid) continue; Point direction = neighbour - node.position; // do not move to neighbor that's too far away (即使以最大速度移动,在接触热床之前都无法汇聚) float dist2_to_neighbor = vsize2_with_unscale(direction); - coordf_t branch_bottom_radius = calc_branch_radius(branch_radius, node.dist_mm_to_top + node.print_z, diameter_angle_scale_factor); - coordf_t neighbour_bottom_radius = calc_branch_radius(branch_radius, neighbour_node->dist_mm_to_top + neighbour_node->print_z, diameter_angle_scale_factor); - double max_converge_distance = tan_angle * (ts_layer->print_z - DO_NOT_MOVER_UNDER_MM) + std::max(branch_bottom_radius, neighbour_bottom_radius); + coordf_t branch_bottom_radius = calc_radius(node.dist_mm_to_top + node.print_z); + coordf_t neighbour_bottom_radius = calc_radius(neighbour_node->dist_mm_to_top + neighbour_node->print_z); + double max_converge_distance = tan_angle * (p_node->print_z - DO_NOT_MOVER_UNDER_MM) + std::max(branch_bottom_radius, neighbour_bottom_radius); if (dist2_to_neighbor > max_converge_distance * max_converge_distance) continue; if (is_line_cut_by_contour(node.position, neighbour)) continue; @@ -2956,41 +2782,42 @@ void TreeSupport::drop_nodes(std::vector>& contact_nodes) if (!is_strong) sum_direction += direction * (1 / dist2_to_neighbor); else - sum_direction += direction; + sum_direction += direction; } if (!is_strong) move_to_neighbor_center = sum_direction; else { - if (vsize2_with_unscale(sum_direction) <= max_move_distance2) { + if (vsize2_with_unscale(sum_direction) <= get_max_move_dist(p_node, 2)) { move_to_neighbor_center = sum_direction; } else { - move_to_neighbor_center = normal(sum_direction, scale_(get_max_move_dist(&node))); + move_to_neighbor_center = normal(sum_direction, scale_(get_max_move_dist(p_node))); } } } - const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.dist_mm_to_top/*+node.print_z*/, diameter_angle_scale_factor); #ifdef SUPPORT_TREE_DEBUG_TO_SVG if (node.position(1) > max_y) { max_y = node.position(1); - branch_radius_temp = branch_radius_node; + branch_radius_temp = get_radius(p_node); } #endif - auto avoid_layer = m_ts_data->get_avoidance(branch_radius_node, layer_nr_next); + coordf_t next_radius = calc_radius(node.dist_mm_to_top + height_next); + auto avoidance_next = get_avoidance(next_radius, obj_layer_nr_next); - Point to_outside = projection_onto(avoid_layer, node.position); + Point to_outside = projection_onto(avoidance_next, node.position); Point direction_to_outer = to_outside - node.position; double dist2_to_outer = vsize2_with_unscale(direction_to_outer); // don't move if // 1) line of node and to_outside is cut by contour (means supports may intersect with object) // 2) it's impossible to move to build plate - if (is_line_cut_by_contour(node.position, to_outside) || dist2_to_outer > max_move_distance2 * SQ(layer_nr) || - !is_inside_ex(avoid_layer, node.position)) { + if (is_line_cut_by_contour(node.position, to_outside) || dist2_to_outer > max_move_distance2 * SQ(obj_layer_nr) || + !is_inside_ex(avoidance_next, node.position)) { // try move to outside of lower layer instead Point candidate_vertex = node.position; const coordf_t max_move_between_samples = max_move_distance + radius_sample_resolution + EPSILON; // 100 micron extra for rounding errors. - bool is_outside = move_out_expolys(avoid_layer, candidate_vertex, max_move_between_samples, max_move_between_samples); + // use get_collision instead of get_avoidance here (See STUDIO-4252) + bool is_outside = move_out_expolys(get_collision(next_radius,obj_layer_nr_next), candidate_vertex, max_move_between_samples, max_move_between_samples); if (is_outside) { direction_to_outer = candidate_vertex - node.position; dist2_to_outer = vsize2_with_unscale(direction_to_outer); @@ -3010,46 +2837,55 @@ void TreeSupport::drop_nodes(std::vector>& contact_nodes) movement = move_to_neighbor_center; // otherwise move to neighbor center first } - if (vsize2_with_unscale(movement) > get_max_move_dist(&node,2)) - movement = normal(movement, scale_(get_max_move_dist(&node))); - - // add momentum to force smooth movement - //movement = movement * 0.5 + p_node->movement * 0.5; + if (node.is_sharp_tail && node.dist_mm_to_top < 3) { + movement = normal(node.skin_direction, scale_(get_max_move_dist(&node))); + } + else if (dist2_to_outer > 0) + movement = normal(direction_to_outer, scale_(get_max_move_dist(&node))); + else + movement = normal(move_to_neighbor_center, scale_(get_max_move_dist(&node))); next_layer_vertex += movement; - if (group_index == 0) { + if (group_index == 0 && 0) { // Avoid collisions. const coordf_t max_move_between_samples = get_max_move_dist(&node, 1) + radius_sample_resolution + EPSILON; // 100 micron extra for rounding errors. - bool is_outside = move_out_expolys(avoid_layer, next_layer_vertex, radius_sample_resolution + EPSILON, max_move_between_samples); + bool is_outside = move_out_expolys(avoidance_next, next_layer_vertex, radius_sample_resolution + EPSILON, max_move_between_samples); if (!is_outside) { Point candidate_vertex = node.position; - is_outside = move_out_expolys(avoid_layer, candidate_vertex, radius_sample_resolution + EPSILON, max_move_between_samples); + is_outside = move_out_expolys(avoidance_next, candidate_vertex, radius_sample_resolution + EPSILON, max_move_between_samples); if (is_outside) { next_layer_vertex = candidate_vertex; } } } - - const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], next_layer_vertex);// !is_inside_ex(m_ts_data->get_avoidance(m_ts_data->m_xy_distance, layer_nr - 1), next_layer_vertex); - Node * next_node = new Node(next_layer_vertex, node.distance_to_top + 1, layer_nr_next, node.support_roof_layers_below - 1, to_buildplate, p_node, + auto next_collision = get_collision(0, obj_layer_nr_next); + const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex); + SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next, node.support_roof_layers_below - 1, to_buildplate, p_node, print_z_next, height_next); - next_node->movement = movement; + // don't increase radius if next node will collide partially with the object (STUDIO-7883) + to_outside = projection_onto(next_collision, next_node->position); + direction_to_outer = to_outside - node.position; + double dist_to_outer = unscale_(direction_to_outer.cast().norm()); + next_node->radius = std::max(node.radius, std::min(next_node->radius, dist_to_outer)); get_max_move_dist(next_node); - next_node->is_merged = false; + m_ts_data->m_mutex.lock(); contact_nodes[layer_nr_next].push_back(next_node); + m_ts_data->m_mutex.unlock(); } + ); } #ifdef SUPPORT_TREE_DEBUG_TO_SVG if (contact_nodes[layer_nr].empty() == false) { - draw_contours_and_nodes_to_svg((boost::format("%.2f") % contact_nodes[layer_nr][0]->print_z).str(), m_ts_data->get_avoidance(0, layer_nr), - m_ts_data->get_avoidance(branch_radius_temp, layer_nr), - m_ts_data->m_layer_outlines_below[layer_nr], - contact_nodes[layer_nr], contact_nodes[layer_nr_next], "contact_points", { "overhang","avoid","outline" }, { "blue","red","yellow" }); + draw_contours_and_nodes_to_svg(debug_out_path("contact_points_%.2f.svg", contact_nodes[layer_nr][0]->print_z), get_collision(0,obj_layer_nr_next), + get_avoidance(branch_radius_temp, obj_layer_nr), + m_ts_data->m_layer_outlines[obj_layer_nr], + contact_nodes[layer_nr], contact_nodes[layer_nr_next], { "overhang","avoid","outline" }, { "blue","red","yellow" }); - BOOST_LOG_TRIVIAL(debug) << "drop_nodes layer " << layer_nr << ", print_z=" << ts_layer->print_z; + BOOST_LOG_TRIVIAL(debug) << "drop_nodes layer->next " << layer_nr << "->" << layer_nr_next << ", print_z=" << print_z + << ", num points: " << contact_nodes[layer_nr].size() << "->" << contact_nodes[layer_nr_next].size(); for (size_t i = 0; i < std::min(size_t(5), contact_nodes[layer_nr].size()); i++) { auto &node = contact_nodes[layer_nr][i]; - BOOST_LOG_TRIVIAL(debug) << "\t node " << i << ", pos=" << node->position << ", move = " << node->movement << ", is_merged=" << node->is_merged; + BOOST_LOG_TRIVIAL(debug) << "\t node " << i << ", pos=" << node->position << ", move = " << node->movement; } } #endif @@ -3058,322 +2894,263 @@ void TreeSupport::drop_nodes(std::vector>& contact_nodes) for (;! unsupported_branch_leaves.empty(); unsupported_branch_leaves.pop_back()) { const auto& entry = unsupported_branch_leaves.back(); - Node* i_node = entry.second; + SupportNode* i_node = entry.second; for (; i_node != nullptr; i_node = i_node->parent) { size_t i_layer = i_node->obj_layer_nr; - std::vector::iterator to_erase = std::find(contact_nodes[i_layer].begin(), contact_nodes[i_layer].end(), i_node); - if (to_erase != contact_nodes[i_layer].end()) { // update the parent-child chain - if(i_node->parent) + if (i_node->parent) { i_node->parent->child = i_node->child; - if(i_node->child) + for (SupportNode* parent : i_node->parents) { + if (parent->child==i_node) + parent->child = i_node->child; + } + } + if (i_node->child) { i_node->child->parent = i_node->parent; - contact_nodes[i_layer].erase(to_erase); - to_free_node_set.insert(i_node); + i_node->child->parents.erase(std::find(i_node->child->parents.begin(), i_node->child->parents.end(), i_node)); + append(i_node->child->parents, i_node->parents); + } + i_node->is_processed = true; // mark to be deleted later - for (Node* neighbour : i_node->merged_neighbours) + for (SupportNode* neighbour : i_node->merged_neighbours) { - unsupported_branch_leaves.push_front({ i_layer, neighbour }); + if (neighbour && !neighbour->is_processed) + unsupported_branch_leaves.push_front({ i_layer, neighbour }); } } } } + for (auto &layer_contact_nodes : contact_nodes) { + if (!layer_contact_nodes.empty()) + layer_contact_nodes.erase(std::remove_if(layer_contact_nodes.begin(), layer_contact_nodes.end(), [](SupportNode *node) { return node->is_processed; }), + layer_contact_nodes.end()); + } } - - BOOST_LOG_TRIVIAL(debug) << "after m_avoidance_cache.size()=" << m_ts_data->m_avoidance_cache.size(); - for (Node *node : to_free_node_set) - { - delete node; - } - to_free_node_set.clear(); + BOOST_LOG_TRIVIAL(debug) << "after m_avoidance_cache.size()=" << m_ts_data->m_avoidance_cache.size(); } -void TreeSupport::smooth_nodes(std::vector> &contact_nodes) +void TreeSupport::smooth_nodes() { for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { - std::vector &curr_layer_nodes = contact_nodes[layer_nr]; + std::vector &curr_layer_nodes = contact_nodes[layer_nr]; if (curr_layer_nodes.empty()) continue; - for (Node *node : curr_layer_nodes) { + for (SupportNode *node : curr_layer_nodes) { node->is_processed = false; - if (layer_nr == 0) node->is_merged = true; // nodes on plate are also merged nodes } } + + float max_move = scale_(m_object_config->support_line_width / 2); + // if the branch is very tall, the tip also needs extra wall + float thresh_tall_branch = 100; + float thresh_dist_to_top = 30; for (int layer_nr = 0; layer_nr< contact_nodes.size(); layer_nr++) { - std::vector &curr_layer_nodes = contact_nodes[layer_nr]; + std::vector &curr_layer_nodes = contact_nodes[layer_nr]; if (curr_layer_nodes.empty()) continue; - for (Node *node : curr_layer_nodes) { + for (SupportNode *node : curr_layer_nodes) { if (!node->is_processed) { std::vector pts; - std::vector branch; - Node * p_node = node; - // add a fixed head - if (node->child) { + std::vector radii; + std::vector branch; + SupportNode * p_node = node; + float total_height = 0; + // add a fixed head if it's not a polygon node, see STUDIO-4403 + // Polygon node can't be added because the move distance might be huge, making the nodes in between jump and dangling + if (node->child && node->child->type!=ePolygon) { pts.push_back(p_node->child->position); + radii.push_back(p_node->child->radius); branch.push_back(p_node->child); + total_height += p_node->child->height; } do { pts.push_back(p_node->position); + radii.push_back(p_node->radius); branch.push_back(p_node); + total_height += p_node->height; p_node = p_node->parent; } while (p_node && !p_node->is_processed); if (pts.size() < 3) continue; std::vector pts1 = pts; + std::vector radii1 = radii; // TODO here we assume layer height gap is constant. If not true, need to consider height jump const int iterations = 100; for (size_t k = 0; k < iterations; k++) { for (size_t i = 1; i < pts.size() - 1; i++) { - size_t i2 = i >= 2 ? i - 2 : 0; - size_t i3 = i < pts.size() - 2 ? i + 2 : pts.size() - 1; - Point pt = (pts[i2] + pts[i - 1] + pts[i] + pts[i + 1] + pts[i3]) / 5; + Point pt = ( pts[i - 1] + pts[i] + pts[i + 1] ) / 3; pts1[i] = pt; + radii1[i] = (radii[i - 1] + radii[i] + radii[i + 1] ) / 3; if (k == iterations - 1) { branch[i]->position = pt; + branch[i]->radius = radii1[i]; branch[i]->movement = (pts[i + 1] - pts[i - 1]) / 2; branch[i]->is_processed = true; + if (branch[i]->parents.size() > 1 || (branch[i]->movement.x() > max_move || branch[i]->movement.y() > max_move) || + (total_height > thresh_tall_branch && branch[i]->dist_mm_to_top < thresh_dist_to_top)) + branch[i]->need_extra_wall = true; + BOOST_LOG_TRIVIAL(info) << "smooth_nodes: layer_nr=" << layer_nr << ", i=" << i << ", pt=" << pt << ", movement=" << branch[i]->movement << ", radius=" << branch[i]->radius; } } - if (k < iterations - 1) + if (k < iterations - 1) { std::swap(pts, pts1); + std::swap(radii, radii1); + } + else { + // interpolate need_extra_wall in the end + for (size_t i = 1; i < branch.size() - 1; i++) { + if (branch[i - 1]->need_extra_wall && branch[i + 1]->need_extra_wall) + branch[i]->need_extra_wall = true; + } + } } } } } - // save tree structure for viewing in python - auto& tree_nodes = m_ts_data->tree_nodes; - std::map ptr2idx; - std::map idx2ptr; - for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { - std::vector& curr_layer_nodes = contact_nodes[layer_nr]; - for (Node* node : curr_layer_nodes) { - ptr2idx.emplace(node, tree_nodes.size()); - idx2ptr.emplace(tree_nodes.size(), node); - tree_nodes.emplace_back(node->position, node->print_z); - } - } - for (size_t i = 0; i < tree_nodes.size(); i++) { - TreeNode& tree_node = tree_nodes[i]; - Node* p_node = idx2ptr[i]; - if (p_node->child) - tree_node.children.push_back(ptr2idx[p_node->child]); - if(p_node->parent) - tree_node.parents.push_back(ptr2idx[p_node->parent]); - } -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - nlohmann::json jj; - for (size_t i = 0; i < tree_nodes.size(); i++) { - nlohmann::json j; - j["pos"] = tree_nodes[i].pos; - j["children"] = tree_nodes[i].children; - j["linked"] = !(tree_nodes[i].pos.z() > 0.205 && tree_nodes[i].children.empty()); - jj.push_back(j); - } - - std::ofstream ofs("tree_nodes.json"); - ofs << jj.dump(); - ofs.close(); -#endif } -void TreeSupport::adjust_layer_heights(std::vector>& contact_nodes) +std::vector TreeSupport::plan_layer_heights() { - if (contact_nodes.empty()) - return; - - const PrintConfig& print_config = m_object->print()->config(); - const PrintObjectConfig& config = m_object->config(); - // don't merge layers for Vine support, or the branches will be unsmooth - // TODO can we merge layers in a way that guaranttees smoothness? - if (!print_config.independent_support_layer_height || is_slim) { - for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { - std::vector& curr_layer_nodes = contact_nodes[layer_nr]; - for (Node* node : curr_layer_nodes) { - node->print_z = m_object->get_layer(layer_nr)->print_z; - node->height = m_object->get_layer(layer_nr)->height; - } + std::vector layer_heights; + std::map z_heights; // print_z:height + if (!m_support_params.independent_layer_height) { + layer_heights.resize(m_object->layer_count()); + for (int layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { + z_heights[m_object->get_layer(layer_nr)->print_z] = m_object->get_layer(layer_nr)->height; + layer_heights[layer_nr] = {m_object->get_layer(layer_nr)->print_z, m_object->get_layer(layer_nr)->height, size_t(layer_nr)}; } - return; - } - - // extreme layer_id - std::vector extremes; - const coordf_t layer_height = config.layer_height.value; - const coordf_t max_layer_height = m_slicing_params.max_layer_height; - const size_t bot_intf_layers = config.support_interface_bottom_layers.value; - const size_t top_intf_layers = config.support_interface_top_layers.value; - - // if already using max layer height, no need to adjust - if (layer_height == max_layer_height) return; - - extremes.push_back(0); - for (Node* node : contact_nodes[0]) { - node->print_z = m_object->get_layer(0)->print_z; - node->height = m_object->get_layer(0)->height; - } - - for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) { - std::vector& curr_layer_nodes = contact_nodes[layer_nr]; - for (Node* node : curr_layer_nodes) { - if (node->support_roof_layers_below >0 || node->support_floor_layers_above == bot_intf_layers) { - extremes.push_back(layer_nr); - break; + } else { + const coordf_t max_layer_height = m_slicing_params.max_suport_layer_height; + const coordf_t min_layer_height = m_slicing_params.min_layer_height; + std::map bounds; // print_z: height + // Keep first layer still + bounds[m_object->get_layer(0)->print_z] = {m_object->get_layer(0)->height}; + std::vector obj_layer_zs; + obj_layer_zs.reserve(m_object->layer_count()); + for (const Layer *l : m_object->layers()) obj_layer_zs.emplace_back((float) l->print_z); + z_heights[m_object->get_layer(0)->print_z] = m_object->get_layer(0)->height; + // Collect top contact layers + for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) { + if (!contact_nodes[layer_nr].empty()) { + coordf_t print_z = contact_nodes[layer_nr].front()->print_z; + coordf_t height = contact_nodes[layer_nr].front()->height; + // insertion will fail if there is already a key of print_z, so no need to check + bounds.insert({print_z, height}); + bounds.insert({print_z - height, 0}); // the bottom_z of the layer } } - if (extremes.back() == layer_nr) { - // contact layer use the same print_z and layer height with object layer - for (Node* node : curr_layer_nodes) { - node->print_z = m_object->get_layer(layer_nr)->print_z; - node->height = m_object->get_layer(layer_nr)->height; + auto it1 = bounds.begin(); + auto it2 = std::next(it1); + for (; it2 != bounds.end(); it2++) { + coordf_t z2 = it2->first; + coordf_t h2 = it2->second; + coordf_t z1 = it1->first; + coordf_t h1 = it1->second; + coordf_t dist = z2 - z1; + if (dist < min_layer_height - EPSILON) continue; + + BOOST_LOG_TRIVIAL(trace) << format("plan_layer_heights0 (%.2f,%.2f)->(%.2f,%.2f): ", z1, h1, z2, h2); + + // Insert intermediate layers. + size_t n_layers_extra = size_t(ceil(dist / max_layer_height)); + coordf_t step = dist / coordf_t(n_layers_extra); + coordf_t print_z = z1 + step; + for (int i = 0; i < n_layers_extra; i++, print_z += step) { + z_heights[print_z] = step; + BOOST_LOG_TRIVIAL(debug) << "plan_layer_heights add entry print_z, height: " << print_z << " " << step; } + it1=it2; } - } - // schedule new layer heights and print_z - for (size_t idx_extreme = 0; idx_extreme < extremes.size(); idx_extreme++) { - int extr2_layer_nr = extremes[idx_extreme]; - coordf_t extr2z = m_object->get_layer(extr2_layer_nr)->bottom_z(); - int extr1_layer_nr = idx_extreme == 0 ? -1 : extremes[idx_extreme - 1]; - coordf_t extr1z = idx_extreme == 0 ? 0.f : m_object->get_layer(extr1_layer_nr)->print_z; - coordf_t dist = extr2z - extr1z; + // map z_heights to layer_heights + int i = 0; + size_t obj_layer_nr = 0; + layer_heights.resize(z_heights.size()); + for (auto it = z_heights.begin(); it != z_heights.end(); it++, i++) { + coordf_t print_z = it->first; + coordf_t height = it->second; + while (obj_layer_nr < obj_layer_zs.size() && obj_layer_zs[obj_layer_nr] < print_z - height / 2) obj_layer_nr++; + layer_heights[i] = {print_z, height, obj_layer_nr}; - // Insert intermediate layers. - size_t n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height)); - if (n_layers_extra <= 1) - continue; - - coordf_t step = dist / coordf_t(n_layers_extra); - coordf_t print_z = extr1z + step; - assert(step >= layer_height - EPSILON); - for (int layer_nr = extr1_layer_nr + 1; layer_nr < extr2_layer_nr; layer_nr++) { - std::vector& curr_layer_nodes = contact_nodes[layer_nr]; - if (curr_layer_nodes.empty()) continue; - - if (std::abs(print_z - curr_layer_nodes[0]->print_z) < step / 2 + EPSILON) { - for (Node* node : curr_layer_nodes) { - node->print_z = print_z; - node->height = step; - } - print_z += step; - } - else { - // can't clear curr_layer_nodes, or the model will have empty layers - for (Node* node : curr_layer_nodes) { - node->print_z = 0.0; - node->height = 0.0; - } - } } } -} -std::vector TreeSupport::plan_layer_heights(std::vector> &contact_nodes) -{ - const PrintObjectConfig& config = m_object->config(); - const PrintConfig & print_config = m_object->print()->config(); - const coordf_t max_layer_height = m_slicing_params.max_layer_height; - const coordf_t layer_height = config.layer_height.value; - coordf_t z_distance_top = m_slicing_params.gap_support_object; - // BBS: add extra distance if thick bridge is enabled - // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height - if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) { - z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height; - } - const size_t support_roof_layers = config.support_interface_top_layers.value; - const int z_distance_top_layers = round_up_divide(scale_(z_distance_top), scale_(layer_height)) + 1; - std::vector layer_heights(contact_nodes.size()); - std::vector bounds; - - if (!config.tree_support_adaptive_layer_height || layer_height == max_layer_height || !print_config.independent_support_layer_height) { - for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { - layer_heights[layer_nr] = {m_object->get_layer(layer_nr)->print_z, m_object->get_layer(layer_nr)->height, layer_nr > 0 ? size_t(layer_nr - 1) : 0}; + // add support layers according to layer_heights + int support_layer_nr = m_raft_layers; + for (size_t i = 0; i < layer_heights.size(); i++, support_layer_nr++) { + SupportLayer *ts_layer = m_object->add_tree_support_layer(support_layer_nr, layer_heights[i].print_z, layer_heights[i].height, layer_heights[i].print_z); + if (ts_layer->id() > m_raft_layers) { + SupportLayer *lower_layer = m_object->get_support_layer(ts_layer->id() - 1); + if (lower_layer) { + lower_layer->upper_layer = ts_layer; + ts_layer->lower_layer = lower_layer; + } } - return layer_heights; } - bounds.push_back(0); - // Keep first layer still - layer_heights[0] = {m_object->get_layer(0)->print_z, m_object->get_layer(0)->height, 0}; - // Collect top contact layers - for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) - { - if (!contact_nodes[layer_nr].empty()) - for (int i = 0; i < support_roof_layers + z_distance_top_layers + 1; i++) { - if (layer_nr - i > 0) { - bounds.push_back(layer_nr - i); - layer_heights[layer_nr - i].print_z = m_object->get_layer(layer_nr - i)->print_z; - layer_heights[layer_nr - i].height = m_object->get_layer(layer_nr - i)->height; - } - else { - break; - } - } + // re-distribute contact_nodes to support layers + decltype(contact_nodes) contact_nodes2(support_layer_nr); + for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { + if (contact_nodes[layer_nr].empty()) continue; + SupportNode *node1 = contact_nodes[layer_nr].front(); + auto it = std::min_element(layer_heights.begin(), layer_heights.end(), [node1](const LayerHeightData &l1, const LayerHeightData &l2) { + return std::abs(l1.print_z - node1->print_z) < std::abs(l2.print_z - node1->print_z); + }); + if (it == layer_heights.end()) it = std::prev(it); + int layer_nr2 = std::distance(layer_heights.begin(), it); + contact_nodes2[layer_nr2].insert(contact_nodes2[layer_nr2].end(), contact_nodes[layer_nr].begin(), contact_nodes[layer_nr].end()); } - std::set s(bounds.begin(), bounds.end()); - bounds.assign(s.begin(), s.end()); - - for (size_t idx_extreme = 0; idx_extreme < bounds.size(); idx_extreme++) { - int extr2_layer_nr = bounds[idx_extreme]; - coordf_t extr2z = m_object->get_layer(extr2_layer_nr)->bottom_z(); - int extr1_layer_nr = idx_extreme == 0 ? -1 : bounds[idx_extreme - 1]; - coordf_t extr1z = idx_extreme == 0 ? 0.f : m_object->get_layer(extr1_layer_nr)->print_z; - coordf_t dist = extr2z - extr1z; - - // Insert intermediate layers. - size_t n_layers_extra = size_t(ceil(dist / (m_slicing_params.max_suport_layer_height + EPSILON))); - int actual_internel_layers = extr2_layer_nr - extr1_layer_nr - 1; - int extr_layers_left = extr2_layer_nr - extr1_layer_nr - n_layers_extra - 1; - if (n_layers_extra < 1) - continue; + contact_nodes = contact_nodes2; - coordf_t step = dist / coordf_t(n_layers_extra); - coordf_t print_z = extr1z + step; - assert(step >= layer_height - EPSILON); - for (int layer_nr = extr1_layer_nr + 1; layer_nr < extr2_layer_nr; layer_nr++) { - // if (curr_layer_nodes.empty()) continue; - if (std::abs(print_z - m_object->get_layer(layer_nr)->print_z) < step / 2 + EPSILON || extr_layers_left < 1) { - layer_heights[layer_nr].print_z = print_z; - layer_heights[layer_nr].height = step; - print_z += step; - } - else { - // can't clear curr_layer_nodes, or the model will have empty layers - layer_heights[layer_nr].print_z = 0.0; - layer_heights[layer_nr].height = 0.0; - extr_layers_left--; + // adjust contact nodes' distance_to_top and support_roof_layers_below according to layer_heights + // In case of very large top z distance, one gap layer is not enough, we need to split it into multiple layers + for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { + if (contact_nodes[layer_nr].empty()) continue; + SupportNode *node1 = contact_nodes[layer_nr].front(); + BOOST_LOG_TRIVIAL(debug) << format("plan_layer_heights node1->layer_nr,printz,height,distance_to_top: %d, %.2f,%.2f, %d", layer_nr, node1->print_z, node1->height, node1->distance_to_top) + << ", object_layer_zs[" << layer_heights[layer_nr].obj_layer_nr << "]=" << m_object->get_layer(layer_heights[layer_nr].obj_layer_nr)->print_z; + coordf_t new_height = layer_heights[layer_nr].height; + if (std::abs(node1->height - new_height) < EPSILON) continue; + if (top_z_distance < EPSILON && node1->height < EPSILON) continue; // top_z_distance==0, this is soluable interface + coordf_t accum_height = 0; + int num_layers = 0; + for (int i=layer_nr;i>=0;i--){ + if (layer_heights[i].height > EPSILON) { + accum_height += layer_heights[i].height; + num_layers++; + if (accum_height > node1->height - EPSILON) break; } } - } - - // fill in next_layer_nr - int i = layer_heights.size() - 1, j = i; - for (; j >= 0; i = j) { - if (layer_heights[i].height < EPSILON) { - j--; - continue; - } - for (j = i - 1; j >= 0; j--) { - if (layer_heights[j].height > EPSILON) { - layer_heights[i].next_layer_nr = j; - break; - } + BOOST_LOG_TRIVIAL(debug) << format("plan_layer_heights adjust node's height print_z[%d]=%.2f: (%.3f,%d)->(%.3f,%.3f,%d)", layer_nr, node1->print_z, + node1->height, node1->distance_to_top, new_height, accum_height, -num_layers); + for (SupportNode *node : contact_nodes[layer_nr]) { + node->height = new_height; + node->distance_to_top = -num_layers; + node->support_roof_layers_below += num_layers - 1; } - BOOST_LOG_TRIVIAL(trace) << "plan_layer_heights print_z, height, layer_nr->next_layer_nr: " << layer_heights[i].print_z << " " << layer_heights[i].height << " " - << i << "->" << layer_heights[i].next_layer_nr << std::endl; } + // log layer_heights + for (size_t i = 0; i < layer_heights.size(); i++) { + //if (layer_heights[i].height > EPSILON) + BOOST_LOG_TRIVIAL(trace) << format("plan_layer_heights[%d] print_z, height: %.2f, %.2f",i, layer_heights[i].print_z, layer_heights[i].height); + } return layer_heights; } -void TreeSupport::generate_contact_points(std::vector>& contact_nodes) +void TreeSupport::generate_contact_points() { const PrintObjectConfig &config = m_object->config(); const coordf_t point_spread = scale_(config.tree_support_branch_distance.value); + const coordf_t max_bridge_length = scale_(config.max_bridge_length.value); + coord_t radius_scaled = scale_(base_radius); + bool on_buildplate_only = m_object_config->support_on_build_plate_only.value; + const bool roof_enabled = config.support_interface_top_layers.value > 0; + const bool force_tip_to_roof = roof_enabled && m_support_params.soluble_interface; //First generate grid points to cover the entire area of the print. BoundingBox bounding_box = m_object->bounding_box(); @@ -3388,8 +3165,9 @@ void TreeSupport::generate_contact_points(std::vector grid_points; - for (auto x = -rotated_dims(0); x < rotated_dims(0); x += point_spread) { - for (auto y = -rotated_dims(1); y < rotated_dims(1); y += point_spread) { + coordf_t sample_step = std::max(point_spread, max_bridge_length / 2); + for (auto x = -rotated_dims(0); x < rotated_dims(0); x += sample_step) { + for (auto y = -rotated_dims(1); y < rotated_dims(1); y += sample_step) { Point pt(x, y); pt.rotate(cos_angle, sin_angle); pt += center; @@ -3400,13 +3178,17 @@ void TreeSupport::generate_contact_points(std::vectorthick_bridges) { - z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height; - } + coordf_t z_distance_top = this->top_z_distance; + // if (!m_support_params.independent_layer_height) { + // z_distance_top = round(z_distance_top / layer_height) * layer_height; + // // BBS: add extra distance if thick bridge is enabled + // // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height + // if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) { + // z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height; + //} + // } const int z_distance_top_layers = round_up_divide(scale_(z_distance_top), scale_(layer_height)) + 1; //Support must always be 1 layer below overhang. + int gap_layers = z_distance_top == 0 ? 0 : 1; size_t support_roof_layers = config.support_interface_top_layers.value; if (support_roof_layers > 0) @@ -3418,137 +3200,158 @@ void TreeSupport::generate_contact_points(std::vectorlayers().size() <= z_distance_top_layers + 1) return; - m_highest_overhang_layer = 0; - int nonempty_layers = 0; - std::vector all_nodes; - for (size_t layer_nr = 1; layer_nr < m_object->layers().size(); layer_nr++) - { - if (m_object->print()->canceled()) - break; - auto ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - const ExPolygons &overhang = ts_layer->overhang_areas; - auto & curr_nodes = contact_nodes[layer_nr]; - if (overhang.empty()) - continue; - - m_highest_overhang_layer = std::max(m_highest_overhang_layer, layer_nr); - auto print_z = m_object->get_layer(layer_nr)->print_z; - auto height = m_object->get_layer(layer_nr)->height; + contact_nodes.clear(); + contact_nodes.resize(m_object->layers().size()); + + tbb::spin_mutex mtx; + + // add vertical enforcer points + std::vector zs = zs_from_layers(m_object->layers()); + std::vector>> vertical_enforcer_points_by_layers(m_object->layer_count()); + for (auto& pt_and_normal : m_vertical_enforcer_points) { + auto pt = pt_and_normal.first; + auto normal = pt_and_normal.second; // normal seems useless + auto iter = std::lower_bound(zs.begin(), zs.end(), pt.z()); + if (iter != zs.end()) { + size_t layer_nr = iter - zs.begin(); + if (layer_nr > 0 && layer_nr < contact_nodes.size()) { + vertical_enforcer_points_by_layers[layer_nr].push_back({ to_2d(pt).cast(),scaled(to_2d(normal)) }); + } + } + } - for (const ExPolygon &overhang_part : overhang) - { - BoundingBox overhang_bounds = get_extents(overhang_part); - if (support_style==smsTreeHybrid && overhang_part.area() > m_support_params.thresh_big_overhang) { - Point candidate = overhang_bounds.center(); - if (!overhang_part.contains(candidate)) - move_inside_expoly(overhang_part, candidate); - if (!(config.support_on_build_plate_only && is_inside_ex(m_ts_data->m_layer_outlines_below[layer_nr], candidate))) { - Node* contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z, - height, z_distance_top); - contact_node->type = ePolygon; - contact_node->overhang = &overhang_part; + int nonempty_layers = 0; + tbb::concurrent_vector all_nodes; + tbb::parallel_for(tbb::blocked_range(1, m_object->layers().size()), [&](const tbb::blocked_range& range) { + for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { + if (m_object->print()->canceled()) + break; + Layer* layer = m_object->get_layer(layer_nr); + auto& curr_nodes = contact_nodes[layer_nr-1]; + + std::unordered_set already_inserted; + auto bottom_z = m_object->get_layer(layer_nr)->bottom_z(); + bool added = false; // Did we add a point this way? + bool is_sharp_tail = false; + + // take the least restrictive avoidance possible + ExPolygons relevant_forbidden = offset_ex(m_ts_data->m_layer_outlines[layer_nr - 1], scale_(MIN_BRANCH_RADIUS)); + // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. + relevant_forbidden = offset_ex(union_ex(relevant_forbidden), scaled(0.005), jtMiter, 1.2); + + + auto insert_point = [&](Point pt, const ExPolygon& overhang, double radius, bool force_add = false, bool add_interface=true) { + Point hash_pos = pt / ((radius_scaled + 1) / 1); + SupportNode* contact_node = nullptr; + if (force_add || !already_inserted.count(hash_pos)) { + already_inserted.emplace(hash_pos); + bool to_buildplate = true; + size_t roof_layers = add_interface ? support_roof_layers : 0; + // add a new node as a virtual node which acts as the invisible gap between support and object + // distance_to_top=-1: it's virtual + // print_z=object_layer->bottom_z: it directly contacts the bottom + // height=z_distance_top: it's height is exactly the gap distance + // dist_mm_to_top=0: it directly contacts the bottom + contact_node = m_ts_data->create_node(pt, -gap_layers, layer_nr-1, roof_layers + 1, to_buildplate, SupportNode::NO_PARENT, bottom_z, z_distance_top, 0, + radius); + contact_node->overhang = overhang; + contact_node->is_sharp_tail = is_sharp_tail; curr_nodes.emplace_back(contact_node); - continue; - } - } + added = true; + }; + return contact_node; + }; - overhang_bounds.inflated(half_overhang_distance); - bool added = false; //Did we add a point this way? - for (Point candidate : grid_points) - { - if (overhang_bounds.contains(candidate)) - { - // BBS: move_inside_expoly shouldn't be used if candidate is already inside, as it moves point to boundary and the inside is not well supported! - bool is_inside = is_inside_ex(overhang_part, candidate); - if (!is_inside) { - constexpr coordf_t distance_inside = 0; // Move point towards the border of the polygon if it is closer than half the overhang distance: Catch points that - // fall between overhang areas on constant surfaces. - move_inside_expoly(overhang_part, candidate, distance_inside, half_overhang_distance); - is_inside = is_inside_ex(overhang_part, candidate); - } - if (is_inside) - { - // collision radius has to be 0 or the supports are too few at curved slopes - //if (!is_inside_ex(m_ts_data->get_collision(0, layer_nr), candidate)) - { - constexpr bool to_buildplate = true; - Node * contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, to_buildplate, - Node::NO_PARENT, print_z, height, z_distance_top); - contact_node->overhang = &overhang_part; + for (const auto& overhang_part : layer->loverhangs) { + const auto& overhang_type = this->overhang_types[&overhang_part]; + is_sharp_tail = overhang_type == OverhangType::SharpTail; + ExPolygons overhangs_regular; + if (m_support_params.support_style == smsTreeHybrid && overhang_part.area() > m_support_params.thresh_big_overhang && !is_sharp_tail) { + overhangs_regular = offset_ex(intersection_ex({overhang_part}, m_ts_data->m_layer_outlines_below[layer_nr - 1]), radius_scaled); + ExPolygons overhangs_normal = diff_ex({overhang_part}, overhangs_regular); + if (area(overhangs_normal) > m_support_params.thresh_big_overhang) { + // if the outside area is still big, we can need normal nodes + for (auto &overhang : overhangs_normal) { + BoundingBox overhang_bounds = get_extents(overhang); + double radius = unscale_(overhang_bounds.radius()); + Point candidate = overhang_bounds.center(); + SupportNode *contact_node = insert_point(candidate, overhang, radius, true, true); + contact_node->type = ePolygon; curr_nodes.emplace_back(contact_node); - added = true; + } + }else{ + // otherwise, all nodes should be circle nodes + overhangs_regular = ExPolygons{overhang_part}; + } + } else { + overhangs_regular = ExPolygons{overhang_part}; + } + + for (auto &overhang : overhangs_regular) { + bool add_interface = (force_tip_to_roof || area(overhang) > minimum_roof_area) && !is_sharp_tail; + BoundingBox overhang_bounds = get_extents(overhang); + double radius = std::clamp(unscale_(overhang_bounds.radius()), MIN_BRANCH_RADIUS, base_radius); + // add supports at corners for both auto and manual overhangs, github #2008 + auto &points = overhang.contour.points; + int nSize = points.size(); + for (int i = 0; i < nSize; i++) { + auto pt = points[i]; + auto v1 = (pt - points[(i - 1 + nSize) % nSize]).cast().normalized(); + auto v2 = (pt - points[(i + 1) % nSize]).cast().normalized(); + if (v1.dot(v2) > -0.7) { // angle smaller than 135 degrees + SupportNode *contact_node = insert_point(pt, overhang, radius, false, add_interface); + if (contact_node) { + contact_node->is_corner = true; + } } } - } - } - if (!added) //If we didn't add any points due to bad luck, we want to add one anyway such that loose parts are also supported. - { - auto bbox = overhang_part.contour.bounding_box(); - Points candidates; - if (ts_layer->overhang_types[&overhang_part] == SupportLayer::Detected) - candidates = {bbox.min, bounding_box_middle(bbox), bbox.max}; - else - candidates = {bounding_box_middle(bbox)}; - - for (Point candidate : candidates) { - if (!overhang_part.contains(candidate)) - move_inside_expoly(overhang_part, candidate); - constexpr bool to_buildplate = true; - Node *contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, to_buildplate, Node::NO_PARENT, - print_z, height, z_distance_top); - contact_node->overhang = &overhang_part; - curr_nodes.emplace_back(contact_node); - } - } - // add supports at corners for both auto and manual overhangs, github #2008 - if (/*ts_layer->overhang_types[&overhang_part] == SupportLayer::Detected*/1) { - // add points at corners - auto &points = overhang_part.contour.points; - int nSize = points.size(); - for (int i = 0; i < nSize; i++) { - auto pt = points[i]; - auto v1 = (pt - points[(i - 1 + nSize) % nSize]).cast().normalized(); - auto v2 = (pt - points[(i + 1) % nSize]).cast().normalized(); - if (v1.dot(v2) > -0.7) { // angle smaller than 135 degrees - Node *contact_node = new Node(pt, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z, - height, z_distance_top); - contact_node->overhang = &overhang_part; - contact_node->is_corner = true; - curr_nodes.emplace_back(contact_node); + // add supports along contours + libnest2d::placers::EdgeCache edge_cache(overhang); + for (size_t i = 0; i < edge_cache.holeCount() + 1; i++) { + double step = point_spread / (i == 0 ? edge_cache.circumference() : edge_cache.circumference(i - 1)); + double distance = 0; + while (distance < 1) { + auto pt = i == 0 ? edge_cache.coords(distance) : edge_cache.coords(i - 1, distance); + SupportNode *contact_node = insert_point(pt, overhang,radius, false, add_interface); + distance += step; + } } - } - } - if(ts_layer->overhang_types[&overhang_part] == SupportLayer::Enforced || is_slim){ - // remove close points in Enforcers - // auto above_nodes = contact_nodes[layer_nr - 1]; - if (!curr_nodes.empty() /*&& !above_nodes.empty()*/) { - for (auto it = curr_nodes.begin(); it != curr_nodes.end();) { - bool is_duplicate = false; - if (!(*it)->is_corner) { - Slic3r::Vec3f curr_pt((*it)->position(0), (*it)->position(1), scale_((*it)->print_z)); - for (auto &pt : all_nodes) { - auto dif = curr_pt - pt; - if (dif.norm() < point_spread / 2) { - delete (*it); - it = curr_nodes.erase(it); - is_duplicate = true; - break; - } - } + + // don't add inner supports for sharp tails + if (is_sharp_tail) continue; + + // add inner supports + overhang_bounds.inflated(-radius_scaled); + ExPolygons overhang_inner = offset_ex(overhang, -radius_scaled); + for (Point candidate : grid_points) { + if (overhang_bounds.contains(candidate)) { + // BBS: move_inside_expoly shouldn't be used if candidate is already inside, as it moves point to boundary and the inside is not well supported! + bool is_inside = is_inside_ex(overhang_inner, candidate); + if (is_inside) { SupportNode *contact_node = insert_point(candidate, overhang,radius, false, add_interface); } } - if (!is_duplicate) it++; } } } - } - if (!curr_nodes.empty()) nonempty_layers++; - for (auto node : curr_nodes) { all_nodes.emplace_back(node->position(0), node->position(1), scale_(node->print_z)); } + for (auto& pt_and_normal : vertical_enforcer_points_by_layers[layer_nr]) { + is_sharp_tail = true;// fake it as sharp tail point so the contact distance will be 0 + auto vertical_enforcer_point= pt_and_normal.first; + auto node=insert_point(vertical_enforcer_point, ExPolygon(), false); + if (node) + node->skin_direction = pt_and_normal.second; + } + if (!curr_nodes.empty()) nonempty_layers++; + for (auto node : curr_nodes) { all_nodes.emplace_back(node->position(0), node->position(1), scale_(node->print_z)); } #ifdef SUPPORT_TREE_DEBUG_TO_SVG - draw_contours_and_nodes_to_svg(std::to_string(print_z), overhang, m_ts_data->m_layer_outlines_below[layer_nr], {}, - contact_nodes[layer_nr], {}, "init_contact_points", { "overhang","outlines","" }); + if (!curr_nodes.empty()) + draw_contours_and_nodes_to_svg(debug_out_path("init_contact_points_%.2f.svg", bottom_z), layer->loverhangs,layer->lslices_extrudable, m_ts_data->m_layer_outlines_below[layer_nr], + contact_nodes[layer_nr], contact_nodes[layer_nr - 1], { "overhang","lslices","outlines_below"}); #endif - } + }} + ); // end tbb::parallel_for + + + int nNodes = all_nodes.size(); avg_node_per_layer = nodes_angle = 0; if (nNodes > 0) { @@ -3566,31 +3369,35 @@ void TreeSupport::generate_contact_points(std::vector children; // index of children in the storing vector - std::vector parents; // index of parents in the storing vector - TreeNode(Point pt, float z) { - pos = { float(unscale_(pt.x())),float(unscale_(pt.y())),z }; + +/*! + * \brief Represents the metadata of a node in the tree. + */ +struct SupportNode +{ + static constexpr SupportNode* NO_PARENT = nullptr; + + SupportNode() + : distance_to_top(0) + , position(Point(0, 0)) + , obj_layer_nr(0) + , support_roof_layers_below(0) + , to_buildplate(true) + , parent(nullptr) + , print_z(0.0) + , height(0.0) + {} + + // when dist_mm_to_top_==0, new node's dist_mm_to_top=parent->dist_mm_to_top + parent->height; + SupportNode(const Point position, const int distance_to_top, const int obj_layer_nr, const int support_roof_layers_below, const bool to_buildplate, SupportNode* parent, + coordf_t print_z_, coordf_t height_, coordf_t dist_mm_to_top_ = 0, coordf_t radius_ = 0) + : distance_to_top(distance_to_top) + , position(position) + , obj_layer_nr(obj_layer_nr) + , support_roof_layers_below(support_roof_layers_below) + , to_buildplate(to_buildplate) + , parent(parent) + , print_z(print_z_) + , height(height_) + , dist_mm_to_top(dist_mm_to_top_) + , radius(radius_) + { + if (parent) { + parents.push_back(parent); + type = parent->type; + overhang = parent->overhang; + if (dist_mm_to_top == 0) + dist_mm_to_top = parent->dist_mm_to_top + parent->height; + if (radius == 0 && parent->radius>0) + radius = parent->radius + (dist_mm_to_top - parent->dist_mm_to_top) * diameter_angle_scale_factor; + parent->child = this; + for (auto& neighbor : parent->merged_neighbours) { + neighbor->child = this; + parents.push_back(neighbor); + } + is_sharp_tail = parent->is_sharp_tail; + skin_direction = parent->skin_direction; + } + } + +#ifdef DEBUG // Clear the delete node's data so if there's invalid access after, we may get a clue by inspecting that node. + ~SupportNode() + { + parent = nullptr; + merged_neighbours.clear(); + } +#endif // DEBUG + + /*! + * \brief The number of layers to go to the top of this branch. + * Negative value means it's a virtual node between support and overhang, which doesn't need to be extruded. + */ + int distance_to_top; + coordf_t dist_mm_to_top = 0; // dist to bottom contact in mm + + // all nodes will have same diameter_angle_scale_factor because it's defined by user + static double diameter_angle_scale_factor; + + /*! + * \brief The position of this node on the layer. + */ + Point position; + Point movement; // movement towards neighbor center or outline + mutable double radius = 0.0; + mutable double max_move_dist = 0.0; + TreeNodeType type = eCircle; + bool is_corner = false; + bool is_processed = false; + bool need_extra_wall = false; + bool is_sharp_tail = false; + bool valid = true; + ExPolygon overhang; // when type==ePolygon, set this value to get original overhang area + + /*! + * \brief The direction of the skin lines above the tip of the branch. + * + * This determines in which direction we should reduce the width of the + * branch. + */ + Point skin_direction; + + /*! + * \brief The number of support roof layers below this one. + * + * When a contact point is created, it is determined whether the mesh + * needs to be supported with support roof or not, since that is a + * per-mesh setting. This is stored in this variable in order to track + * how far we need to extend that support roof downwards. + */ + int support_roof_layers_below; + int obj_layer_nr; + + /*! + * \brief Whether to try to go towards the build plate. + * + * If the node is inside the collision areas, it has no choice but to go + * towards the model. If it is not inside the collision areas, it must + * go towards the build plate to prevent a scar on the surface. + */ + bool to_buildplate; + + /*! + * \brief The originating node for this one, one layer higher. + * + * In order to prune branches that can't have any support (because they + * can't be on the model and the path to the buildplate isn't clear), + * the entire branch needs to be known. + */ + SupportNode* parent; + std::vector parents; + SupportNode* child = nullptr; + + /*! + * \brief All neighbours (on the same layer) that where merged into this node. + * + * In order to prune branches that can't have any support (because they + * can't be on the model and the path to the buildplate isn't clear), + * the entire branch needs to be known. + */ + std::list merged_neighbours; + + coordf_t print_z; + coordf_t height; + + bool operator==(const SupportNode& other) const + { + return position == other.position; } }; @@ -54,12 +197,12 @@ class TreeSupportData * * \param xy_distance The required clearance between the model and the * tree branches. - * \param max_move The maximum allowable movement between nodes on - * adjacent layers * \param radius_sample_resolution Sample size used to round requested node radii. - * \param collision_resolution */ - TreeSupportData(const PrintObject& object, coordf_t max_move, coordf_t radius_sample_resolution, coordf_t collision_resolution); + TreeSupportData(const PrintObject& object, coordf_t xy_distance, coordf_t radius_sample_resolution); + ~TreeSupportData() { + clear_nodes(); + } TreeSupportData(TreeSupportData&&) = default; TreeSupportData& operator=(TreeSupportData&&) = default; @@ -98,9 +241,13 @@ class TreeSupportData Polygons get_contours(size_t layer_nr) const; Polygons get_contours_with_holes(size_t layer_nr) const; + SupportNode* create_node(const Point position, const int distance_to_top, const int obj_layer_nr, const int support_roof_layers_below, const bool to_buildplate, SupportNode* parent, + coordf_t print_z_, coordf_t height_, coordf_t dist_mm_to_top_ = 0, coordf_t radius_ = 0); + void clear_nodes(); std::vector layer_heights; - std::vector tree_nodes; + std::vector> contact_nodes; + // ExPolygon m_machine_border; private: /*! @@ -110,7 +257,7 @@ class TreeSupportData coordf_t radius; size_t layer_nr; int recursions; - + }; struct RadiusLayerPairEquality { constexpr bool operator()(const RadiusLayerPair& _Left, const RadiusLayerPair& _Right) const { @@ -146,6 +293,7 @@ class TreeSupportData */ const ExPolygons& calculate_avoidance(const RadiusLayerPair& key) const; + tbb::spin_mutex m_mutex; public: bool is_slim = false; @@ -154,11 +302,7 @@ class TreeSupportData */ coordf_t m_xy_distance; - /*! - * \brief The maximum distance that the centrepoint of a tree branch may - * move in consequtive layers - */ - coordf_t m_max_move; + double branch_scale_factor = 1.0; // tan(45 degrees) /*! * \brief Sample resolution for radius values. @@ -177,6 +321,8 @@ class TreeSupportData // union contours of all layers below std::vector m_layer_outlines_below; + std::vector m_max_move_distances; + /*! * \brief Caches for the collision, avoidance and internal model polygons * at given radius and layer indices. @@ -185,7 +331,7 @@ class TreeSupportData * generally considered OK as the functions are still logically const * (ie there is no difference in behaviour for the user betweeen * calculating the values each time vs caching the results). - * + * * coconut: previously stl::unordered_map is used which seems problematic with tbb::parallel_for. * So we change to tbb::concurrent_unordered_map */ @@ -215,6 +361,10 @@ class TreeSupport */ TreeSupport(PrintObject& object, const SlicingParameters &slicing_params); + void move_bounds_to_contact_nodes(std::vector &move_bounds, + PrintObject &print_object, + const TreeSupport3D::TreeSupportSettings &config); + /*! * \brief Create the areas that need support. * @@ -224,180 +374,28 @@ class TreeSupport */ void generate(); - void detect_overhangs(bool detect_first_sharp_tail_only=false); - - enum NodeType { - eCircle, - eSquare, - ePolygon - }; - - /*! - * \brief Represents the metadata of a node in the tree. - */ - struct Node - { - static constexpr Node* NO_PARENT = nullptr; - - Node() - : distance_to_top(0) - , position(Point(0, 0)) - , obj_layer_nr(0) - , support_roof_layers_below(0) - , support_floor_layers_above(0) - , to_buildplate(true) - , parent(nullptr) - , print_z(0.0) - , height(0.0) - {} - - // when dist_mm_to_top_==0, new node's dist_mm_to_top=parent->dist_mm_to_top + parent->height; - Node(const Point position, const int distance_to_top, const int obj_layer_nr, const int support_roof_layers_below, const bool to_buildplate, Node* parent, - coordf_t print_z_, coordf_t height_, coordf_t dist_mm_to_top_=0) - : distance_to_top(distance_to_top) - , position(position) - , obj_layer_nr(obj_layer_nr) - , support_roof_layers_below(support_roof_layers_below) - , support_floor_layers_above(0) - , to_buildplate(to_buildplate) - , parent(parent) - , print_z(print_z_) - , height(height_) - , dist_mm_to_top(dist_mm_to_top_) - { - if (parent) { - type = parent->type; - overhang = parent->overhang; - if (dist_mm_to_top==0) - dist_mm_to_top = parent->dist_mm_to_top + parent->height; - parent->child = this; - for (auto& neighbor : parent->merged_neighbours) - neighbor->child = this; - } - } - -#ifdef DEBUG // Clear the delete node's data so if there's invalid access after, we may get a clue by inspecting that node. - ~Node() - { - parent = nullptr; - merged_neighbours.clear(); - } -#endif // DEBUG - - /*! - * \brief The number of layers to go to the top of this branch. - * Negative value means it's a virtual node between support and overhang, which doesn't need to be extruded. - */ - int distance_to_top; - coordf_t dist_mm_to_top = 0; // dist to bottom contact in mm - - /*! - * \brief The position of this node on the layer. - */ - Point position; - Point movement; // movement towards neighbor center or outline - mutable double radius = 0.0; - mutable double max_move_dist = 0.0; - NodeType type = eCircle; - bool is_merged = false; // this node is generated by merging upper nodes - bool is_corner = false; - bool is_processed = false; - const ExPolygon *overhang = nullptr; // when type==ePolygon, set this value to get original overhang area - - /*! - * \brief The direction of the skin lines above the tip of the branch. - * - * This determines in which direction we should reduce the width of the - * branch. - */ - bool skin_direction; - - /*! - * \brief The number of support roof layers below this one. - * - * When a contact point is created, it is determined whether the mesh - * needs to be supported with support roof or not, since that is a - * per-mesh setting. This is stored in this variable in order to track - * how far we need to extend that support roof downwards. - */ - int support_roof_layers_below; - int support_floor_layers_above; - int obj_layer_nr; - - /*! - * \brief Whether to try to go towards the build plate. - * - * If the node is inside the collision areas, it has no choice but to go - * towards the model. If it is not inside the collision areas, it must - * go towards the build plate to prevent a scar on the surface. - */ - bool to_buildplate; - - /*! - * \brief The originating node for this one, one layer higher. - * - * In order to prune branches that can't have any support (because they - * can't be on the model and the path to the buildplate isn't clear), - * the entire branch needs to be known. - */ - Node *parent; - Node *child = nullptr; - - /*! - * \brief All neighbours (on the same layer) that where merged into this node. - * - * In order to prune branches that can't have any support (because they - * can't be on the model and the path to the buildplate isn't clear), - * the entire branch needs to be known. - */ - std::list merged_neighbours; - - coordf_t print_z; - coordf_t height; - - bool operator==(const Node& other) const - { - return position == other.position; - } - }; - - struct SupportParams + void detect_overhangs(bool check_support_necessity = false); + + SupportNode* create_node(const Point position, + const int distance_to_top, + const int obj_layer_nr, + const int support_roof_layers_below, + const bool to_buildplate, + SupportNode* parent, + coordf_t print_z_, + coordf_t height_, + coordf_t dist_mm_to_top_ = 0, + coordf_t radius_ = 0) { - Flow first_layer_flow; - Flow support_material_flow; - Flow support_material_interface_flow; - Flow support_material_bottom_interface_flow; - coordf_t support_extrusion_width; - // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? - bool can_merge_support_regions; - - coordf_t support_layer_height_min; - // coordf_t support_layer_height_max; - - coordf_t gap_xy; - - float base_angle; - float interface_angle; - coordf_t interface_spacing; - coordf_t interface_density; - coordf_t support_spacing; - coordf_t support_density; - - InfillPattern base_fill_pattern; - InfillPattern interface_fill_pattern; - InfillPattern contact_fill_pattern; - bool with_sheath; - const double thresh_big_overhang = SQ(scale_(10)); - }; + return m_ts_data->create_node(position, distance_to_top, obj_layer_nr, support_roof_layers_below, to_buildplate, parent, print_z_, height_, dist_mm_to_top_, radius_); + } int avg_node_per_layer = 0; - float nodes_angle = 0; - bool has_overhangs = false; + float nodes_angle = 0; bool has_sharp_tails = false; bool has_cantilever = false; double max_cantilever_dist = 0; SupportType support_type; - SupportMaterialStyle support_style; std::unique_ptr generator; std::unordered_map printZ_to_lightninglayer; @@ -410,6 +408,10 @@ class TreeSupport */ ExPolygon m_machine_border; + enum OverhangType { Detected = 0, Enforced, SharpTail }; + std::map overhang_types; + std::vector> m_vertical_enforcer_points; + private: /*! * \brief Generator for model collision, avoidance and internal guide volumes @@ -417,20 +419,29 @@ class TreeSupport * Lazily computes volumes as needed. * \warning This class is NOT currently thread-safe and should not be accessed in OpenMP blocks */ + std::vector> contact_nodes; std::shared_ptr m_ts_data; + std::unique_ptr m_model_volumes; PrintObject *m_object; - const PrintObjectConfig *m_object_config; + const PrintObjectConfig* m_object_config; SlicingParameters m_slicing_params; // Various precomputed support parameters to be shared with external functions. - SupportParams m_support_params; - size_t m_raft_layers = 0; + SupportParameters m_support_params; + size_t m_raft_layers = 0; // number of raft layers, including raft base, raft interface, raft gap size_t m_highest_overhang_layer = 0; std::vector> m_spanning_trees; std::vector< std::unordered_map> m_mst_line_x_layer_contour_caches; - coordf_t MAX_BRANCH_RADIUS = 10.0; - coordf_t MAX_BRANCH_RADIUS_FIRST_LAYER = 12.0; - coordf_t MIN_BRANCH_RADIUS = 0.5; - float tree_support_branch_diameter_angle = 5.0; + float DO_NOT_MOVER_UNDER_MM = 0.0; + coordf_t base_radius = 0.0; + const coordf_t MAX_BRANCH_RADIUS = 10.0; + const coordf_t MIN_BRANCH_RADIUS = 0.4; + const coordf_t MAX_BRANCH_RADIUS_FIRST_LAYER = 12.0; + const coordf_t MIN_BRANCH_RADIUS_FIRST_LAYER = 2.0; + double diameter_angle_scale_factor = tan(5.0*M_PI/180.0); + // minimum roof area (1 mm^2), area smaller than this value will not have interface + const double minimum_roof_area{SQ(scaled(1.))}; + float top_z_distance = 0.0; + bool is_strong = false; bool is_slim = false; bool with_infill = false; @@ -447,7 +458,7 @@ class TreeSupport * save the resulting support polygons to. * \param contact_nodes The nodes to draw as support. */ - void draw_circles(const std::vector>& contact_nodes); + void draw_circles(); /*! * \brief Drops down the nodes of the tree support towards the build plate. @@ -461,18 +472,16 @@ class TreeSupport * dropped down. The nodes are dropped to lower layers inside the same * vector of layers. */ - void drop_nodes(std::vector> &contact_nodes); + void drop_nodes(); - void smooth_nodes(std::vector> &contact_nodes); - - void adjust_layer_heights(std::vector>& contact_nodes); + void smooth_nodes(); /*! BBS: MusangKing: maximum layer height * \brief Optimize the generation of tree support by pre-planning the layer_heights - * + * */ - std::vector plan_layer_heights(std::vector> &contact_nodes); + std::vector plan_layer_heights(); /*! * \brief Creates points where support contacts the model. * @@ -486,20 +495,27 @@ class TreeSupport * \return For each layer, a list of points where the tree should connect * with the model. */ - void generate_contact_points(std::vector>& contact_nodes); + void generate_contact_points(); /*! * \brief Add a node to the next layer. * * If a node is already at that position in the layer, the nodes are merged. */ - void insert_dropped_node(std::vector& nodes_layer, Node* node); + void insert_dropped_node(std::vector& nodes_layer, SupportNode* node); void create_tree_support_layers(); void generate_toolpaths(); - Polygons spanning_tree_to_polygon(const std::vector& spanning_trees, Polygons layer_contours, int layer_nr); - Polygons contact_nodes_to_polygon(const std::vector& contact_nodes, Polygons layer_contours, int layer_nr, std::vector& radiis, std::vector& is_interface); + // get unscaled radius of node coordf_t calc_branch_radius(coordf_t base_radius, size_t layers_to_top, size_t tip_layers, double diameter_angle_scale_factor); - coordf_t calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor); + // get unscaled radius(mm) of node based on the distance mm to top + coordf_t calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor, bool use_min_distance=true); + coordf_t calc_radius(coordf_t mm_to_top); + coordf_t get_radius(const SupportNode* node); + ExPolygons get_avoidance(coordf_t radius, size_t obj_layer_nr); + // layer's expolygon expanded by radius+m_xy_distance + ExPolygons get_collision(coordf_t radius, size_t layer_nr); + // get Polygons instead of ExPolygons + Polygons get_collision_polys(coordf_t radius, size_t layer_nr); // similar to SupportMaterial::trim_support_layers_by_object Polygons get_trim_support_regions( diff --git a/src/libslic3r/Support/TreeSupport3D.cpp b/src/libslic3r/Support/TreeSupport3D.cpp index 98dc41f4864..e1971e9b4ce 100644 --- a/src/libslic3r/Support/TreeSupport3D.cpp +++ b/src/libslic3r/Support/TreeSupport3D.cpp @@ -7,22 +7,22 @@ // CuraEngine is released under the terms of the AGPLv3 or higher. #include "TreeSupport3D.hpp" - -#include "../AABBTreeIndirect.hpp" -#include "../BuildVolume.hpp" -#include "../ClipperUtils.hpp" -#include "../EdgeGrid.hpp" -#include "../Fill/Fill.hpp" -#include "../Layer.hpp" -#include "../Print.hpp" -#include "../MultiPoint.hpp" -#include "../Polygon.hpp" -#include "../Polyline.hpp" -#include "../MutablePolygon.hpp" -#include "TreeSupportCommon.hpp" +#include "AABBTreeIndirect.hpp" +#include "AABBTreeLines.hpp" +#include "BuildVolume.hpp" +#include "ClipperUtils.hpp" +#include "EdgeGrid.hpp" +#include "Fill/Fill.hpp" +#include "Layer.hpp" +#include "Print.hpp" +#include "MultiPoint.hpp" +#include "Polygon.hpp" +#include "Polyline.hpp" +#include "MutablePolygon.hpp" #include "SupportCommon.hpp" +#include "TriangleMeshSlicer.hpp" #include "TreeSupport.hpp" -#include "libslic3r.h" +#include "I18N.hpp" #include #include @@ -36,6 +36,7 @@ #include #include +#include #if defined(TREE_SUPPORT_SHOW_ERRORS) && defined(_WIN32) #define TREE_SUPPORT_SHOW_ERRORS_WIN32 @@ -49,7 +50,11 @@ #include #endif // TREE_SUPPORT_ORGANIC_NUDGE_NEW -// #define TREESUPPORT_DEBUG_SVG +#ifndef _L +#define _L(s) Slic3r::I18N::translate(s) +#endif + + //#define TREESUPPORT_DEBUG_SVG namespace Slic3r { @@ -57,16 +62,6 @@ namespace Slic3r namespace TreeSupport3D { -enum class LineStatus -{ - INVALID, - TO_MODEL, - TO_MODEL_GRACIOUS, - TO_MODEL_GRACIOUS_SAFE, - TO_BP, - TO_BP_SAFE -}; - using LineInformation = std::vector>; using LineInformations = std::vector; using namespace std::literals; @@ -74,28 +69,28 @@ using namespace std::literals; static inline void validate_range(const Point &pt) { static constexpr const int32_t hi = 65536 * 16384; - if (pt.x() > hi || pt.y() > hi || -pt.x() > hi || -pt.y() > hi) - throw ClipperLib::clipperException("Coordinate outside allowed range"); + if (pt.x() > hi || pt.y() > hi || -pt.x() > hi || -pt.y() > hi) + throw ClipperLib::clipperException("Coordinate outside allowed range"); } -static inline void validate_range(const Points &points) +static inline void validate_range(const Points &points) { for (const Point &p : points) validate_range(p); } -static inline void validate_range(const MultiPoint &mp) +static inline void validate_range(const MultiPoint &mp) { validate_range(mp.points); } -static inline void validate_range(const Polygons &polygons) +static inline void validate_range(const Polygons &polygons) { for (const Polygon &p : polygons) validate_range(p); } -static inline void validate_range(const Polylines &polylines) +static inline void validate_range(const Polylines &polylines) { for (const Polyline &p : polylines) validate_range(p); @@ -142,7 +137,7 @@ static std::vector>> group_me size_t largest_printed_mesh_idx = 0; - // Group all meshes that can be processed together. NOTE this is different from mesh-groups! Only one setting object is needed per group, + // Group all meshes that can be processed together. NOTE this is different from mesh-groups! Only one setting object is needed per group, // as different settings in the same group may only occur in the tip, which uses the original settings objects from the meshes. for (size_t object_id : print_object_ids) { const PrintObject &print_object = *print.get_object(object_id); @@ -195,7 +190,7 @@ static std::vector>> group_me } #endif -[[nodiscard]] static const std::vector generate_overhangs(const TreeSupportSettings &settings, const PrintObject &print_object, std::function throw_on_cancel) +[[nodiscard]] static const std::vector generate_overhangs(const TreeSupportSettings &settings, PrintObject &print_object, std::function throw_on_cancel) { const size_t num_raft_layers = settings.raft_layers.size(); const size_t num_object_layers = print_object.layer_count(); @@ -216,11 +211,26 @@ static std::vector>> group_me double tan_threshold = support_threshold_auto ? 0. : tan(M_PI * double(support_threshold + 1) / 180.); //FIXME this is a fudge constant! auto enforcer_overhang_offset = scaled(config.tree_support_tip_diameter.value); + const coordf_t radius_sample_resolution = g_config_tree_support_collision_resolution; + + // calc the extrudable expolygons of each layer + const coordf_t extrusion_width = config.line_width.value; + const coordf_t extrusion_width_scaled = scale_(extrusion_width); + tbb::parallel_for(tbb::blocked_range(0, print_object.layer_count()), + [&](const tbb::blocked_range& range) { + for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { + if (print_object.print()->canceled()) + break; + Layer* layer = print_object.get_layer(layer_nr); + // Filter out areas whose diameter that is smaller than extrusion_width, but we don't want to lose any details. + layer->lslices_extrudable = intersection_ex(layer->lslices, offset2_ex(layer->lslices, -extrusion_width_scaled / 2, extrusion_width_scaled)); + } + }); size_t num_overhang_layers = support_auto ? num_object_layers : std::min(num_object_layers, std::max(size_t(support_enforce_layers), enforcers_layers.size())); tbb::parallel_for(tbb::blocked_range(1, num_overhang_layers), - [&print_object, &config, &print_config, &enforcers_layers, &blockers_layers, - support_auto, support_enforce_layers, support_threshold_auto, tan_threshold, enforcer_overhang_offset, num_raft_layers, &throw_on_cancel, &out] + [&print_object, &config, &print_config, &enforcers_layers, &blockers_layers, + support_auto, support_enforce_layers, support_threshold_auto, tan_threshold, enforcer_overhang_offset, num_raft_layers, radius_sample_resolution, &throw_on_cancel, &out] (const tbb::blocked_range &range) { for (LayerIndex layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { const Layer ¤t_layer = *print_object.get_layer(layer_id); @@ -244,22 +254,17 @@ static std::vector>> group_me lower_layer_offset = external_perimeter_width - float(scale_(config.support_threshold_overlap.get_abs_value(unscale_(external_perimeter_width)))); } else lower_layer_offset = scaled(lower_layer.height / tan_threshold); - overhangs = lower_layer_offset == 0 ? - diff(current_layer.lslices, lower_layer.lslices) : - diff(current_layer.lslices, offset(lower_layer.lslices, lower_layer_offset)); + Polygons lower_layer_offseted = offset(lower_layer.lslices_extrudable, lower_layer_offset); + overhangs = diff(current_layer.lslices_extrudable, lower_layer_offseted); if (lower_layer_offset == 0) { raw_overhangs = overhangs; raw_overhangs_calculated = true; } - if (! (enforced_layer || blockers_layers.empty() || blockers_layers[layer_id].empty())) { - Polygons &blocker = blockers_layers[layer_id]; - // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, - // which are not valid polygons, and will be removed by offset. union_ can make these polygons right. - overhangs = diff(overhangs, offset(union_(blocker), scale_(g_config_tree_support_collision_resolution)), ApplySafetyOffset::Yes); - } + if (! (enforced_layer || blockers_layers.empty() || blockers_layers[layer_id].empty())) + overhangs = diff(overhangs, offset_ex(union_(blockers_layers[layer_id]), scale_(radius_sample_resolution)), ApplySafetyOffset::Yes); if (config.bridge_no_support) { for (const LayerRegion *layerm : current_layer.regions()) - remove_bridges_from_contacts(print_config, lower_layer, *layerm, + remove_bridges_from_contacts(print_config, lower_layer, *layerm, float(layerm->flow(frExternalPerimeter).scaled_width()), overhangs); } } @@ -280,7 +285,7 @@ static std::vector>> group_me enforced_overhangs = diff(offset(union_ex(enforced_overhangs), enforcer_overhang_offset), lower_layer.lslices); #ifdef TREESUPPORT_DEBUG_SVG -// if (! intersecting_edges(enforced_overhangs).empty()) +// if (! intersecting_edges(enforced_overhangs).empty()) { static int irun = 0; SVG::export_expolygons(debug_out_path("treesupport-self-intersections-%d.svg", ++irun), @@ -293,7 +298,7 @@ static std::vector>> group_me overhangs = overhangs.empty() ? std::move(enforced_overhangs) : union_(overhangs, enforced_overhangs); //check_self_intersections(overhangs, "generate_overhangs - enforcers"); } - } + } out[layer_id + num_raft_layers] = std::move(overhangs); throw_on_cancel(); } @@ -353,6 +358,28 @@ static std::vector>> group_me return max_layer; } +// picked from convert_lines_to_internal() +[[nodiscard]] LineStatus get_avoidance_status(const Point& p, coord_t radius, LayerIndex layer_idx, + const TreeModelVolumes& volumes, const TreeSupportSettings& config) +{ + const bool min_xy_dist = config.xy_distance > config.xy_min_distance; + + LineStatus type = LineStatus::INVALID; + + if (!contains(volumes.getAvoidance(radius, layer_idx, TreeModelVolumes::AvoidanceType::FastSafe, false, min_xy_dist), p)) + type = LineStatus::TO_BP_SAFE; + else if (!contains(volumes.getAvoidance(radius, layer_idx, TreeModelVolumes::AvoidanceType::Fast, false, min_xy_dist), p)) + type = LineStatus::TO_BP; + else if (config.support_rests_on_model && !contains(volumes.getAvoidance(radius, layer_idx, TreeModelVolumes::AvoidanceType::FastSafe, true, min_xy_dist), p)) + type = LineStatus::TO_MODEL_GRACIOUS_SAFE; + else if (config.support_rests_on_model && !contains(volumes.getAvoidance(radius, layer_idx, TreeModelVolumes::AvoidanceType::Fast, true, min_xy_dist), p)) + type = LineStatus::TO_MODEL_GRACIOUS; + else if (config.support_rests_on_model && !contains(volumes.getCollision(radius, layer_idx, min_xy_dist), p)) + type = LineStatus::TO_MODEL; + + return type; +} + /*! * \brief Converts a Polygons object representing a line into the internal format. * @@ -434,7 +461,7 @@ static std::vector>> group_me return true; if (config.support_rests_on_model && (p.second != LineStatus::TO_BP && p.second != LineStatus::TO_BP_SAFE)) return ! contains( - p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? + p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FastSafe : AvoidanceType::Fast, true, min_xy_dist) : volumes.getCollision(config.getRadius(0), current_layer - 1, min_xy_dist), p.first); @@ -506,7 +533,7 @@ static std::optional> polyline_sample_next_point_at_dis // Squared distance of "start_pt" from the ray (p0, p1). double l2_from_line = xf.squaredNorm(); // Squared distance of an intersection point of a circle with center at the foot point. - if (double l2_intersection = dist2 - l2_from_line; + if (double l2_intersection = dist2 - l2_from_line; l2_intersection > - SCALED_EPSILON) { // The ray (p0, p1) touches or intersects a circle centered at "start_pt" with radius "dist". // Distance of the circle intersection point from the foot point. @@ -601,7 +628,7 @@ static std::optional> polyline_sample_next_point_at_dis } else { if (current_point == next_point->first) { // In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here... - BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance << + BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance << ") is smaller than 100"; tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing."sv, true); if (next_distance > 2 * current_distance) @@ -667,9 +694,9 @@ static std::optional> polyline_sample_next_point_at_dis int divisor = static_cast(angles.size()); int index = ((layer_idx % divisor) + divisor) % divisor; const AngleRadians fill_angle = angles[index]; - Infill roof_computation(pattern, true /* zig_zaggify_infill */, connect_polygons, polygon, - roof ? config.support_roof_line_width : config.support_line_width, support_infill_distance, support_roof_overlap, infill_multiplier, - fill_angle, z, support_shift, config.resolution, wall_line_count, infill_origin, + Infill roof_computation(pattern, true /* zig_zaggify_infill */, connect_polygons, polygon, + roof ? config.support_roof_line_width : config.support_line_width, support_infill_distance, support_roof_overlap, infill_multiplier, + fill_angle, z, support_shift, config.resolution, wall_line_count, infill_origin, perimeter_gaps, connected_zigzags, use_endpieces, false /* skip_some_zags */, zag_skip_count, pocket_size); Polygons polygons; Polygons lines; @@ -686,7 +713,7 @@ static std::optional> polyline_sample_next_point_at_dis filler->layer_id = layer_idx; filler->spacing = flow.spacing(); - filler->angle = roof ? + filler->angle = roof ? //fixme support_layer.interface_id() instead of layer_idx (support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) : support_params.base_angle; @@ -757,7 +784,7 @@ static std::optional> polyline_sample_next_point_at_dis result = union_(offset(to_polylines(first), scaled(0.002), jtMiter, 1.2), offset(to_polylines(second), scaled(0.002), jtMiter, 1.2)); } } - + return result; } @@ -774,7 +801,7 @@ static std::optional> polyline_sample_next_point_at_dis { bool do_final_difference = last_step_offset_without_check == 0; Polygons ret = safe_union(me); // ensure sane input - + // Trim the collision polygons with the region of interest for diff() efficiency. Polygons collision_trimmed_buffer; auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons& { @@ -786,7 +813,7 @@ static std::optional> polyline_sample_next_point_at_dis if (distance == 0) return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); if (safe_step_size < 0 || last_step_offset_without_check < 0) { - BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!"; + BOOST_LOG_TRIVIAL(warning) << "Offset increase got invalid parameter!"; tree_supports_show_error("Negative offset distance... How did you manage this ?"sv, true); return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); } @@ -856,7 +883,7 @@ class RichInterfacePlacer : public InterfacePlacer { // called by sample_overhang_area() void add_points_along_lines( // Insert points (tree tips or top contact interfaces) along these lines. - LineInformations lines, + LineInformations lines, // Start at this layer. LayerIndex insert_layer_idx, // Insert this number of interface layers. @@ -896,7 +923,7 @@ class RichInterfacePlacer : public InterfacePlacer { // add all points that would not be valid for (const LineInformation &line : points) for (const std::pair &point_data : line) - add_point_as_influence_area(point_data, this_layer_idx, + add_point_as_influence_area(point_data, this_layer_idx, // don't move until roof_tip_layers - dtt_roof_tip, // supports roof @@ -983,7 +1010,7 @@ class RichInterfacePlacer : public InterfacePlacer { // Temps coord_t m_base_radius; Polygon m_base_circle; - + // Mutexes, guards std::mutex m_mutex_movebounds; std::vector> m_already_inserted; @@ -1067,10 +1094,10 @@ void finalize_raft_contact( // Produce // 1) Maximum num_support_roof_layers roof (top interface & contact) layers. // 2) Tree tips supporting either the roof layers or the object itself. -// num_support_roof_layers should always be respected: +// num_support_roof_layers should always be respected: // If num_support_roof_layers contact layers could not be produced, then the tree tip // is augmented with SupportElementState::missing_roof_layers -// and the top "missing_roof_layers" of such particular tree tips are supposed to be coverted to +// and the top "missing_roof_layers" of such particular tree tips are supposed to be coverted to // roofs aka interface layers by the tool path generator. void sample_overhang_area( // Area to support @@ -1082,18 +1109,18 @@ void sample_overhang_area( const size_t layer_idx, // Maximum number of roof (contact, interface) layers between the overhang and tree tips to be generated. const size_t num_support_roof_layers, - // + // const coord_t connect_length, // Configuration classes - const TreeSupportMeshGroupSettings &mesh_group_settings, + const TreeSupportMeshGroupSettings& mesh_group_settings, // Configuration & Output - RichInterfacePlacer &interface_placer) + RichInterfacePlacer& interface_placer) { - // Assumption is that roof will support roof further up to avoid a lot of unnecessary branches. Each layer down it is checked whether the roof area - // is still large enough to be a roof and aborted as soon as it is not. This part was already reworked a few times, and there could be an argument + // Assumption is that roof will support roof further up to avoid a lot of unnecessary branches. Each layer down it is checked whether the roof area + // is still large enough to be a roof and aborted as soon as it is not. This part was already reworked a few times, and there could be an argument // made to change it again if there are actual issues encountered regarding supporting roofs. - // Main problem is that some patterns change each layer, so just calculating points and checking if they are still valid an layer below is not useful, - // as the pattern may be different one layer below. Same with calculating which points are now no longer being generated as result from + // Main problem is that some patterns change each layer, so just calculating points and checking if they are still valid an layer below is not useful, + // as the pattern may be different one layer below. Same with calculating which points are now no longer being generated as result from // a decreasing roof, as there is no guarantee that a line will be above these points. Implementing a separate roof support behavior // for each pattern harms maintainability as it very well could be >100 LOC auto generate_roof_lines = [&interface_placer, &mesh_group_settings](const Polygons &area, LayerIndex layer_idx) -> Polylines { @@ -1160,10 +1187,10 @@ void sample_overhang_area( if (overhang_lines.empty()) { // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, but not only is this the only reasonable choice, - // but it ensures consistant behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. + // but it ensures consistant behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. // This is not doen when a roof is above as the roof will support the model and the trees only need to support the roof - bool supports_roof = dtt_roof > 0; - bool continuous_tips = ! supports_roof && large_horizontal_roof; + bool supports_roof = dtt_roof > 0; + bool continuous_tips = !supports_roof && large_horizontal_roof; Polylines polylines = ensure_maximum_distance_polyline( generate_support_infill_lines(overhang_area, interface_placer.support_parameters, supports_roof, layer_idx - layer_generation_dtt, supports_roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance), @@ -1174,10 +1201,10 @@ void sample_overhang_area( const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), coord_t(total_length(overhang_area) / connect_length))); if (point_count <= min_support_points) { // add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the the support line width. - // I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them - // will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, + // I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them + // will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60 degrees so there is a fallback, // as some support is better than none. - Polygons reduced_overhang_area = offset(union_ex(overhang_area), - interface_placer.config.support_line_width / 2.2, jtMiter, 1.2); + Polygons reduced_overhang_area = offset(union_ex(overhang_area), -interface_placer.config.support_line_width / 2.2, jtMiter, 1.2); polylines = ensure_maximum_distance_polyline( to_polylines( ! reduced_overhang_area.empty() && @@ -1243,11 +1270,11 @@ static void generate_initial_areas( const coord_t connect_length = (config.support_line_width * 100. / mesh_group_settings.support_tree_top_rate) + std::max(2. * config.min_radius - 1.0 * config.support_line_width, 0.0); // As r*r=x*x+y*y (circle equation): If a circle with center at (0,0) the top most point is at (0,r) as in y=r. - // This calculates how far one has to move on the x-axis so that y=r-support_line_width/2. + // This calculates how far one has to move on the x-axis so that y=r-support_line_width/2. // In other words how far does one need to move on the x-axis to be support_line_width/2 away from the circle line. // As a circle is round this length is identical for every axis as long as the 90 degrees angle between both remains. - const coord_t circle_length_to_half_linewidth_change = config.min_radius < config.support_line_width ? - config.min_radius / 2 : + const coord_t circle_length_to_half_linewidth_change = config.min_radius < config.support_line_width ? + config.min_radius / 2 : scale_(sqrt(sqr(unscale(config.min_radius)) - sqr(unscale(config.min_radius - config.support_line_width / 2)))); // Extra support offset to compensate for larger tip radiis. Also outset a bit more when z overwrites xy, because supporting something with a part of a support line is better than not supporting it at all. //FIXME Vojtech: This is not sufficient for support enforcers to work. @@ -1260,14 +1287,16 @@ static void generate_initial_areas( const size_t num_support_roof_layers = mesh_group_settings.support_roof_layers; const bool roof_enabled = num_support_roof_layers > 0; const bool force_tip_to_roof = roof_enabled && (interface_placer.support_parameters.soluble_interface || sqr(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area); - // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point - // may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang - // does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. + // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point + // may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang + // does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. // The 2*z_distance_delta is only a catch for when the support angle is very high. // Used only if not min_xy_dist. coord_t max_overhang_insert_lag = 0; if (config.z_distance_top_layers > 0) { max_overhang_insert_lag = 2 * config.z_distance_top_layers; + + //FIXME if (mesh_group_settings.support_angle > EPSILON && mesh_group_settings.support_angle < 0.5 * M_PI - EPSILON) { //FIXME mesh_group_settings.support_angle does not apply to enforcers and also it does not apply to automatic support angle (by half the external perimeter width). //used by max_overhang_insert_lag, only if not min_xy_dist. @@ -1313,12 +1342,12 @@ static void generate_initial_areas( relevant_forbidden = offset(union_ex(relevant_forbidden_raw), scaled(0.005), jtMiter, 1.2); } - // every overhang has saved if a roof should be generated for it. This can NOT be done in the for loop as an area may NOT have a roof - // even if it is larger than the minimum_roof_area when it is only larger because of the support horizontal expansion and + // every overhang has saved if a roof should be generated for it. This can NOT be done in the for loop as an area may NOT have a roof + // even if it is larger than the minimum_roof_area when it is only larger because of the support horizontal expansion and // it would not have a roof if the overhang is offset by support roof horizontal expansion instead. (At least this is the current behavior of the regular support) Polygons overhang_regular; { - // When support_offset = 0 safe_offset_inc will only be the difference between overhang_raw and relevant_forbidden, that has to be calculated anyway. + // When support_offset = 0 safe_offset_inc will only be the difference between overhang_raw and relevant_forbidden, that has to be calculated anyway. overhang_regular = safe_offset_inc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, config.min_radius * 1.75 + config.xy_min_distance, 0, 1); //check_self_intersections(overhang_regular, "overhang_regular1"); @@ -1337,7 +1366,7 @@ static void generate_initial_areas( for (coord_t extra_total_offset_acc = 0; ! remaining_overhang.empty() && extra_total_offset_acc + config.support_line_width / 8 < extra_outset; ) { const coord_t offset_current_step = std::min( extra_total_offset_acc + 2 * config.support_line_width > config.min_radius ? - config.support_line_width / 8 : + config.support_line_width / 8 : circle_length_to_half_linewidth_change, extra_outset - extra_total_offset_acc); extra_total_offset_acc += offset_current_step; @@ -1357,10 +1386,10 @@ static void generate_initial_areas( LineInformations overhang_lines; { //Vojtech: Generate support heads at support_tree_branch_distance spacing by producing a zig-zag infill at support_tree_branch_distance spacing, - // which is then resmapled - // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, - // mbut not only is this the only reasonable choice, but it ensures consistent behavior as some infill patterns generate - // each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that + // which is then resmapled + // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, + // mbut not only is this the only reasonable choice, but it ensures consistent behavior as some infill patterns generate + // each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that // the area that is valid a layer below is to small for support roof. Polylines polylines = ensure_maximum_distance_polyline( generate_support_infill_lines(remaining_overhang, support_params, false, layer_idx, mesh_group_settings.support_tree_branch_distance), @@ -1378,7 +1407,7 @@ static void generate_initial_areas( } for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) { // get least restricted avoidance for layer_idx-lag_ctr - const Polygons &relevant_forbidden_below = config.support_rests_on_model ? + const Polygons &relevant_forbidden_below = config.support_rests_on_model ? volumes.getCollision(config.getRadius(0), layer_idx - lag_ctr, min_xy_dist) : volumes.getAvoidance(config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, false, min_xy_dist); // it is not required to offset the forbidden area here as the points wont change: If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. @@ -1416,8 +1445,9 @@ static void generate_initial_areas( // or roof is enabled and these are the thin overhangs at object slopes (not horizontal overhangs). if (mesh_group_settings.minimum_support_area > 0) remove_small(overhang_regular, mesh_group_settings.minimum_support_area); + for (ExPolygon &support_part : union_ex(overhang_regular)) { - sample_overhang_area(to_polygons(std::move(support_part)), + sample_overhang_area(to_polygons(std::move(support_part)), false, layer_idx, num_support_roof_layers, connect_length, mesh_group_settings, rich_interface_placer); throw_on_cancel(); @@ -1459,7 +1489,7 @@ static unsigned int move_inside(const Polygons &polygons, Point &from, int dista } int64_t dot_prod = ab.dot(ap); if (dot_prod <= 0) { // x is projected to before ab - if (projected_p_beyond_prev_segment) { + if (projected_p_beyond_prev_segment) { // case which looks like: > . projected_p_beyond_prev_segment = false; Point& x = p1; @@ -1494,7 +1524,7 @@ static unsigned int move_inside(const Polygons &polygons, Point &from, int dista p0 = p1; p1 = p2; continue; - } else { + } else { // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | . projected_p_beyond_prev_segment = false; Point x = a + (ab.cast() * (double(dot_prod) / double(ab_length2))).cast(); @@ -1568,7 +1598,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di Polygons &to_bp_data, Polygons &to_model_data, Polygons &increased, - const coord_t overspeed, + const coord_t overspeed, const bool mergelayer) { SupportElementState current_elem{ SupportElementState::propagate_down(parent.state) }; @@ -1581,18 +1611,18 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di if (settings.move) { increased = relevant_offset; if (overspeed > 0) { - const coord_t safe_movement_distance = - (current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + + const coord_t safe_movement_distance = + (current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); // The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later. // The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later. - increased = safe_offset_inc(increased, overspeed, volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist), + increased = safe_offset_inc(increased, overspeed, volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist), safe_movement_distance, safe_movement_distance + radius, 1); } if (settings.no_error && settings.move) // as ClipperLib::jtRound has to be used for offsets this simplify is VERY important for performance. polygons_simplify(increased, scaled(0.025), polygons_strictly_simple); - } else + } else // if no movement is done the areas keep parent area as no move == offset(0) increased = parent.influence_area; @@ -1601,7 +1631,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di if (! current_elem.to_buildplate && area(to_bp_data) > _tiny_area_threshold) { // mostly happening in the tip, but with merges one should check every time, just to be sure. current_elem.to_buildplate = true; // sometimes nodes that can reach the buildplate are marked as cant reach, tainting subtrees. This corrects it. - BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong to model value on layer " << layer_idx - 1 << " targeting " << + BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong to model value on layer " << layer_idx - 1 << " targeting " << current_elem.target_height << " with radius " << radius; } } @@ -1612,7 +1642,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di if (!current_elem.to_model_gracious) { if (mergelayer && area(to_model_data) >= _tiny_area_threshold) { current_elem.to_model_gracious = true; - BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong non gracious value on layer " << layer_idx - 1 << " targeting " << + BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong non gracious value on layer " << layer_idx - 1 << " targeting " << current_elem.target_height << " with radius " << radius; } else // Cannot route to gracious areas. Push the tree away from object and route it down anyways. @@ -1633,8 +1663,8 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di to_bp_data_2 = diff_clipped(increased, volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)); Polygons to_model_data_2; if (config.support_rests_on_model && !current_elem.to_buildplate) - to_model_data_2 = diff_clipped(increased, - current_elem.to_model_gracious ? + to_model_data_2 = diff_clipped(increased, + current_elem.to_model_gracious ? volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : volumes.getCollision(next_radius, layer_idx - 1, settings.use_min_distance)); Polygons check_layer_data_2 = current_elem.to_buildplate ? to_bp_data_2 : to_model_data_2; @@ -1649,8 +1679,8 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di while (current_ceil_radius < target_radius && validWithRadius(volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance))) current_ceil_radius = volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance); size_t resulting_eff_dtt = current_elem.effective_radius_height; - while (resulting_eff_dtt + 1 < current_elem.distance_to_top && - config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= current_ceil_radius && + while (resulting_eff_dtt + 1 < current_elem.distance_to_top && + config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= current_ceil_radius && config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= support_element_radius(config, current_elem)) ++ resulting_eff_dtt; current_elem.effective_radius_height = resulting_eff_dtt; @@ -1658,7 +1688,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di radius = support_element_collision_radius(config, current_elem); const coord_t foot_radius_increase = std::max(config.bp_radius_increase_per_layer - config.branch_radius_increase_per_layer, 0.0); - // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, + // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, // which could cause the radius to become bigger than precalculated. double planned_foot_increase = std::min(1.0, double(config.recommendedMinRadius(layer_idx - 1) - support_element_radius(config, current_elem)) / foot_radius_increase); //FIXME @@ -1675,14 +1705,14 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di if (current_elem.to_buildplate) to_bp_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); if (config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) - to_model_data = safe_union(diff_clipped(increased, - current_elem.to_model_gracious ? + to_model_data = safe_union(diff_clipped(increased, + current_elem.to_model_gracious ? volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance) )); check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; if (area(check_layer_data) < _tiny_area_threshold) { - BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << + BOOST_LOG_TRIVIAL(debug) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << volumes.ceilRadius(support_element_collision_radius(config, current_elem), settings.use_min_distance); tree_supports_show_error("Area lost catching up radius. May not cause visible malformation."sv, true); } @@ -1720,7 +1750,7 @@ struct SupportElementMerging { const Eigen::AlignedBox& bbox() const { return bbox_data;} const Point centroid() const { return (bbox_data.min() + bbox_data.max()) / 2; } - void set_bbox(const BoundingBox& abbox) + void set_bbox(const BoundingBox& abbox) { Point eps { coord_t(SCALED_EPSILON), coord_t(SCALED_EPSILON) }; bbox_data = { abbox.min - eps, abbox.max + eps }; } // Called by the AABBTree builder to get an index into the vector of source elements. @@ -1752,7 +1782,7 @@ static void increase_areas_one_layer( // New areas at the layer below layer_idx std::vector &merging_areas, // Layer above merging_areas. - const LayerIndex layer_idx, + const LayerIndex layer_idx, // Layer elements above merging_areas. SupportElements &layer_elements, // If false, the merging_areas will not be merged for performance reasons. @@ -1768,7 +1798,7 @@ static void increase_areas_one_layer( assert(merging_area.parents.size() == 1); SupportElement &parent = layer_elements[merging_area.parents.front()]; SupportElementState elem = SupportElementState::propagate_down(parent.state); - const Polygons &wall_restriction = + const Polygons &wall_restriction = // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist); @@ -1799,10 +1829,10 @@ static void increase_areas_one_layer( * layer z-1:dddddxxxxxxxxxx * For more detailed visualisation see calculateWallRestrictions */ - const coord_t safe_movement_distance = - (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + + const coord_t safe_movement_distance = + (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); - if (ceiled_parent_radius == volumes.ceilRadius(projected_radius_increased, parent.state.use_min_xy_dist) || + if (ceiled_parent_radius == volumes.ceilRadius(projected_radius_increased, parent.state.use_min_xy_dist) || projected_radius_increased < config.increase_radius_until_radius) // If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of the slower moving wall extra_speed += projected_radius_delta; @@ -1811,7 +1841,7 @@ static void increase_areas_one_layer( // Ensure that the slow movement distance can not become larger than the fast one. extra_slow_speed += std::min(projected_radius_delta, (config.maximum_move_distance + extra_speed) - (config.maximum_move_distance_slow + extra_slow_speed)); - if (config.layer_start_bp_radius > layer_idx && + if (config.layer_start_bp_radius > layer_idx && config.recommendedMinRadius(layer_idx - 1) < config.getRadius(elem.effective_radius_height + 1, elem.elephant_foot_increases)) { // can guarantee elephant foot radius increase if (ceiled_parent_radius == volumes.ceilRadius(config.getRadius(parent.state.effective_radius_height + 1, parent.state.elephant_foot_increases + 1), parent.state.use_min_xy_dist)) @@ -1846,7 +1876,7 @@ static void increase_areas_one_layer( if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && !mergelayer && !avoidance_speed_mismatch && (elem.distance_to_top >= config.tip_layers || parent_moved_slow)) { // assume that the avoidance type that was best for the parent is best for me. Makes this function about 7% faster. - insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, + insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, !increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); @@ -1866,7 +1896,7 @@ static void increase_areas_one_layer( insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); } else { insertSetting({ AvoidanceType::Slow, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); - // while moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, + // while moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, // which looks similar to a layer shift and can reduce stability. // as such idx have chosen to only use the user setting for radius increases as a friendly recommendation. insertSetting({ AvoidanceType::Slow, slow_speed, !increase_radius, no_error, !use_min_radius, move }, true); // a @@ -1903,9 +1933,9 @@ static void increase_areas_one_layer( for (const AreaIncreaseSettings &settings : order) { if (settings.move) { if (offset_slow.empty() && (settings.increase_speed == slow_speed || ! offset_independant_faster)) { - // offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. At this point one can see that the Polygons class + // offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. At this point one can see that the Polygons class // was never made for precision in the single digit micron range. - offset_slow = safe_offset_inc(parent.influence_area, extra_speed + extra_slow_speed + config.maximum_move_distance_slow, + offset_slow = safe_offset_inc(parent.influence_area, extra_speed + extra_slow_speed + config.maximum_move_distance_slow, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2); #ifdef TREESUPPORT_DEBUG_SVG SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-slow-%d-%ld.svg", layer_idx, int(merging_area_idx)), @@ -1915,7 +1945,7 @@ static void increase_areas_one_layer( } if (offset_fast.empty() && settings.increase_speed != slow_speed) { if (offset_independant_faster) - offset_fast = safe_offset_inc(parent.influence_area, extra_speed + config.maximum_move_distance, + offset_fast = safe_offset_inc(parent.influence_area, extra_speed + config.maximum_move_distance, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 1); else { const coord_t delta_slow_fast = config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed); @@ -1930,12 +1960,12 @@ static void increase_areas_one_layer( } std::optional result; inc_wo_collision.clear(); - if (!settings.no_error) { + if (!settings.no_error) { // ERROR CASE - // if the area becomes for whatever reason something that clipper sees as a line, offset would stop working, so ensure that even if if wrongly would be a line, it still actually has an area that can be increased + // if the area becomes for whatever reason something that clipper sees as a line, offset would stop working, so ensure that even if it would be a line wrongly, it still actually has an area that can be increased Polygons lines_offset = offset(to_polylines(parent.influence_area), scaled(0.005), jtMiter, 1.2); Polygons base_error_area = union_(parent.influence_area, lines_offset); - result = increase_single_area(volumes, config, settings, layer_idx, parent, + result = increase_single_area(volumes, config, settings, layer_idx, parent, base_error_area, to_bp_data, to_model_data, inc_wo_collision, (config.maximum_move_distance + extra_speed) * 1.5, mergelayer); #ifdef TREE_SUPPORT_SHOW_ERRORS BOOST_LOG_TRIVIAL(error) @@ -1944,7 +1974,7 @@ static void increase_areas_one_layer( #endif // TREE_SUPPORT_SHOW_ERRORS << "Influence area could not be increased! Data about the Influence area: " "Radius: " << radius << " at layer: " << layer_idx - 1 << " NextTarget: " << elem.layer_idx << " Distance to top: " << elem.distance_to_top << - " Elephant foot increases " << elem.elephant_foot_increases << " use_min_xy_dist " << elem.use_min_xy_dist << " to buildplate " << elem.to_buildplate << + " Elephant foot increases " << elem.elephant_foot_increases << " use_min_xy_dist " << elem.use_min_xy_dist << " to buildplate " << elem.to_buildplate << " gracious " << elem.to_model_gracious << " safe " << elem.can_use_safe_radius << " until move " << elem.dont_move_until << " \n " "Parent " << &parent << ": Radius: " << support_element_collision_radius(config, parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.layer_idx << " Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist << @@ -1976,14 +2006,14 @@ static void increase_areas_one_layer( elem.use_min_xy_dist = false; if (!settings.no_error) #ifdef TREE_SUPPORT_SHOW_ERRORS - BOOST_LOG_TRIVIAL(error) + BOOST_LOG_TRIVIAL(error) #else // TREE_SUPPORT_SHOW_ERRORS BOOST_LOG_TRIVIAL(info) #endif // TREE_SUPPORT_SHOW_ERRORS << "Trying to keep area by moving faster than intended: Success"; break; } else if (!settings.no_error) - BOOST_LOG_TRIVIAL(error) << "Trying to keep area by moving faster than intended: FAILURE! WRONG BRANCHES LIKLY!"; + BOOST_LOG_TRIVIAL(warning) << "Trying to keep area by moving faster than intended: FAILURE! WRONG BRANCHES LIKLY!"; } if (add) { @@ -2003,7 +2033,7 @@ static void increase_areas_one_layer( merging_area.areas.to_model_areas = std::move(to_model_data); } } else { - // If the bottom most point of a branch is set, later functions will assume that the position is valid, and ignore it. + // If the bottom most point of a branch is set, later functions will assume that the position is valid, and ignore it. // But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. // A point can be set on the top most tip layer (maybe more if it should not move for a few layers). parent.state.result_on_layer_reset(); @@ -2047,7 +2077,7 @@ static void increase_areas_one_layer( out.elephant_foot_increases = 0; if (config.bp_radius_increase_per_layer > 0) { coord_t foot_increase_radius = std::abs(std::max(support_element_collision_radius(config, second), support_element_collision_radius(config, first)) - support_element_collision_radius(config, out)); - // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch + // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch // the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. out.elephant_foot_increases = foot_increase_radius / (config.bp_radius_increase_per_layer - config.branch_radius_increase_per_layer); } @@ -2070,8 +2100,8 @@ static bool merge_influence_areas_two_elements( { // Don't merge gracious with a non gracious area as bad placement could negatively impact reliability of the whole subtree. const bool merging_gracious_and_non_gracious = dst.state.to_model_gracious != src.state.to_model_gracious; - // Could cause some issues with the increase of one area, as it is assumed that if the smaller is increased - // by the delta to the larger it is engulfed by it already. But because a different collision + // Could cause some issues with the increase of one area, as it is assumed that if the smaller is increased + // by the delta to the larger it is engulfed by it already. But because a different collision // may be removed from the in draw_area() generated circles, this assumption could be wrong. const bool merging_min_and_regular_xy = dst.state.use_min_xy_dist != src.state.use_min_xy_dist; @@ -2115,10 +2145,10 @@ static bool merge_influence_areas_two_elements( if (increased_to_model_radius > config.max_to_model_radius_increase) return false; } - // if a merge could place a stable branch on unstable ground, would be increasing the radius further - // than allowed to when merging to model and to_bp trees or would merge to model before it is known + // if a merge could place a stable branch on unstable ground, would be increasing the radius further + // than allowed to when merging to model and to_bp trees or would merge to model before it is known // they will even been drawn the merge is skipped - if (! dst.state.supports_roof && ! src.state.supports_roof && + if (! dst.state.supports_roof && ! src.state.supports_roof && std::max(src.state.distance_to_top, dst.state.distance_to_top) < config.min_dtt_to_model) return false; } @@ -2129,7 +2159,7 @@ static bool merge_influence_areas_two_elements( return false; // the bigger radius is used to verify that the area is still valid after the increase with the delta. - // If there were a point where the big influence area could be valid with can_use_safe_radius + // If there were a point where the big influence area could be valid with can_use_safe_radius // the element would already be can_use_safe_radius. // the smaller radius, which gets increased by delta may reach into the area where use_min_xy_dist is no longer required. const bool use_min_radius = bigger_rad.state.use_min_xy_dist && smaller_rad.state.use_min_xy_dist; @@ -2160,7 +2190,7 @@ static bool merge_influence_areas_two_elements( const auto _tiny_area_threshold = tiny_area_threshold(); // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) - // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). + // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). if (area(intersect) <= _tiny_area_threshold) return false; @@ -2182,7 +2212,7 @@ static bool merge_influence_areas_two_elements( Point new_pos = move_inside_if_outside(intersect, dst.state.next_position); SupportElementState new_state = merge_support_element_states(dst.state, src.state, new_pos, layer_idx - 1, config); - new_state.increased_to_model_radius = increased_to_model_radius == 0 ? + new_state.increased_to_model_radius = increased_to_model_radius == 0 ? // increased_to_model_radius was not set yet. Propagate maximum. std::max(dst.state.increased_to_model_radius, src.state.increased_to_model_radius) : increased_to_model_radius; @@ -2270,7 +2300,7 @@ static SupportElementMerging* merge_influence_areas_two_sets( SupportElementMerging * const dst_begin, SupportElementMerging * dst_end, SupportElementMerging * src_begin, SupportElementMerging * const src_end) { - // Merging src into dst. + // Merging src into dst. // Areas of src should not overlap with areas of another elements of src. // Areas of dst should not overlap with areas of another elements of dst. // The memory from dst_begin to src_end is reserved for the merging operation, @@ -2323,8 +2353,8 @@ static SupportElementMerging* merge_influence_areas_two_sets( * \param layer_idx[in] The current layer. */ static void merge_influence_areas( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, const LayerIndex layer_idx, std::vector &influence_areas, std::function throw_on_cancel) @@ -2466,7 +2496,7 @@ static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupp return true; if (elem.areas.to_bp_areas.empty() && elem.areas.to_model_areas.empty()) { if (area(elem.areas.influence_areas) < _tiny_area_threshold) { - BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; + BOOST_LOG_TRIVIAL(warning) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; tree_supports_show_error("Insert error of area after bypassing merge.\n"sv, true); } // Move the area to output. @@ -2499,7 +2529,7 @@ static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupp if (! elem.areas.influence_areas.empty()) { Polygons new_area = safe_union(elem.areas.influence_areas); if (area(new_area) < _tiny_area_threshold) { - BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.state.to_buildplate; + BOOST_LOG_TRIVIAL(warning) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.state.to_buildplate; tree_supports_show_error("Insert error of area after merge.\n"sv, true); } this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(new_area)); @@ -2512,7 +2542,7 @@ static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupp throw_on_cancel(); } - BOOST_LOG_TRIVIAL(info) << "Time spent with creating influence areas' subtasks: Increasing areas " << dur_inc.count() / 1000000 << + BOOST_LOG_TRIVIAL(info) << "Time spent with creating influence areas' subtasks: Increasing areas " << dur_inc.count() / 1000000 << " ms merging areas: " << (dur_total - dur_inc).count() / 1000000 << " ms"; } @@ -2528,7 +2558,7 @@ static void set_points_on_areas(const SupportElement &elem, SupportElements *lay // Based on the branch center point of the current layer, the point on the next (further up) layer is calculated. if (! elem.state.result_on_layer_is_set()) { - BOOST_LOG_TRIVIAL(error) << "Uninitialized support element"; + BOOST_LOG_TRIVIAL(warning) << "Uninitialized support element"; tree_supports_show_error("Uninitialized support element. A branch may be missing.\n"sv, true); return; } @@ -2541,7 +2571,7 @@ static void set_points_on_areas(const SupportElement &elem, SupportElements *lay // if the value was set somewhere else it it kept. This happens when a branch tries not to move after being unable to create a roof. if (! next_elem.state.result_on_layer_is_set()) { // Move inside has edgecases (see tests) so DONT use Polygons.inside to confirm correct move, Error with distance 0 is <= 1 - // it is not required to check if how far this move moved a point as is can be larger than maximum_movement_distance. + // it is not required to check if how far this move moved a point as is can be larger than maximum_movement_distance. // While this seems like a problem it may for example occur after merges. next_elem.state.result_on_layer = move_inside_if_outside(next_elem.influence_area, elem.state.result_on_layer); // do not call recursive because then amount of layers would be restricted by the stack size @@ -2566,9 +2596,9 @@ static void set_to_model_contact_simple(SupportElement &elem) * \param layer_idx[in] The current layer. */ static void set_to_model_contact_to_model_gracious( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - std::vector &move_bounds, + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + std::vector &move_bounds, SupportElement &first_elem, std::function throw_on_cancel) { @@ -2637,7 +2667,7 @@ static void remove_deleted_elements(std::vector &move_bounds) assert(i == layer.size() || i + 1 < layer.size()); if (i + 1 < int32_t(layer.size())) { element = std::move(layer.back()); - layer.pop_back(); + layer.pop_back(); // Mark the current element as deleted. map_current[i] = -1; // Mark the moved element as moved to index i. @@ -2666,7 +2696,7 @@ static void create_nodes_from_area( std::vector &move_bounds, std::function throw_on_cancel) { - // Initialize points on layer 0, with a "random" point in the influence area. + // Initialize points on layer 0, with a "random" point in the influence area. // Point is chosen based on an inaccurate estimate where the branches will split into two, but every point inside the influence area would produce a valid result. { SupportElements *layer_above = move_bounds.size() > 1 ? &move_bounds[1] : nullptr; @@ -2696,7 +2726,8 @@ static void create_nodes_from_area( if (! elem.state.result_on_layer_is_set()) { if (elem.state.to_buildplate || (elem.state.distance_to_top < config.min_dtt_to_model && ! elem.state.supports_roof)) { if (elem.state.to_buildplate) { - BOOST_LOG_TRIVIAL(error) << "Uninitialized Influence area targeting " << elem.state.target_position.x() << "," << elem.state.target_position.y() << ") " + BOOST_LOG_TRIVIAL(warning) << "Uninitialized Influence area targeting " << elem.state.target_position.x() << "," << elem.state.target_position.y() + << ") " "at target_height: " << elem.state.target_height << " layer: " << layer_idx; tree_supports_show_error("Uninitialized support element! A branch could be missing or exist partially."sv, true); } @@ -2776,633 +2807,6 @@ static void create_nodes_from_area( #endif // NDEBUG } -// For producing circular / elliptical areas from SupportElements (one DrawArea per one SupportElement) -// and for smoothing those areas along the tree branches. -struct DrawArea -{ - // Element to be processed. - SupportElement *element; - // Element below, if there is such an element. nullptr if element is a root of a tree. - SupportElement *child_element; - // Polygons to be extruded for this element. - Polygons polygons; -}; - -/*! - * \brief Draws circles around result_on_layer points of the influence areas - * - * \param linear_data[in] All currently existing influence areas with the layer they are on - * \param layer_tree_polygons[out] Resulting branch areas with the layerindex they appear on. layer_tree_polygons.size() has to be at least linear_data.size() as each Influence area in linear_data will save have at least one (that's why it's a vector) corresponding branch area in layer_tree_polygons. - * \param inverse_tree_order[in] A mapping that returns the child of every influence area. - */ -static void generate_branch_areas( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const std::vector &move_bounds, - std::vector &linear_data, - std::function throw_on_cancel) -{ -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC; - constexpr int progress_report_steps = 10; - const size_t progress_inserts_check_interval = linear_data.size() / progress_report_steps; - std::mutex critical_sections; -#endif // SLIC3R_TREESUPPORTS_PROGRESS - - // Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time. - const Polygon branch_circle = make_circle(config.branch_radius, SUPPORT_TREE_CIRCLE_RESOLUTION); - - tbb::parallel_for(tbb::blocked_range(0, linear_data.size()), - [&volumes, &config, &move_bounds, &linear_data, &branch_circle, &throw_on_cancel](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - DrawArea &draw_area = linear_data[idx]; - const LayerIndex layer_idx = draw_area.element->state.layer_idx; - const coord_t radius = support_element_radius(config, *draw_area.element); - bool parent_uses_min = false; - - // Calculate multiple ovalized circles, to connect with every parent and child. Also generate regular circle for the current layer. Merge all these into one area. - std::vector> movement_directions{ std::pair(Point(0, 0), radius) }; - if (! draw_area.element->state.skip_ovalisation) { - if (draw_area.child_element != nullptr) { - const Point movement = draw_area.child_element->state.result_on_layer - draw_area.element->state.result_on_layer; - movement_directions.emplace_back(movement, radius); - } - const SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; - for (int32_t parent_idx : draw_area.element->parents) { - const SupportElement &parent = (*layer_above)[parent_idx]; - const Point movement = parent.state.result_on_layer - draw_area.element->state.result_on_layer; - //FIXME why max(..., config.support_line_width)? - movement_directions.emplace_back(movement, std::max(support_element_radius(config, parent), config.support_line_width)); - parent_uses_min |= parent.state.use_min_xy_dist; - } - } - - const Polygons &collision = volumes.getCollision(0, layer_idx, parent_uses_min || draw_area.element->state.use_min_xy_dist); - auto generateArea = [&collision, &draw_area, &branch_circle, branch_radius = config.branch_radius, support_line_width = config.support_line_width, &movement_directions] - (coord_t aoffset, double &max_speed) { - Polygons poly; - max_speed = 0; - for (std::pair movement : movement_directions) { - max_speed = std::max(max_speed, movement.first.cast().norm()); - - // Visualization: https://jsfiddle.net/0zvcq39L/2/ - // Ovalizes the circle to an ellipse, that contains both old center and new target position. - double used_scale = (movement.second + aoffset) / (1.0 * branch_radius); - Point center_position = draw_area.element->state.result_on_layer + movement.first / 2; - const double moveX = movement.first.x() / (used_scale * branch_radius); - const double moveY = movement.first.y() / (used_scale * branch_radius); - const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); - - double matrix[] = { - used_scale * (1 + moveX * moveX * vsize_inv), - used_scale * (0 + moveX * moveY * vsize_inv), - used_scale * (0 + moveX * moveY * vsize_inv), - used_scale * (1 + moveY * moveY * vsize_inv), - }; - Polygon circle; - for (Point vertex : branch_circle) - circle.points.emplace_back(center_position + Point(matrix[0] * vertex.x() + matrix[1] * vertex.y(), matrix[2] * vertex.x() + matrix[3] * vertex.y())); - poly.emplace_back(std::move(circle)); - } - - // There seem to be some rounding errors, causing a branch to be a tiny bit further away from the model that it has to be. - // This can cause the tip to be slightly further away front the overhang (x/y wise) than optimal. This fixes it, and for every other part, 0.05mm will not be noticed. - poly = diff_clipped(offset(union_(poly), std::min(coord_t(50), support_line_width / 4), jtMiter, 1.2), collision); - return poly; - }; - - // Ensure branch area will not overlap with model/collision. This can happen because of e.g. ovalization or increase_until_radius. - double max_speed; - Polygons polygons = generateArea(0, max_speed); - const bool fast_relative_movement = max_speed > radius * 0.75; - - if (fast_relative_movement || support_element_radius(config, *draw_area.element) - support_element_collision_radius(config, draw_area.element->state) > config.support_line_width) { - // Simulate the path the nozzle will take on the outermost wall. - // If multiple parts exist, the outer line will not go all around the support part potentially causing support material to be printed mid air. - ExPolygons nozzle_path = offset_ex(polygons, - config.support_line_width / 2); - if (nozzle_path.size() > 1) { - // Just try to make the area a tiny bit larger. - polygons = generateArea(config.support_line_width / 2, max_speed); - nozzle_path = offset_ex(polygons, -config.support_line_width / 2); - // If larger area did not fix the problem, all parts off the nozzle path that do not contain the center point are removed, hoping for the best. - if (nozzle_path.size() > 1) { - ExPolygons polygons_with_correct_center; - for (ExPolygon &part : nozzle_path) { - bool drop = false; - if (! part.contains(draw_area.element->state.result_on_layer)) { - // try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... - Point pt = draw_area.element->state.result_on_layer; - move_inside(to_polygons(part), pt, 0); - drop = (draw_area.element->state.result_on_layer - pt).cast().norm() >= scaled(0.025); - } - if (! drop) - polygons_with_correct_center.emplace_back(std::move(part)); - } - // Increase the area again, to ensure the nozzle path when calculated later is very similar to the one assumed above. - assert(contains(polygons, draw_area.element->state.result_on_layer)); - polygons = diff_clipped(offset(polygons_with_correct_center, config.support_line_width / 2, jtMiter, 1.2), - //FIXME Vojtech: Clipping may split the region into multiple pieces again, reversing the fixing effort. - collision); - } - } - } - - draw_area.polygons = std::move(polygons); - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - if (idx % progress_inserts_check_interval == 0) { - std::lock_guard critical_section_progress(critical_sections); - progress_total += TREE_PROGRESS_GENERATE_BRANCH_AREAS / progress_report_steps; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - } -#endif - throw_on_cancel(); - } - }); -} - -/*! - * \brief Applies some smoothing to the outer wall, intended to smooth out sudden jumps as they can happen when a branch moves though a hole. - * - * \param layer_tree_polygons[in,out] Resulting branch areas with the layerindex they appear on. - */ -static void smooth_branch_areas( - const TreeSupportSettings &config, - std::vector &move_bounds, - std::vector &linear_data, - const std::vector &linear_data_layers, - std::function throw_on_cancel) -{ -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS; -#endif // SLIC3R_TREESUPPORTS_PROGRESS - - const coord_t max_radius_change_per_layer = 1 + config.support_line_width / 2; // this is the upper limit a radius may change per layer. +1 to avoid rounding errors - - // smooth upwards - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()) - 1; ++ layer_idx) { - const size_t processing_base = linear_data_layers[layer_idx]; - const size_t processing_base_above = linear_data_layers[layer_idx + 1]; - const SupportElements &layer_above = move_bounds[layer_idx + 1]; - tbb::parallel_for(tbb::blocked_range(0, processing_base_above - processing_base), - [&](const tbb::blocked_range &range) { - for (size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { - DrawArea &draw_area = linear_data[processing_base + processing_idx]; - assert(draw_area.element->state.layer_idx == layer_idx); - double max_outer_wall_distance = 0; - bool do_something = false; - for (int32_t parent_idx : draw_area.element->parents) { - const SupportElement &parent = layer_above[parent_idx]; - assert(parent.state.layer_idx == layer_idx + 1); - if (support_element_radius(config, parent) != support_element_collision_radius(config, parent)) { - do_something = true; - max_outer_wall_distance = std::max(max_outer_wall_distance, - (draw_area.element->state.result_on_layer - parent.state.result_on_layer).cast().norm() - (support_element_radius(config, *draw_area.element) - support_element_radius(config, parent))); - } - } - max_outer_wall_distance += max_radius_change_per_layer; // As this change is a bit larger than what usually appears, lost radius can be slowly reclaimed over the layers. - if (do_something) { - assert(contains(draw_area.polygons, draw_area.element->state.result_on_layer)); - Polygons max_allowed_area = offset(draw_area.polygons, float(max_outer_wall_distance), jtMiter, 1.2); - for (int32_t parent_idx : draw_area.element->parents) { - const SupportElement &parent = layer_above[parent_idx]; -#ifndef NDEBUG - assert(parent.state.layer_idx == layer_idx + 1); - assert(contains(linear_data[processing_base_above + parent_idx].polygons, parent.state.result_on_layer)); - double radius_increase = support_element_radius(config, *draw_area.element) - support_element_radius(config, parent); - assert(radius_increase >= 0); - double shift = (draw_area.element->state.result_on_layer - parent.state.result_on_layer).cast().norm(); - assert(shift < radius_increase + 2. * config.maximum_move_distance_slow); -#endif // NDEBUG - if (support_element_radius(config, parent) != support_element_collision_radius(config, parent)) { - // No other element on this layer than the current one may be connected to &parent, - // thus it is safe to update parent's DrawArea directly. - Polygons &dst = linear_data[processing_base_above + parent_idx].polygons; -// Polygons orig = dst; - if (! dst.empty()) { - dst = intersection(dst, max_allowed_area); -#if 0 - if (dst.empty()) { - static int irun = 0; - SVG::export_expolygons(debug_out_path("treesupport-extrude_areas-smooth-error-%d.svg", irun ++), - { { { union_ex(max_allowed_area) }, { "max_allowed_area", "yellow", 0.5f } }, - { { union_ex(orig) }, { "orig", "red", "black", "", scaled(0.1f), 0.5f } } }); - ::MessageBoxA(nullptr, "TreeSupport smoothing bug", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); - } -#endif - } - } - } - } - throw_on_cancel(); - } - }); - } - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - progress_total += TREE_PROGRESS_SMOOTH_BRANCH_AREAS / 2; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); // It is just assumed that both smoothing loops together are one third of the time spent in this function. This was guessed. As the whole function is only 10%, and the smoothing is hard to predict a progress report in the loop may be not useful. -#endif - - // smooth downwards - for (auto& element : move_bounds.back()) - element.state.marked = false; - for (int layer_idx = int(move_bounds.size()) - 2; layer_idx >= 0; -- layer_idx) { - const size_t processing_base = linear_data_layers[layer_idx]; - const size_t processing_base_above = linear_data_layers[layer_idx + 1]; - const SupportElements &layer_above = move_bounds[layer_idx + 1]; - tbb::parallel_for(tbb::blocked_range(0, processing_base_above - processing_base), - [&](const tbb::blocked_range &range) { - for (size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { - DrawArea &draw_area = linear_data[processing_base + processing_idx]; - bool do_something = false; - Polygons max_allowed_area; - for (int32_t parent_idx : draw_area.element->parents) { - const SupportElement &parent = layer_above[parent_idx]; - coord_t max_outer_line_increase = max_radius_change_per_layer; - Polygons result = offset(linear_data[processing_base_above + parent_idx].polygons, max_outer_line_increase, jtMiter, 1.2); - Point direction = draw_area.element->state.result_on_layer - parent.state.result_on_layer; - // move the polygons object - for (auto &outer : result) - for (Point& p : outer) - p += direction; - append(max_allowed_area, std::move(result)); - do_something = do_something || parent.state.marked || support_element_collision_radius(config, parent) != support_element_radius(config, parent); - } - if (do_something) { - // Trim the current drawing areas with max_allowed_area. - Polygons result = intersection(max_allowed_area, draw_area.polygons); - if (area(result) < area(draw_area.polygons)) { - // Mark parent as modified to propagate down. - draw_area.element->state.marked = true; - draw_area.polygons = std::move(result); - } - } - throw_on_cancel(); - } - }); - } - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - progress_total += TREE_PROGRESS_SMOOTH_BRANCH_AREAS / 2; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); -#endif -} - -/*! - * \brief Drop down areas that do rest non-gracefully on the model to ensure the branch actually rests on something. - * - * \param layer_tree_polygons[in] Resulting branch areas with the layerindex they appear on. - * \param linear_data[in] All currently existing influence areas with the layer they are on - * \param dropped_down_areas[out] Areas that have to be added to support all non-graceful areas. - * \param inverse_tree_order[in] A mapping that returns the child of every influence area. - */ -static void drop_non_gracious_areas( - const TreeModelVolumes &volumes, - const std::vector &linear_data, - std::vector &support_layer_storage, - std::function throw_on_cancel) -{ - const auto _tiny_area_threshold = tiny_area_threshold(); - std::vector>> dropped_down_areas(linear_data.size()); - tbb::parallel_for(tbb::blocked_range(0, linear_data.size()), - [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - // If a element has no child, it connects to whatever is below as no support further down for it will exist. - if (const DrawArea &draw_element = linear_data[idx]; ! draw_element.element->state.to_model_gracious && draw_element.child_element == nullptr) { - Polygons rest_support; - const LayerIndex layer_idx_first = draw_element.element->state.layer_idx - 1; - for (LayerIndex layer_idx = layer_idx_first; area(rest_support) > _tiny_area_threshold && layer_idx >= 0; -- layer_idx) { - rest_support = diff_clipped(layer_idx == layer_idx_first ? draw_element.polygons : rest_support, volumes.getCollision(0, layer_idx, false)); - dropped_down_areas[idx].emplace_back(layer_idx, rest_support); - } - } - throw_on_cancel(); - } - }); - - for (coord_t i = 0; i < static_cast(dropped_down_areas.size()); i++) - for (std::pair &pair : dropped_down_areas[i]) - append(support_layer_storage[pair.first], std::move(pair.second)); -} - -/*! - * \brief Generates Support Floor, ensures Support Roof can not cut of branches, and saves the branches as support to storage - * - * \param support_layer_storage[in] Areas where support should be generated. - * \param support_roof_storage[in] Areas where support was replaced with roof. - * \param storage[in,out] The storage where the support should be stored. - */ -static void finalize_interface_and_support_areas( - const PrintObject &print_object, - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const std::vector &overhangs, - std::vector &support_layer_storage, - std::vector &support_roof_storage, - - SupportGeneratorLayersPtr &bottom_contacts, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage, - - std::function throw_on_cancel) -{ - assert(std::all_of(bottom_contacts.begin(), bottom_contacts.end(), [](auto *p) { return p == nullptr; })); -// assert(std::all_of(top_contacts.begin(), top_contacts.end(), [](auto* p) { return p == nullptr; })); - assert(std::all_of(intermediate_layers.begin(), intermediate_layers.end(), [](auto* p) { return p == nullptr; })); - - InterfacePreference interface_pref = config.interface_preference; // InterfacePreference::InterfaceAreaOverwritesSupport; - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS + TREE_PROGRESS_SMOOTH_BRANCH_AREAS; -#endif // SLIC3R_TREESUPPORTS_PROGRESS - - // Iterate over the generated circles in parallel and clean them up. Also add support floor. - tbb::parallel_for(tbb::blocked_range(0, support_layer_storage.size()), - [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - // Subtract support lines of the branches from the roof - SupportGeneratorLayer *support_roof = top_contacts[layer_idx]; - Polygons support_roof_polygons; - - if (Polygons &src = support_roof_storage[layer_idx]; ! src.empty()) { - if (support_roof != nullptr && ! support_roof->polygons.empty()) { - support_roof_polygons = union_(src, support_roof->polygons); - support_roof->polygons.clear(); - } else - support_roof_polygons = std::move(src); - } else if (support_roof != nullptr) { - support_roof_polygons = std::move(support_roof->polygons); - support_roof->polygons.clear(); - } - - assert(intermediate_layers[layer_idx] == nullptr); - Polygons base_layer_polygons = std::move(support_layer_storage[layer_idx]); - - if (! base_layer_polygons.empty()) { - // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. - base_layer_polygons = smooth_outward(union_(base_layer_polygons), config.support_line_width); //FIXME was .smooth(50); - //smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : - // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. - base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution)), polygons_strictly_simple); - } - - if (! support_roof_polygons.empty() && ! base_layer_polygons.empty()) { -// if (area(intersection(base_layer_polygons, support_roof_polygons)) > tiny_area_threshold) - { - switch (interface_pref) { - case InterfacePreference::InterfaceAreaOverwritesSupport: - base_layer_polygons = diff(base_layer_polygons, support_roof_polygons); - break; - case InterfacePreference::SupportAreaOverwritesInterface: - support_roof_polygons = diff(support_roof_polygons, base_layer_polygons); - break; - //FIXME - #if 1 - case InterfacePreference::InterfaceLinesOverwriteSupport: - case InterfacePreference::SupportLinesOverwriteInterface: - assert(false); - [[fallthrough]]; - #else - case InterfacePreference::InterfaceLinesOverwriteSupport: - { - // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. - Polygons interface_lines = offset(to_polylines( - generate_support_infill_lines(support_roof->polygons, true, layer_idx, config.support_roof_line_distance)), - config.support_roof_line_width / 2); - base_layer_polygons = diff(base_layer_polygons, interface_lines); - break; - } - case InterfacePreference::SupportLinesOverwriteInterface: - { - // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. - Polygons tree_lines = union_(offset(to_polylines( - generate_support_infill_lines(base_layer_polygons, false, layer_idx, config.support_line_distance, true)), - config.support_line_width / 2)); - // do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. - support_roof->polygons = diff(support_roof->polygons, tree_lines); - break; - } - #endif - case InterfacePreference::Nothing: - break; - } - } - } - - // Subtract support floors from the support area and add them to the support floor instead. - if (config.support_bottom_layers > 0 && ! base_layer_polygons.empty()) { - SupportGeneratorLayer*& support_bottom = bottom_contacts[layer_idx]; - Polygons layer_outset = diff_clipped( - config.support_bottom_offset > 0 ? offset(base_layer_polygons, config.support_bottom_offset, jtMiter, 1.2) : base_layer_polygons, - volumes.getCollision(0, layer_idx, false)); - Polygons floor_layer; - size_t layers_below = 0; - while (layers_below <= config.support_bottom_layers) { - // one sample at 0 layers below, another at config.support_bottom_layers. In-between samples at config.performance_interface_skip_layers distance from each other. - const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(config.z_distance_bottom_layers))); - //FIXME subtract the wipe tower - append(floor_layer, intersection(layer_outset, overhangs[sample_layer])); - if (layers_below < config.support_bottom_layers) - layers_below = std::min(layers_below + 1, config.support_bottom_layers); - else - break; - } - if (! floor_layer.empty()) { - if (support_bottom == nullptr) - support_bottom = &layer_allocate(layer_storage, SupporLayerType::BottomContact, print_object.slicing_parameters(), config, layer_idx); - support_bottom->polygons = union_(floor_layer, support_bottom->polygons); - base_layer_polygons = diff_clipped(base_layer_polygons, offset(support_bottom->polygons, scaled(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support. - } - } - - if (! support_roof_polygons.empty()) { - if (support_roof == nullptr) - support_roof = top_contacts[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, layer_idx); - support_roof->polygons = union_(support_roof_polygons); - } - if (! base_layer_polygons.empty()) { - SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::Base, print_object.slicing_parameters(), config, layer_idx); - base_layer->polygons = union_(base_layer_polygons); - } - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - { - std::lock_guard critical_section_progress(critical_sections); - progress_total += TREE_PROGRESS_FINALIZE_BRANCH_AREAS / support_layer_storage.size(); - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - } -#endif -#if 0 - { - std::lock_guard lock(critical_sections); - if (!storage.support.supportLayers[layer_idx].support_infill_parts.empty() || !storage.support.supportLayers[layer_idx].support_roof.empty()) - storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, static_cast(layer_idx)); - } -#endif - throw_on_cancel(); - } - }); -} - -/*! - * \brief Draws circles around result_on_layer points of the influence areas and applies some post processing. - * - * \param move_bounds[in] All currently existing influence areas - * \param storage[in,out] The storage where the support should be stored. - */ -static void draw_areas( - PrintObject &print_object, - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const std::vector &overhangs, - std::vector &move_bounds, - - SupportGeneratorLayersPtr &bottom_contacts, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage, - std::function throw_on_cancel) -{ - std::vector support_layer_storage(move_bounds.size()); - std::vector support_roof_storage(move_bounds.size()); - // All SupportElements are put into a layer independent storage to improve parallelization. - std::vector linear_data; - std::vector linear_data_layers; - { - std::vector> map_downwards_old; - std::vector> map_downwards_new; - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { - SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; - map_downwards_new.clear(); - linear_data_layers.emplace_back(linear_data.size()); - std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto &l, auto &r) { return l.first < r.first; }); - for (SupportElement &elem : move_bounds[layer_idx]) { - SupportElement *child = nullptr; - if (layer_idx > 0) { - auto it = std::lower_bound(map_downwards_old.begin(), map_downwards_old.end(), &elem, [](auto &l, const SupportElement *r) { return l.first < r; }); - if (it != map_downwards_old.end() && it->first == &elem) { - child = it->second; - // Only one link points to a node above from below. - assert(! (++ it != map_downwards_old.end() && it->first == &elem)); - } - assert(child ? child->state.result_on_layer_is_set() : elem.state.target_height > layer_idx); - } - for (int32_t parent_idx : elem.parents) { - SupportElement &parent = (*layer_above)[parent_idx]; - if (parent.state.result_on_layer_is_set()) - map_downwards_new.emplace_back(&parent, &elem); - } - linear_data.push_back({ &elem, child }); - } - std::swap(map_downwards_old, map_downwards_new); - } - linear_data_layers.emplace_back(linear_data.size()); - } - - throw_on_cancel(); - -#ifndef NDEBUG - for (size_t i = 0; i < move_bounds.size(); ++ i) { - size_t begin = linear_data_layers[i]; - size_t end = linear_data_layers[i + 1]; - for (size_t j = begin; j < end; ++ j) - assert(linear_data[j].element == &move_bounds[i][j - begin]); - } -#endif // NDEBUG - - auto t_start = std::chrono::high_resolution_clock::now(); - // Generate the circles that will be the branches. - generate_branch_areas(volumes, config, move_bounds, linear_data, throw_on_cancel); - -#if 0 - assert(linear_data_layers.size() == move_bounds.size() + 1); - for (const auto &draw_area : linear_data) - assert(contains(draw_area.polygons, draw_area.element->state.result_on_layer)); - for (size_t i = 0; i < move_bounds.size(); ++ i) { - size_t begin = linear_data_layers[i]; - size_t end = linear_data_layers[i + 1]; - for (size_t j = begin; j < end; ++ j) { - const auto &draw_area = linear_data[j]; - assert(draw_area.element == &move_bounds[i][j - begin]); - assert(contains(draw_area.polygons, draw_area.element->state.result_on_layer)); - } - } -#endif - -#if 0 - for (size_t area_layer_idx = 0; area_layer_idx + 1 < linear_data_layers.size(); ++ area_layer_idx) { - size_t begin = linear_data_layers[area_layer_idx]; - size_t end = linear_data_layers[area_layer_idx + 1]; - Polygons polygons; - for (size_t area_idx = begin; area_idx < end; ++ area_idx) { - DrawArea &area = linear_data[area_idx]; - append(polygons, area.polygons); - } - SVG::export_expolygons(debug_out_path("treesupport-extrude_areas-raw-%d.svg", area_layer_idx), - { { { union_ex(polygons) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); - } -#endif - - auto t_generate = std::chrono::high_resolution_clock::now(); - // In some edgecases a branch may go though a hole, where the regular radius does not fit. This can result in an apparent jump in branch radius. As such this cases need to be caught and smoothed out. - smooth_branch_areas(config, move_bounds, linear_data, linear_data_layers, throw_on_cancel); - -#if 0 - for (size_t area_layer_idx = 0; area_layer_idx + 1 < linear_data_layers.size(); ++area_layer_idx) { - size_t begin = linear_data_layers[area_layer_idx]; - size_t end = linear_data_layers[area_layer_idx + 1]; - Polygons polygons; - for (size_t area_idx = begin; area_idx < end; ++area_idx) { - DrawArea& area = linear_data[area_idx]; - append(polygons, area.polygons); - } - SVG::export_expolygons(debug_out_path("treesupport-extrude_areas-smooth-%d.svg", area_layer_idx), - { { { union_ex(polygons) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); - } -#endif - - auto t_smooth = std::chrono::high_resolution_clock::now(); - // drop down all trees that connect non gracefully with the model - drop_non_gracious_areas(volumes, linear_data, support_layer_storage, throw_on_cancel); - auto t_drop = std::chrono::high_resolution_clock::now(); - - // Single threaded combining all support areas to the right layers. - { - auto begin = linear_data.begin(); - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { - size_t cnt_roofs = 0; - size_t cnt_layers = 0; - auto end = begin; - for (; end != linear_data.end() && end->element->state.layer_idx == layer_idx; ++ end) - ++ (end->element->state.missing_roof_layers > end->element->state.distance_to_top ? cnt_roofs : cnt_layers); - auto &this_roofs = support_roof_storage[layer_idx]; - auto &this_layers = support_layer_storage[layer_idx]; - this_roofs.reserve(this_roofs.size() + cnt_roofs); - this_layers.reserve(this_layers.size() + cnt_layers); - for (auto it = begin; it != end; ++ it) - std::move(std::begin(it->polygons), std::end(it->polygons), std::back_inserter(it->element->state.missing_roof_layers > it->element->state.distance_to_top ? this_roofs : this_layers)); - begin = end; - } - } - - finalize_interface_and_support_areas(print_object, volumes, config, overhangs, support_layer_storage, support_roof_storage, - bottom_contacts, top_contacts, intermediate_layers, layer_storage, throw_on_cancel); - auto t_end = std::chrono::high_resolution_clock::now(); - - auto dur_gen_tips = 0.001 * std::chrono::duration_cast(t_generate - t_start).count(); - auto dur_smooth = 0.001 * std::chrono::duration_cast(t_smooth - t_generate).count(); - auto dur_drop = 0.001 * std::chrono::duration_cast(t_drop - t_smooth).count(); - auto dur_finalize = 0.001 * std::chrono::duration_cast(t_end - t_drop).count(); - - BOOST_LOG_TRIVIAL(info) << - "Time used for drawing subfuctions: generate_branch_areas: " << dur_gen_tips << " ms " - "smooth_branch_areas: " << dur_smooth << " ms " - "drop_non_gracious_areas: " << dur_drop << " ms " - "finalize_interface_and_support_areas " << dur_finalize << " ms"; -} - template void triangulate_fan(indexed_triangle_set &its, int ifan, int ibegin, int iend) { @@ -3513,11 +2917,11 @@ static std::pair discretize_circle(const Vec3f ¢er, const Vec3f &n // Returns Z span of the generated mesh. static std::pair extrude_branch( - const std::vector &path, - const TreeSupportSettings &config, - const SlicingParameters &slicing_params, - const std::vector &move_bounds, - indexed_triangle_set &result) + const std::vector&path, + const TreeSupportSettings &config, + const SlicingParameters &slicing_params, + const std::vector &move_bounds, + indexed_triangle_set &result) { Vec3d p1, p2, p3; Vec3d v1, v2; @@ -3526,10 +2930,6 @@ static std::pair extrude_branch( assert(path.size() >= 2); static constexpr const float eps = 0.015f; std::pair prev_strip; - -// char fname[2048]; -// static int irun = 0; - float zmin = 0; float zmax = 0; @@ -3675,7 +3075,7 @@ static void organic_smooth_branches_avoid_collisions( Vec3f position; // Previous position, for Laplacian smoothing. Vec3f prev_position; - // + // Vec3f last_collision; double last_collision_depth; // Minimum Z for which the sphere collision will be evaluated. @@ -3775,7 +3175,7 @@ static void organic_smooth_branches_avoid_collisions( // Collision detected to be removed. // Nudge the circle center away from the collision. if (collision_sphere.last_collision_depth > EPSILON) - // a little bit of hysteresis to detect end of + // a little bit of hysteresis to detect end of ++ num_moved; // Shift by maximum 2mm. double nudge_dist = std::min(std::max(0., collision_sphere.last_collision_depth + collision_extra_gap), max_nudge_collision_avoidance); @@ -3938,7 +3338,7 @@ static void organic_smooth_branches_avoid_collisions( * \param storage The data storage where the mesh data is gotten from and * where the resulting support areas are stored. */ -static void generate_support_areas(Print &print, const BuildVolume &build_volume, const std::vector &print_object_ids, std::function throw_on_cancel) +static void generate_support_areas(Print &print, TreeSupport* tree_support, const BuildVolume &build_volume, const std::vector &print_object_ids, std::function throw_on_cancel) { // Settings with the indexes of meshes that use these settings. std::vector>> grouped_meshes = group_meshes(print, print_object_ids); @@ -3980,26 +3380,63 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume // Generator for model collision, avoidance and internal guide volumes. TreeModelVolumes volumes{ print_object, build_volume, config.maximum_move_distance, config.maximum_move_distance_slow, processing.second.front(), #ifdef SLIC3R_TREESUPPORTS_PROGRESS - m_progress_multiplier, m_progress_offset, + m_progress_multiplier, m_progress_offset, #endif // SLIC3R_TREESUPPORTS_PROGRESS /* additional_excluded_areas */{} }; - //FIXME generating overhangs just for the furst mesh of the group. + //FIXME generating overhangs just for the first mesh of the group. assert(processing.second.size() == 1); - std::vector overhangs = generate_overhangs(config, *print.get_object(processing.second.front()), throw_on_cancel); +#if 1 + // use smart overhang detection + std::vector overhangs; + tree_support->detect_overhangs(); + const int num_raft_layers = int(config.raft_layers.size()); + const int num_layers = int(print_object.layer_count()) + num_raft_layers; + overhangs.resize(num_layers); + for (size_t i = 0; i < print_object.layer_count(); i++) { + for (ExPolygon& expoly : print_object.get_layer(i)->loverhangs) { + Polygons polys = to_polygons(expoly); + if (tree_support->overhang_types[&expoly] == TreeSupport::SharpTail) { polys = offset(polys, scale_(0.2)); + } + append(overhangs[i + num_raft_layers], polys); + } + } + // add vertical enforcer points + std::vector zs = zs_from_layers(print_object.layers()); + Polygon base_circle = make_circle(scale_(0.5), SUPPORT_TREE_CIRCLE_RESOLUTION); + for (auto &pt_and_normal :tree_support->m_vertical_enforcer_points) { + auto pt = pt_and_normal.first; + auto normal = pt_and_normal.second; // normal seems useless + auto iter = std::lower_bound(zs.begin(), zs.end(), pt.z()); + if (iter != zs.end()) { + size_t layer_nr = iter - zs.begin(); + if (layer_nr > 0 && layer_nr < print_object.layer_count()) { + Polygon circle = base_circle; + circle.translate(to_2d(pt).cast()); + overhangs[layer_nr + num_raft_layers].emplace_back(std::move(circle)); + } + } + } +#else + std::vector overhangs = generate_overhangs(config, *print.get_object(processing.second.front()), throw_on_cancel); +#endif // ### Precalculate avoidances, collision etc. size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes, throw_on_cancel); bool has_support = num_support_layers > 0; bool has_raft = config.raft_layers.size() > 0; num_support_layers = std::max(num_support_layers, config.raft_layers.size()); + if (num_support_layers == 0) + continue; + SupportParameters support_params(print_object); support_params.with_sheath = true; // Don't override the support density of tree supports, as the support density is used for raft. // The trees will have the density zeroed in tree_supports_generate_paths() // support_params.support_density = 0; + SupportGeneratorLayerStorage layer_storage; SupportGeneratorLayersPtr top_contacts; SupportGeneratorLayersPtr bottom_contacts; @@ -4043,7 +3480,7 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume move_bounds, interface_placer, throw_on_cancel); auto t_gen = std::chrono::high_resolution_clock::now(); - #ifdef TREESUPPORT_DEBUG_SVG +#ifdef TREESUPPORT_DEBUG_SVG for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++layer_idx) { Polygons polys; for (auto& area : move_bounds[layer_idx]) @@ -4057,6 +3494,7 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume #endif // TREESUPPORT_DEBUG_SVG // ### Propagate the influence areas downwards. This is an inherently serial operation. + print.set_status(60, _L("Generating support")); create_layer_pathing(volumes, config, move_bounds, throw_on_cancel); auto t_path = std::chrono::high_resolution_clock::now(); @@ -4065,18 +3503,13 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume auto t_place = std::chrono::high_resolution_clock::now(); // ### draw these points as circles + // this new function give correct result when raft is also enabled + organic_draw_branches( + *print.get_object(processing.second.front()), volumes, config, move_bounds, + bottom_contacts, top_contacts, interface_placer, intermediate_layers, layer_storage, + throw_on_cancel); - if (print_object.config().support_style.value != smsOrganic && - // Orca: use organic as default - print_object.config().support_style.value != smsDefault) { - draw_areas(*print.get_object(processing.second.front()), volumes, config, overhangs, move_bounds, - bottom_contacts, top_contacts, intermediate_layers, layer_storage, throw_on_cancel); - } else { - organic_draw_branches( - *print.get_object(processing.second.front()), volumes, config, move_bounds, - bottom_contacts, top_contacts, interface_placer, intermediate_layers, layer_storage, - throw_on_cancel); - } + //tree_support->move_bounds_to_contact_nodes(move_bounds, print_object, config); remove_undefined_layers(); @@ -4117,9 +3550,12 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume }); // Don't fill in the tree supports, make them hollow with just a single sheath line. + print.set_status(69, _L("Generating support")); generate_support_toolpaths(print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); - + + auto t_end = std::chrono::high_resolution_clock::now(); + BOOST_LOG_TRIVIAL(info) << "Total time of organic tree support: " << 0.001 * std::chrono::duration_cast(t_end - t_start).count() << " ms"; #if 0 //#ifdef SLIC3R_DEBUG { @@ -4586,7 +4022,7 @@ void generate_tree_support_3D(PrintObject &print_object, TreeSupport* tree_suppo std::transform(bedpts.begin(), bedpts.end(), std::back_inserter(bedptsf), [](const Point &p) { return unscale(p); }); BuildVolume build_volume{ bedptsf, tree_support->m_print_config->printable_height }; - TreeSupport3D::generate_support_areas(*print_object.print(), build_volume, { idx }, throw_on_cancel); + TreeSupport3D::generate_support_areas(*print_object.print(), tree_support, build_volume, { idx }, throw_on_cancel); } } // namespace Slic3r diff --git a/src/libslic3r/Support/TreeSupport3D.hpp b/src/libslic3r/Support/TreeSupport3D.hpp index 311df504b7e..2d50f73ac50 100644 --- a/src/libslic3r/Support/TreeSupport3D.hpp +++ b/src/libslic3r/Support/TreeSupport3D.hpp @@ -47,8 +47,6 @@ struct SlicingParameters; namespace TreeSupport3D { -// The number of vertices in each circle. -static constexpr const size_t SUPPORT_TREE_CIRCLE_RESOLUTION = 25; struct AreaIncreaseSettings { @@ -139,6 +137,10 @@ struct SupportElementStateBits { struct SupportElementState : public SupportElementStateBits { + int type = 0; + coordf_t radius = 0; + float print_z = 0; + /*! * \brief The layer this support elements wants reach */ @@ -306,7 +308,7 @@ void organic_draw_branches( SupportGeneratorLayersPtr &intermediate_layers, SupportGeneratorLayerStorage &layer_storage, - std::function throw_on_cancel); + std::function throw_on_cancel); } // namespace TreeSupport3D diff --git a/src/libslic3r/Support/TreeSupportCommon.hpp b/src/libslic3r/Support/TreeSupportCommon.hpp index 50ca626fa3f..31e410838dc 100644 --- a/src/libslic3r/Support/TreeSupportCommon.hpp +++ b/src/libslic3r/Support/TreeSupportCommon.hpp @@ -17,10 +17,10 @@ namespace Slic3r { - + // The number of vertices in each circle. + static constexpr const size_t SUPPORT_TREE_CIRCLE_RESOLUTION = 25; namespace TreeSupport3D { - using LayerIndex = int; enum class InterfacePreference @@ -40,18 +40,18 @@ struct TreeSupportMeshGroupSettings { const PrintObjectConfig &config = print_object.config(); const SlicingParameters &slicing_params = print_object.slicing_parameters(); // const std::vector printing_extruders = print_object.object_extruders(); - + // Support must be enabled and set to Tree style. - assert(config.enable_support || config.enforce_support_layers > 0); - assert(is_tree(config.support_type)); - + //assert(config.support_material); + //assert(config.support_material_style == smsTree || config.support_material_style == smsOrganic); + // Calculate maximum external perimeter width over all printing regions, taking into account the default layer height. coordf_t external_perimeter_width = 0.; for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) { const PrintRegion ®ion = print_object.printing_region(region_id); external_perimeter_width = std::max(external_perimeter_width, region.flow(print_object, frExternalPerimeter, config.layer_height).width()); } - + this->layer_height = scaled(config.layer_height.value); this->resolution = scaled(print_config.resolution.value); // Arache feature @@ -69,56 +69,28 @@ struct TreeSupportMeshGroupSettings { 0; this->support_material_buildplate_only = config.support_on_build_plate_only; this->support_xy_distance = scaled(config.support_object_xy_distance.value); + this->support_xy_distance_1st_layer = scaled(config.support_object_first_layer_gap.value); // Separation of interfaces, it is likely smaller than support_xy_distance. this->support_xy_distance_overhang = std::min(this->support_xy_distance, scaled(0.5 * external_perimeter_width)); this->support_top_distance = scaled(slicing_params.gap_support_object); this->support_bottom_distance = scaled(slicing_params.gap_object_support); - // this->support_interface_skip_height = - // this->support_infill_angles = this->support_roof_enable = config.support_interface_top_layers.value > 0; - this->support_roof_layers = this->support_roof_enable ? config.support_interface_top_layers.value : 0; - this->support_floor_enable = config.support_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value > 0; - this->support_floor_layers = this->support_floor_enable ? config.support_interface_bottom_layers.value : 0; - // this->minimum_roof_area = - // this->support_roof_angles = + this->support_roof_layers = config.support_interface_top_layers.value; + this->support_floor_enable = config.support_interface_bottom_layers.value > 0; + this->support_floor_layers = config.support_interface_bottom_layers.value; this->support_roof_pattern = config.support_interface_pattern; this->support_pattern = config.support_base_pattern; this->support_line_spacing = scaled(config.support_base_pattern_spacing.value); - // this->support_bottom_offset = - // this->support_wall_count = config.support_material_with_sheath ? 1 : 0; - this->support_wall_count = 1; + this->support_wall_count = std::max(1, config.tree_support_wall_count.value); // at least 1 wall for organic tree support this->support_roof_line_distance = scaled(config.support_interface_spacing.value) + this->support_roof_line_width; - // this->minimum_support_area = - // this->minimum_bottom_area = - // this->support_offset = this->support_tree_branch_distance = scaled(config.tree_support_branch_distance_organic.value); this->support_tree_angle = std::clamp(config.tree_support_branch_angle_organic * M_PI / 180., 0., 0.5 * M_PI - EPSILON); this->support_tree_angle_slow = std::clamp(config.tree_support_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON); this->support_tree_branch_diameter = scaled(config.tree_support_branch_diameter_organic.value); - this->support_tree_branch_diameter_angle = std::clamp(config.tree_support_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); + this->support_tree_branch_diameter_angle = std::clamp(config.tree_support_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); this->support_tree_top_rate = config.tree_support_top_rate.value; // percent // this->support_tree_tip_diameter = this->support_line_width; this->support_tree_tip_diameter = std::clamp(scaled(config.tree_support_tip_diameter.value), (coord_t)0, this->support_tree_branch_diameter); - - std::cout << "\n---------------\n" - << "layer_height: " << layer_height << "\nresolution: " << resolution << "\nmin_feature_size: " << min_feature_size - << "\nsupport_angle: " << support_angle << "\nconfig.support_threshold_angle: " << config.support_threshold_angle << "\nsupport_line_width: " << support_line_width - << "\nsupport_roof_line_width: " << support_roof_line_width << "\nsupport_bottom_enable: " << support_bottom_enable - << "\nsupport_bottom_height: " << support_bottom_height - << "\nsupport_material_buildplate_only: " << support_material_buildplate_only - << "\nsupport_xy_distance: " << support_xy_distance << "\nsupport_xy_distance_overhang: " << support_xy_distance_overhang - << "\nsupport_top_distance: " << support_top_distance << "\nsupport_bottom_distance: " << support_bottom_distance - << "\nsupport_roof_enable: " << support_roof_enable << "\nsupport_roof_layers: " << support_roof_layers - << "\nsupport_floor_enable: " << support_floor_enable << "\nsupport_floor_layers: " << support_floor_layers - << "\nsupport_roof_pattern: " << support_roof_pattern << "\nsupport_pattern: " << support_pattern - << "\nsupport_line_spacing: " << support_line_spacing << "\nsupport_wall_count: " << support_wall_count - << "\nsupport_roof_line_distance: " << support_roof_line_distance - << "\nsupport_tree_branch_distance: " << support_tree_branch_distance - << "\nsupport_tree_angle_slow: " << support_tree_angle_slow - << "\nsupport_tree_branch_diameter: " << support_tree_branch_diameter - << "\nsupport_tree_branch_diameter_angle: " << support_tree_branch_diameter_angle - << "\nsupport_tree_top_rate: " << support_tree_top_rate << "\nsupport_tree_tip_diameter: " << support_tree_tip_diameter - << "\n---------------\n"; } /*********************************************************************/ @@ -158,6 +130,7 @@ struct TreeSupportMeshGroupSettings { // Distance of the support structure from the print in the X/Y directions. // minimum: 0, maximum warning: 1.5 * machine_nozzle_tip_outer_diameter coord_t support_xy_distance { scaled(0.7) }; + coord_t support_xy_distance_1st_layer { scaled(0.7) }; // Minimum Support X/Y Distance // Distance of the support structure from the overhang in the X/Y directions. // minimum_value: 0, minimum warning": support_xy_distance - support_line_width * 2, maximum warning: support_xy_distance @@ -766,6 +739,17 @@ class InterfacePlacer { std::mutex m_mutex_layer_storage; }; +enum class LineStatus +{ + INVALID, + TO_MODEL, + TO_MODEL_GRACIOUS, + TO_MODEL_GRACIOUS_SAFE, + TO_BP, + TO_BP_SAFE +}; + + } // namespace TreeSupport3D } // namespace Slic3r diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 4e102887e32..c9a59e5b2ff 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -957,7 +957,6 @@ inline std::pair slice_slabs_make_lines( } slice_facet_with_slabs(vertices, indices, face_idx, neighbors, edge_ids, num_edges, zs, lines_top, lines_mutex_top); } - // BBS: add vertical faces option if (bottom && (fo == FaceOrientation::Down || fo == FaceOrientation::Degenerate)) { Vec3i32 neighbors = face_neighbors[face_idx]; // Reset neighborship of this triangle in case the other triangle is oriented backwards from this one. @@ -2063,6 +2062,7 @@ void slice_mesh_slabs( const Transform3d &trafo, std::vector *out_top, std::vector *out_bottom, + std::vector> *vertical_points, std::function throw_on_cancel) { BOOST_LOG_TRIVIAL(debug) << "slice_mesh_slabs to polygons"; @@ -2133,6 +2133,11 @@ void slice_mesh_slabs( // Is the triangle vertical or degenerate? assert(d == 0); fo = fa == fb || fa == fc || fb == fc ? FaceOrientation::Degenerate : FaceOrientation::Vertical; + if(vertical_points && fo==FaceOrientation::Vertical) + { + Vec3f normal = (fb - fa).cross(fc - fa).normalized(); + vertical_points->push_back({ (fa + fb + fc) / 3,normal }); + } } face_orientation[&tri - mesh.indices.data()] = fo; } @@ -2297,7 +2302,7 @@ void project_mesh( { std::vector top, bottom; std::vector zs { -1e10, 1e10 }; - slice_mesh_slabs(mesh, zs, trafo, out_top ? &top : nullptr, out_bottom ? &bottom : nullptr, throw_on_cancel); + slice_mesh_slabs(mesh, zs, trafo, out_top ? &top : nullptr, out_bottom ? &bottom : nullptr, nullptr, throw_on_cancel); if (out_top) *out_top = std::move(top.front()); if (out_bottom) @@ -2311,7 +2316,7 @@ Polygons project_mesh( { std::vector top, bottom; std::vector zs { -1e10, 1e10 }; - slice_mesh_slabs(mesh, zs, trafo, &top, &bottom, throw_on_cancel); + slice_mesh_slabs(mesh, zs, trafo, &top, &bottom, nullptr, throw_on_cancel); return union_(top.front(), bottom.back()); } diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp index ea6a7262cc7..1f7bba9d273 100644 --- a/src/libslic3r/TriangleMeshSlicer.hpp +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -107,6 +107,7 @@ void slice_mesh_slabs( const Transform3d &trafo, std::vector *out_top, std::vector *out_bottom, + std::vector> *vertical_points, std::function throw_on_cancel); // Project mesh upwards pointing surfaces / downwards pointing surfaces into 2D polygons. diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index b6900553eea..744eb983113 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "format.hpp" #include "Platform.hpp" @@ -298,12 +299,13 @@ static std::atomic debug_out_path_called(false); std::string debug_out_path(const char *name, ...) { - static constexpr const char *SLIC3R_DEBUG_OUT_PATH_PREFIX = "out/"; + //static constexpr const char *SLIC3R_DEBUG_OUT_PATH_PREFIX = "out/"; + auto svg_folder = boost::filesystem::path(g_data_dir) / "SVG/"; if (! debug_out_path_called.exchange(true)) { - std::string path = boost::filesystem::system_complete(SLIC3R_DEBUG_OUT_PATH_PREFIX).string(); - if (!boost::filesystem::exists(path)) { - boost::filesystem::create_directory(path); + if (!boost::filesystem::exists(svg_folder)) { + boost::filesystem::create_directory(svg_folder); } + std::string path = boost::filesystem::system_complete(svg_folder).string(); printf("Debugging output files will be written to %s\n", path.c_str()); } char buffer[2048]; @@ -311,7 +313,13 @@ std::string debug_out_path(const char *name, ...) va_start(args, name); std::vsprintf(buffer, name, args); va_end(args); - return std::string(SLIC3R_DEBUG_OUT_PATH_PREFIX) + std::string(buffer); + + std::string buf(buffer); + if (size_t pos = buf.find_first_of('/'); pos != std::string::npos) { + std::string sub_dir = buf.substr(0, pos); + std::filesystem::create_directory(svg_folder.string() + sub_dir); + } + return svg_folder.string() + std::string(buffer); } namespace logging = boost::log; diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index a376aa7f1fe..a10f1399c92 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -426,7 +426,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con auto support_type = config->opt_enum("support_type"); auto support_style = config->opt_enum("support_style"); std::set enum_set_normal = { smsDefault, smsGrid, smsSnug }; - std::set enum_set_tree = { smsDefault, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsOrganic }; + std::set enum_set_tree = { smsDefault, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsTreeOrganic }; auto & set = is_tree(support_type) ? enum_set_tree : enum_set_normal; if (set.find(support_style) == set.end()) { DynamicPrintConfig new_conf = *config; @@ -596,14 +596,14 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co "support_interface_pattern", "support_interface_top_layers", "support_interface_bottom_layers", "bridge_no_support", "max_bridge_length", "support_top_z_distance", "support_bottom_z_distance", "support_type", "support_on_build_plate_only", "support_critical_regions_only","support_interface_not_for_body", - "support_object_xy_distance"/*, "independent_support_layer_height"*/}) + "support_object_xy_distance","support_object_first_layer_gap"/*, "independent_support_layer_height"*/}) toggle_field(el, have_support_material); toggle_field("support_threshold_angle", have_support_material && is_auto(support_type)); toggle_field("support_threshold_overlap", config->opt_int("support_threshold_angle") == 0 && have_support_material && is_auto(support_type)); //toggle_field("support_closing_radius", have_support_material && support_style == smsSnug); bool support_is_tree = config->opt_bool("enable_support") && is_tree(support_type); - bool support_is_normal_tree = support_is_tree && support_style != smsOrganic && + bool support_is_normal_tree = support_is_tree && support_style != smsTreeOrganic && // Orca: use organic as default support_style != smsDefault; bool support_is_organic = support_is_tree && !support_is_normal_tree; @@ -611,10 +611,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter" }) toggle_line(el, support_is_normal_tree); // settings specific to normal trees - for (auto el : {"tree_support_wall_count", "tree_support_auto_brim", "tree_support_brim_width", "tree_support_adaptive_layer_height"}) + for (auto el : {"tree_support_auto_brim", "tree_support_brim_width", "tree_support_adaptive_layer_height"}) toggle_line(el, support_is_normal_tree); // settings specific to organic trees - for (auto el : {"tree_support_branch_angle_organic", "tree_support_branch_distance_organic", "tree_support_branch_diameter_organic","tree_support_angle_slow","tree_support_tip_diameter", "tree_support_top_rate", "tree_support_branch_diameter_angle", "tree_support_branch_diameter_double_wall"}) + for (auto el : {"tree_support_branch_angle_organic", "tree_support_branch_distance_organic", "tree_support_branch_diameter_organic","tree_support_angle_slow","tree_support_tip_diameter", "tree_support_top_rate", "tree_support_branch_diameter_angle"}) toggle_line(el, support_is_organic); toggle_field("tree_support_brim_width", support_is_tree && !config->opt_bool("tree_support_auto_brim")); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 287f72a0826..a01c509b15c 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2125,7 +2125,8 @@ bool GUI_App::OnInit() { try { return on_init_inner(); - } catch (const std::exception&) { + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(fatal) << "OnInit Got Fatal error: " << e.what(); generic_exception_handle(); return false; } diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 416c2699052..f3861c69aa2 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -90,10 +90,11 @@ std::map> SettingsFactory::OBJECT_C { L("Support"), {{"brim_type", "",1},{"brim_width", "",2},{"brim_object_gap", "",3}, {"enable_support", "",4},{"support_type", "",5},{"support_threshold_angle", "",6}, {"support_threshold_overlap", "",6}, {"support_on_build_plate_only", "",7}, {"support_filament", "",8},{"support_interface_filament", "",9},{"support_expansion", "",24},{"support_style", "",25}, - {"tree_support_brim_width", "",26}, {"tree_support_branch_angle", "",10},{"tree_support_branch_angle_organic","",10}, {"tree_support_wall_count", "",11},//tree support - {"support_top_z_distance", "",13},{"support_bottom_z_distance", "",12},{"support_base_pattern", "",14},{"support_base_pattern_spacing", "",15}, - {"support_interface_top_layers", "",16},{"support_interface_bottom_layers", "",17},{"support_interface_spacing", "",18},{"support_bottom_interface_spacing", "",19}, - {"support_object_xy_distance", "",20}, {"bridge_no_support", "",21},{"max_bridge_length", "",22},{"support_critical_regions_only", "",23},{"support_remove_small_overhang","",27} + {"tree_support_brim_width", "",26}, {"tree_support_branch_angle", "",10},{"tree_support_branch_angle_organic","",10}, {"tree_support_wall_count", "",11},{"tree_support_branch_diameter_angle", "",11},//tree support + {"support_top_z_distance", "",13},{"support_bottom_z_distance", "",12},{"support_base_pattern", "",14},{"support_base_pattern_spacing", "",15}, + {"support_interface_top_layers", "",16},{"support_interface_bottom_layers", "",17},{"support_interface_spacing", "",18},{"support_bottom_interface_spacing", "",19}, + {"support_object_xy_distance", "",20}, {"bridge_no_support", "",21},{"max_bridge_length", "",22},{"support_critical_regions_only", "",23},{"support_remove_small_overhang","",27}, + {"support_object_first_layer_gap","",28} }}, { L("Speed"), {{"support_speed", "",12}, {"support_interface_speed", "",13} }} @@ -155,6 +156,7 @@ std::vector SettingsFactory::get_visible_options(const std::s "tree_support_wall_count", //support "support_top_z_distance", "support_base_pattern", "support_base_pattern_spacing", "support_interface_top_layers", "support_interface_bottom_layers", "support_interface_spacing", "support_bottom_interface_spacing", "support_object_xy_distance", + "support_object_first_layer_gap", //adhesion "brim_type", "brim_width", "brim_object_gap", "raft_layers" };*/ diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index fea8908d755..9abcbfb2dea 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1496,50 +1496,13 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) m_config_manipulation.apply(m_config, &new_conf); } - // BBS popup a message to ask the user to set optimum parameters for tree support - if (opt_key == "support_type" || opt_key == "support_style") { - if (is_tree_slim(m_config->opt_enum("support_type"), m_config->opt_enum("support_style")) && - !(m_config->opt_float("support_top_z_distance") == 0 && m_config->opt_int("support_interface_top_layers") == 0 && m_config->opt_int("tree_support_wall_count") == 2)) { - wxString msg_text = _L("We have added an experimental style \"Tree Slim\" that features smaller support volume but weaker strength.\n" - "We recommend using it with: 0 interface layers, 0 top distance, 2 walls."); - msg_text += "\n\n" + _L("Change these settings automatically? \n" - "Yes - Change these settings automatically\n" - "No - Do not change these settings for me"); - MessageDialog dialog(wxGetApp().plater(), msg_text, "Suggestion", wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog.ShowModal() == wxID_YES) { - new_conf.set_key_value("support_top_z_distance", new ConfigOptionFloat(0)); - new_conf.set_key_value("support_interface_top_layers", new ConfigOptionInt(0)); - new_conf.set_key_value("tree_support_wall_count", new ConfigOptionInt(2)); - m_config_manipulation.apply(m_config, &new_conf); - } - wxGetApp().plater()->update(); - } else if ((m_config->opt_enum("support_type")==stTreeAuto && (m_config->opt_enum("support_style")==smsTreeStrong || m_config->opt_enum("support_style") == smsTreeHybrid)) && - !((m_config->opt_float("support_top_z_distance") >=0.1 || is_support_filament(m_config->opt_int("support_interface_filament") - 1)) - && m_config->opt_int("support_interface_top_layers") >1) ) { - wxString msg_text = _L("For \"Tree Strong\" and \"Tree Hybrid\" styles, we recommend the following settings: at least 2 interface layers, at least 0.1mm top z distance or using support materials on interface."); - msg_text += "\n\n" + _L("Change these settings automatically? \n" - "Yes - Change these settings automatically\n" - "No - Do not change these settings for me"); - MessageDialog dialog(wxGetApp().plater(), msg_text, "Suggestion", wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog.ShowModal() == wxID_YES) { - if (!is_support_filament(m_config->opt_int("support_interface_filament") - 1) && m_config->opt_float("support_top_z_distance") < 0.1) - new_conf.set_key_value("support_top_z_distance", new ConfigOptionFloat(0.2)); - new_conf.set_key_value("support_interface_top_layers", new ConfigOptionInt(2)); - m_config_manipulation.apply(m_config, &new_conf); - } - wxGetApp().plater()->update(); - } - } - // BBS popup a message to ask the user to set optimum parameters for support interface if support materials are used if (opt_key == "support_interface_filament") { int interface_filament_id = m_config->opt_int("support_interface_filament") - 1; // the displayed id is based from 1, while internal id is based from 0 if (is_support_filament(interface_filament_id) && !(m_config->opt_float("support_top_z_distance") == 0 && m_config->opt_float("support_interface_spacing") == 0 && - m_config->opt_enum("support_interface_pattern") == SupportMaterialInterfacePattern::smipConcentric)) { + m_config->opt_enum("support_interface_pattern") == SupportMaterialInterfacePattern::smipRectilinearInterlaced)) { wxString msg_text = _L("When using support material for the support interface, We recommend the following settings:\n" - "0 top z distance, 0 interface spacing, concentric pattern and disable independent support layer height"); + "0 top z distance, 0 interface spacing, interlaced rectilinear pattern and disable independent support layer height"); msg_text += "\n\n" + _L("Change these settings automatically? \n" "Yes - Change these settings automatically\n" "No - Do not change these settings for me"); @@ -1548,7 +1511,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (dialog.ShowModal() == wxID_YES) { new_conf.set_key_value("support_top_z_distance", new ConfigOptionFloat(0)); new_conf.set_key_value("support_interface_spacing", new ConfigOptionFloat(0)); - new_conf.set_key_value("support_interface_pattern", new ConfigOptionEnum(SupportMaterialInterfacePattern::smipConcentric)); + new_conf.set_key_value("support_interface_pattern", new ConfigOptionEnum(SupportMaterialInterfacePattern::smipRectilinearInterlaced)); new_conf.set_key_value("independent_support_layer_height", new ConfigOptionBool(false)); m_config_manipulation.apply(m_config, &new_conf); } @@ -2250,6 +2213,7 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); optgroup->append_single_option_line("support_top_z_distance", "support#top-z-distance"); optgroup->append_single_option_line("support_bottom_z_distance", "support#bottom-z-distance"); + optgroup->append_single_option_line("tree_support_wall_count"); optgroup->append_single_option_line("support_base_pattern", "support#base-pattern"); optgroup->append_single_option_line("support_base_pattern_spacing", "support#base-pattern"); optgroup->append_single_option_line("support_angle"); @@ -2262,8 +2226,9 @@ void TabPrint::build() //optgroup->append_single_option_line("support_interface_loop_pattern"); optgroup->append_single_option_line("support_object_xy_distance", "support"); + optgroup->append_single_option_line("support_object_first_layer_gap", "support"); optgroup->append_single_option_line("bridge_no_support", "support#base-pattern"); - optgroup->append_single_option_line("max_bridge_length", "support#base-pattern"); + optgroup->append_single_option_line("max_bridge_length", "support#tree-support-only-options"); optgroup->append_single_option_line("independent_support_layer_height", "support"); optgroup = page->new_optgroup(L("Tree supports"), L"param_support_tree"); @@ -2277,8 +2242,6 @@ void TabPrint::build() optgroup->append_single_option_line("tree_support_branch_angle", "support#tree-support-only-options"); optgroup->append_single_option_line("tree_support_branch_angle_organic", "support#tree-support-only-options"); optgroup->append_single_option_line("tree_support_angle_slow"); - optgroup->append_single_option_line("tree_support_branch_diameter_double_wall"); - optgroup->append_single_option_line("tree_support_wall_count"); optgroup->append_single_option_line("tree_support_adaptive_layer_height"); optgroup->append_single_option_line("tree_support_auto_brim"); optgroup->append_single_option_line("tree_support_brim_width"); @@ -2441,7 +2404,7 @@ void TabPrint::toggle_options() if (auto choice = dynamic_cast(field)) { auto def = print_config_def.get("support_style"); std::vector enum_set_normal = {smsDefault, smsGrid, smsSnug }; - std::vector enum_set_tree = { smsDefault, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsOrganic }; + std::vector enum_set_tree = { smsDefault, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsTreeOrganic }; auto & set = is_tree(support_type) ? enum_set_tree : enum_set_normal; auto & opt = const_cast(field->m_opt); auto cb = dynamic_cast(choice->window);