From 9ce81d6d12e312d48f6a27573b6bb6990af0ee0b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 10 Mar 2023 09:42:06 +0100 Subject: [PATCH] Organic Supports improvements: Added support_tree_branch_distance parameter to UI Fixed error in calculation of placeable areas, which made some trees to cut through an object. Locked the tree tips against smoothing of their centerline path. Reduced density of tips with zero interface layers (see continuous_tips). Reduced default support_tree_top_rate to 15% Refactored placement of interfaces for readability. --- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 14 +- src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 1 + src/libslic3r/TreeModelVolumes.cpp | 98 +-- src/libslic3r/TreeModelVolumes.hpp | 27 + src/libslic3r/TreeSupport.cpp | 988 +++++++++++++++----------- src/libslic3r/TreeSupport.hpp | 6 +- src/slic3r/GUI/ConfigManipulation.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 1 + 10 files changed, 666 insertions(+), 474 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 0e760f3d3..09514200f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -452,7 +452,7 @@ static std::vector s_Preset_print_options { "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", "support_material_bottom_contact_distance", "support_material_buildplate_only", - "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_tip_diameter", + "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius", "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder", "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fb6a641cb..dbdb36c7d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2910,6 +2910,18 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(5)); + // Tree Support Branch Distance + // How far apart the branches need to be when they touch the model. Making this distance small will cause + // the tree support to touch the model at more points, causing better overhang but making support harder to remove. + def = this->add("support_tree_branch_distance", coFloat); + def->label = L("Branch Distance"); + def->category = L("Support material"); + def->tooltip = L("How far apart the branches need to be when they touch the model. " + "Making this distance small will cause the tree support to touch the model at more points, " + "causing better overhang but making support harder to remove."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.)); + def = this->add("support_tree_top_rate", coPercent); def->label = L("Branch Density"); def->category = L("Support material"); @@ -2921,7 +2933,7 @@ void PrintConfigDef::init_fff_params() def->min = 5; def->max_literal = 35; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercent(30)); + def->set_default_value(new ConfigOptionPercent(15)); def = this->add("temperature", coInts); def->label = L("Other layers"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index a2ccb546b..e5a9f4c41 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -555,6 +555,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, support_tree_branch_diameter)) ((ConfigOptionFloat, support_tree_branch_diameter_angle)) ((ConfigOptionPercent, support_tree_top_rate)) + ((ConfigOptionFloat, support_tree_branch_distance)) ((ConfigOptionFloat, support_tree_tip_diameter)) // The rest ((ConfigOptionBool, thick_bridges)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index bee233e59..bfeb827da 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -693,6 +693,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_tree_branch_diameter" || opt_key == "support_tree_branch_diameter_angle" || opt_key == "support_tree_top_rate" + || opt_key == "support_tree_branch_distance" || opt_key == "support_tree_tip_diameter" || opt_key == "raft_expansion" || opt_key == "raft_first_layer_density" diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 33758f3f0..399685e9c 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -89,7 +89,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr // this->minimum_support_area = // this->minimum_bottom_area = // this->support_offset = -// this->support_tree_branch_distance = 2.5 * line_width ?? + this->support_tree_branch_distance = scaled(config.support_tree_branch_distance.value); this->support_tree_angle = std::clamp(config.support_tree_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); this->support_tree_angle_slow = std::clamp(config.support_tree_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON); this->support_tree_branch_diameter = scaled(config.support_tree_branch_diameter.value); @@ -329,6 +329,7 @@ void TreeModelVolumes::precalculate(const PrintObject& print_object, const coord for (int k = int(j - 1); k >= int(i); -- k) { std::string legend = format("radius-%1%", unscaled(sorted[k].first.first)); expolygons_with_attributes.push_back({ union_ex(sorted[k].second), SVG::ExPolygonAttributes(legend, std::string(colors[(k - int(i)) % num_colors]), 1.) }); + SVG::export_expolygons(debug_out_path("treesupport_cache-%s-%d-%s.svg", name.data(), sorted[i].first.second, legend.c_str()), { expolygons_with_attributes.back() }); } // Render the range of per radius collision polygons into a common SVG. SVG::export_expolygons(debug_out_path("treesupport_cache-%s-%d.svg", name.data(), sorted[i].first.second), expolygons_with_attributes); @@ -416,9 +417,6 @@ const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerI const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, LayerIndex layer_idx, std::function throw_on_cancel) const { - if (orig_radius == 0) - return this->getCollision(0, layer_idx, true); - const coord_t radius = ceilRadius(orig_radius); if (std::optional> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); result) return (*result).get(); @@ -426,7 +424,11 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; tree_supports_show_error("Not precalculated Placeable areas requested."sv, false); } - const_cast(this)->calculatePlaceables(radius, layer_idx, throw_on_cancel); + if (orig_radius == 0) + // Placable areas for radius 0 are calculated in the general collision code. + return this->getCollision(0, layer_idx, true); + else + const_cast(this)->calculatePlaceables(radius, layer_idx, throw_on_cancel); return getPlaceableAreas(orig_radius, layer_idx, throw_on_cancel); } @@ -470,6 +472,7 @@ void TreeModelVolumes::calculateCollision(const std::vector &ke }); } +// Calculate collisions and placable areas for radius and for layer 0 to max_layer_idx inclusive. void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex max_layer_idx, std::function throw_on_cancel) { // assert(radius == this->ceilRadius(radius)); @@ -480,12 +483,14 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex std::sort(layer_outline_indices.begin(), layer_outline_indices.end(), [this](size_t i, size_t j) { return m_layer_outlines[i].second.size() < m_layer_outlines[j].second.size(); }); - const LayerIndex min_layer_last = m_collision_cache.getMaxCalculatedLayer(radius); - std::vector data(max_layer_idx + 1 - min_layer_last, Polygons{}); + // Layer range for which the collisions will be calculated. + LayerPolygonCache data; + data.allocate(m_collision_cache.getMaxCalculatedLayer(radius) + 1, max_layer_idx + 1); + const bool calculate_placable = m_support_rests_on_model && radius == 0; - std::vector data_placeable; + LayerPolygonCache data_placeable; if (calculate_placable) - data_placeable = std::vector(max_layer_idx + 1 - min_layer_last, Polygons{}); + data_placeable.allocate(data.idx_begin, data.idx_end); for (size_t outline_idx : layer_outline_indices) if (const std::vector &outlines = m_layer_outlines[outline_idx].second; ! outlines.empty()) { @@ -493,8 +498,6 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex const coord_t layer_height = settings.layer_height; const int z_distance_bottom_layers = int(round(double(settings.support_bottom_distance) / double(layer_height))); const int z_distance_top_layers = int(round(double(settings.support_top_distance) / double(layer_height))); - const LayerIndex max_required_layer = std::min(outlines.size(), max_layer_idx + std::max(coord_t(1), z_distance_top_layers)); - const LayerIndex min_layer_bottom = std::max(0, min_layer_last - int(z_distance_bottom_layers)); const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : // technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all // not currently processing meshes as this delta will be added at request time. @@ -505,35 +508,40 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex settings.support_xy_distance; // 1) Calculate offsets of collision areas in parallel. - std::vector collision_areas_offsetted(max_required_layer + 1 - min_layer_bottom); - tbb::parallel_for(tbb::blocked_range(min_layer_bottom, max_required_layer + 1), - [&outlines, &machine_border = std::as_const(m_machine_border), offset_value = radius + xy_distance, min_layer_bottom, &collision_areas_offsetted, &throw_on_cancel] + LayerPolygonCache collision_areas_offsetted; + collision_areas_offsetted.allocate( + std::max(0, data.idx_begin - z_distance_bottom_layers), + std::min(outlines.size(), data.idx_end + z_distance_top_layers)); + tbb::parallel_for(tbb::blocked_range(collision_areas_offsetted.idx_begin, collision_areas_offsetted.idx_end), + [&outlines, &machine_border = std::as_const(m_machine_border), offset_value = radius + xy_distance, &collision_areas_offsetted, &throw_on_cancel] (const tbb::blocked_range &range) { for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) { Polygons collision_areas = machine_border; append(collision_areas, outlines[layer_idx]); // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. // if a key does not exist when it is accessed it is added! - collision_areas_offsetted[layer_idx - min_layer_bottom] = offset_value == 0 ? union_(collision_areas) : offset(union_ex(collision_areas), offset_value, ClipperLib::jtMiter, 1.2); + collision_areas_offsetted[layer_idx] = offset_value == 0 ? + union_(collision_areas) : + offset(union_ex(collision_areas), offset_value, ClipperLib::jtMiter, 1.2); throw_on_cancel(); } }); // 2) Sum over top / bottom ranges. - const bool last = outline_idx == layer_outline_indices.size(); - tbb::parallel_for(tbb::blocked_range(min_layer_last + 1, max_layer_idx + 1), - [&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, min_layer_bottom, radius, - xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, min_layer_last, last, &throw_on_cancel] - (const tbb::blocked_range& range) { - for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++layer_idx) { + const bool processing_last_mesh = outline_idx == layer_outline_indices.size(); + tbb::parallel_for(tbb::blocked_range(data.idx_begin, data.idx_end), + [&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius, + xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, processing_last_mesh, &throw_on_cancel] + (const tbb::blocked_range& range) { + for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) { Polygons collisions; - for (int i = -z_distance_bottom_layers; i <= z_distance_top_layers; ++ i) { - int j = layer_idx + i - min_layer_bottom; - if (j >= 0 && j < int(collision_areas_offsetted.size()) && i <= 0) + for (int i = - z_distance_bottom_layers; i <= 0; ++ i) + if (int j = layer_idx + i; collision_areas_offsetted.has(j)) append(collisions, collision_areas_offsetted[j]); - else if (j >= 0 && layer_idx + i < int(outlines.size()) && i > 0) { + for (int i = 1; i <= z_distance_top_layers; ++ i) + if (int j = layer_idx + i; j < int(outlines.size())) { Polygons collision_areas_original = machine_border; - append(collision_areas_original, outlines[layer_idx + i]); + append(collision_areas_original, outlines[j]); // If just the collision (including the xy distance) of the layers above is accumulated, it leads to the // following issue: @@ -566,37 +574,37 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex // the conditional -0.5 ensures that plastic can never touch on the diagonal // downward when the z_distance_top_layers = 1. It is assumed to be better to // not support an overhang<90 degree than to risk fusing to it. - - collision_areas_original = offset(union_ex(collision_areas_original), radius + required_range_x, ClipperLib::jtMiter, 1.2); - append(collisions, collision_areas_original); + append(collisions, offset(union_ex(collision_areas_original), radius + required_range_x, ClipperLib::jtMiter, 1.2)); } - } - collisions = last && layer_idx < int(anti_overhang.size()) ? union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(collisions); - auto &dst = data[layer_idx - (min_layer_last + 1)]; - if (last) { + collisions = processing_last_mesh && layer_idx < int(anti_overhang.size()) ? + union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : + union_(collisions); + auto &dst = data[layer_idx]; + if (processing_last_mesh) { if (! dst.empty()) collisions = union_(collisions, dst); dst = polygons_simplify(collisions, min_resolution); } else - append(dst, collisions); + append(dst, std::move(collisions)); throw_on_cancel(); } }); // 3) Optionally calculate placables. if (calculate_placable) { - // Calculating both the collision areas and placable areas. - tbb::parallel_for(tbb::blocked_range(std::max(min_layer_last + 1, z_distance_bottom_layers + 1), max_layer_idx + 1), - [&collision_areas_offsetted, &anti_overhang = m_anti_overhang, min_layer_bottom, z_distance_bottom_layers, last, min_resolution = m_min_resolution, &data_placeable, min_layer_last, &throw_on_cancel] + // Now calculate the placable areas. + tbb::parallel_for(tbb::blocked_range(std::max(data.idx_begin, 1), data.idx_end), + [&collision_areas_offsetted, &anti_overhang = m_anti_overhang, processing_last_mesh, + min_resolution = m_min_resolution, &data_placeable, &throw_on_cancel] (const tbb::blocked_range& range) { for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) { - LayerIndex layer_idx_below = layer_idx - (z_distance_bottom_layers + 1) - min_layer_bottom; + LayerIndex layer_idx_below = layer_idx - 1; assert(layer_idx_below >= 0); - auto ¤t = collision_areas_offsetted[layer_idx - min_layer_bottom]; - auto &below = collision_areas_offsetted[layer_idx_below]; - auto placable = diff(below, layer_idx < int(anti_overhang.size()) ? union_(current, anti_overhang[layer_idx - (z_distance_bottom_layers + 1)]) : current); - auto &dst = data_placeable[layer_idx - (min_layer_last + 1)]; - if (last) { + const Polygons ¤t = collision_areas_offsetted[layer_idx]; + const Polygons &below = collision_areas_offsetted[layer_idx_below]; + Polygons placable = diff(below, layer_idx_below < int(anti_overhang.size()) ? union_(current, anti_overhang[layer_idx_below]) : current); + auto &dst = data_placeable[layer_idx]; + if (processing_last_mesh) { if (! dst.empty()) placable = union_(placable, dst); dst = polygons_simplify(placable, min_resolution); @@ -619,9 +627,9 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex } #endif throw_on_cancel(); - m_collision_cache.insert(std::move(data), min_layer_last + 1, radius); + m_collision_cache.insert(std::move(data), radius); if (calculate_placable) - m_placeable_areas_cache.insert(std::move(data_placeable), min_layer_last + 1, radius); + m_placeable_areas_cache.insert(std::move(data_placeable), radius); } void TreeModelVolumes::calculateCollisionHolefree(const std::vector &keys, std::function throw_on_cancel) diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index d2af13c34..f5b5669bb 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -328,6 +328,26 @@ public: } private: + // Caching polygons for a range of layers. + struct LayerPolygonCache { + std::vector polygons; + LayerIndex idx_begin; + LayerIndex idx_end; + + void allocate(LayerIndex aidx_begin, LayerIndex aidx_end) { + this->idx_begin = aidx_begin; + this->idx_end = aidx_end; + this->polygons.assign(aidx_end - aidx_begin, {}); + } + + LayerIndex begin() const { return idx_begin; } + LayerIndex end() const { return idx_end; } + size_t size() const { return polygons.size(); } + + bool has(LayerIndex idx) const { return idx >= idx_begin && idx < idx_end; } + Polygons& operator[](LayerIndex idx) { return polygons[idx + idx_begin]; } + }; + /*! * \brief Convenience typedef for the keys to the caches */ @@ -363,6 +383,13 @@ private: for (auto &d : in) m_data[first_layer_idx ++].emplace(radius, std::move(d)); } + void insert(LayerPolygonCache &&in, coord_t radius) { + std::lock_guard guard(m_mutex); + LayerIndex i = in.idx_begin; + allocate_layers(i + LayerIndex(in.size())); + for (auto &d : in.polygons) + m_data[i ++].emplace(radius, std::move(d)); + } /*! * \brief Checks a cache for a given RadiusLayerPair and returns it if it is found * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 2906f6b93..2ce404161 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -744,7 +744,9 @@ static std::optional> polyline_sample_next_point_at_dis * \return A Polygons object that represents the resulting infill lines. */ [[nodiscard]] static Polylines generate_support_infill_lines( - const Polygons &polygon, const SupportParameters &support_params, + // Polygon to fill in with a zig-zag pattern supporting an overhang. + const Polygons &polygon, + const SupportParameters &support_params, bool roof, LayerIndex layer_idx, coord_t support_infill_distance) { #if 0 @@ -783,6 +785,9 @@ static std::optional> polyline_sample_next_point_at_dis append(lines, to_polylines(polygons)); return lines; #else +// const bool connected_zigzags = roof ? false : config.connect_zigzags; +// const int support_shift = roof ? 0 : support_infill_distance / 2; + const Flow &flow = roof ? support_params.support_material_interface_flow : support_params.support_material_flow; std::unique_ptr filler = std::unique_ptr(Fill::new_from_type(roof ? support_params.interface_fill_pattern : support_params.base_fill_pattern)); FillParams fill_params; @@ -1018,431 +1023,13 @@ int generate_raft_contact( } using SupportElements = std::deque; -/*! - * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. - * - * Generates Points where the Model should be supported and creates the areas where these points have to be placed. - * - * \param mesh[in] The mesh that is currently processed. - * \param move_bounds[out] Storage for the influence areas. - * \param storage[in] Background storage, required for adding roofs. - */ -static void generate_initial_areas( + +void finalize_raft_contact( const PrintObject &print_object, - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const std::vector &overhangs, - std::vector &move_bounds, + const int raft_contact_layer_idx, SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &top_interface_layers, - SupportGeneratorLayerStorage &layer_storage, - std::function throw_on_cancel) + std::vector &move_bounds) { - using AvoidanceType = TreeModelVolumes::AvoidanceType; - static constexpr const auto base_radius = scaled(0.01); - const Polygon base_circle = make_circle(base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION); - TreeSupportMeshGroupSettings mesh_group_settings(print_object); - TreeSupportSettings mesh_config{ mesh_group_settings, print_object.slicing_parameters() }; - SupportParameters support_params(print_object); - support_params.with_sheath = true; - support_params.support_density = 0; - - const size_t z_distance_delta = mesh_config.z_distance_top_layers + 1; // To ensure z_distance_top_layers are left empty between the overhang (zeroth empty layer), the support has to be added z_distance_top_layers+1 layers below - - const bool min_xy_dist = mesh_config.xy_distance > mesh_config.xy_min_distance; - -#if 0 - if (mesh.overhang_areas.size() <= z_distance_delta) - return; -#endif - - const coord_t connect_length = (mesh_config.support_line_width * 100. / mesh_group_settings.support_tree_top_rate) + std::max(2. * mesh_config.min_radius - 1.0 * mesh_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. - // 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 = mesh_config.min_radius < mesh_config.support_line_width ? - mesh_config.min_radius / 2 : - sqrt(sqr(mesh_config.min_radius) - sqr(mesh_config.min_radius - mesh_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. - //FIXME There is no account for the support overhang angle. - //FIXME There is no account for the width of the collision regions. - const coord_t extra_outset = std::max(coord_t(0), mesh_config.min_radius - mesh_config.support_line_width / 2) + (min_xy_dist ? mesh_config.support_line_width / 2 : 0) - //FIXME this is a heuristic value for support enforcers to work. -// + 10 * mesh_config.support_line_width; - ; - const size_t support_roof_layers = mesh_group_settings.support_roof_enable ? (mesh_group_settings.support_roof_height + mesh_config.layer_height / 2) / mesh_config.layer_height : 0; - const bool roof_enabled = support_roof_layers != 0; - const bool force_tip_to_roof = sqr(mesh_config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area && roof_enabled; - //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. - const coord_t max_overhang_speed = mesh_group_settings.support_angle < 0.5 * M_PI ? coord_t(tan(mesh_group_settings.support_angle) * mesh_config.layer_height) : std::numeric_limits::max(); - // 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. - const coord_t max_overhang_insert_lag = mesh_config.z_distance_top_layers > 0 ? - std::max(round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers) : - 0; - - const size_t num_raft_layers = config.raft_layers.size(); - const size_t num_support_layers = size_t(std::max(0, int(print_object.layer_count()) + int(num_raft_layers) - int(z_distance_delta))); - const size_t first_support_layer = std::max(int(num_raft_layers) - int(z_distance_delta), 1); - const int raft_contact_layer_idx = generate_raft_contact(print_object, config, top_contacts, layer_storage); - - std::mutex mutex_layer_storage, mutex_movebounds; - std::vector> already_inserted(num_support_layers); - tbb::parallel_for(tbb::blocked_range(first_support_layer, num_support_layers), - [&print_object, &volumes, &config, &overhangs, &mesh_config, &mesh_group_settings, &support_params, - z_distance_delta, min_xy_dist, force_tip_to_roof, roof_enabled, support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, - &base_circle, &mutex_layer_storage, &mutex_movebounds, &top_contacts, &layer_storage, &already_inserted, - &move_bounds, &throw_on_cancel](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - if (overhangs[layer_idx + z_distance_delta].empty()) - continue; - // take the least restrictive avoidance possible - Polygons relevant_forbidden; - { - const Polygons &relevant_forbidden_raw = mesh_config.support_rests_on_model ? - volumes.getCollision(mesh_config.getRadius(0), layer_idx, min_xy_dist) : - volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, false, min_xy_dist); - // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. - relevant_forbidden = offset(union_ex(relevant_forbidden_raw), scaled(0.005), jtMiter, 1.2); - } - - auto generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) -> Polylines { - const coord_t support_infill_distance = roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance; - return generate_support_infill_lines(area, support_params, roof, layer_idx, support_infill_distance); - }; - - // roof_tip_layers = force_tip_to_roof ? support_roof_layers - dtt_roof : 0 - // insert_layer_idx = layer_idx - dtt_roof - // supports_roof = dtt_roof > 0 - // dont_move_until = roof_enabled ? support_roof_layers - dtt_roof : 0 - auto addLinesAsInfluenceAreas = [&](LineInformations lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, bool supports_roof, size_t dont_move_until) - { - auto addPointAsInfluenceArea = [&](std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) - { - bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; - bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - if (!mesh_config.support_rests_on_model && !to_bp) { - BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; - tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly."sv, true); - return; - } - Polygons circle{ base_circle }; - circle.front().translate(p.first); - { - std::lock_guard critical_section_movebounds(mutex_movebounds); - Point hash_pos = p.first / ((mesh_config.min_radius + 1) / 10); - if (! already_inserted[insert_layer].count(hash_pos)) { - // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing - already_inserted[insert_layer].emplace(hash_pos); - SupportElementState state; - state.target_height = insert_layer; - state.target_position = p.first; - state.next_position = p.first; - state.layer_idx = insert_layer; - state.effective_radius_height = dtt; - state.to_buildplate = to_bp; - state.distance_to_top = dtt; - state.result_on_layer = p.first; - assert(state.result_on_layer_is_set()); - state.increased_to_model_radius = 0; - state.to_model_gracious = gracious; - state.elephant_foot_increases = 0; - state.use_min_xy_dist = min_xy_dist; - state.supports_roof = roof; - state.dont_move_until = dont_move_until; - state.can_use_safe_radius = safe_radius; - state.missing_roof_layers = force_tip_to_roof ? dont_move_until : 0; - state.skip_ovalisation = skip_ovalisation; - move_bounds[insert_layer].emplace_back(state, std::move(circle)); - } - } - }; - - validate_range(lines); - // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible - size_t dtt_roof_tip; - for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; dtt_roof_tip++) - { - auto evaluateRoofWillGenerate = [&](std::pair p) { - //FIXME Vojtech: The circle is just shifted, it has a known size, the infill should fit all the time! -#if 0 - Polygon roof_circle; - for (Point corner : base_circle) - roof_circle.points.emplace_back(p.first + corner * mesh_config.min_radius); - return !generate_support_infill_lines({ roof_circle }, mesh_config, true, insert_layer_idx - dtt_roof_tip, mesh_config.support_roof_line_distance).empty(); -#else - return true; -#endif - }; - - { - std::pair split = - // keep all lines that are still valid on the next layer - split_lines(lines, [&volumes, &config, insert_layer_idx, dtt_roof_tip](const std::pair &p) - { return evaluate_point_for_next_layer_function(volumes, config, insert_layer_idx - dtt_roof_tip, p); }); - LineInformations points = std::move(split.second); - // Not all roofs are guaranteed to actually generate lines, so filter these out and add them as points. - split = split_lines(split.first, evaluateRoofWillGenerate); - lines = std::move(split.first); - append(points, split.second); - // add all points that would not be valid - for (const LineInformation &line : points) - for (const std::pair &point_data : line) - addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); - } - - // add all tips as roof to the roof storage - Polygons added_roofs; - for (const LineInformation &line : lines) - //FIXME sweep the tip radius along the line? - for (const std::pair &p : line) { - Polygon roof_circle{ base_circle }; - roof_circle.scale(mesh_config.min_radius / base_radius); - roof_circle.translate(p.first); - added_roofs.emplace_back(std::move(roof_circle)); - } - if (! added_roofs.empty()) { - added_roofs = union_(added_roofs); - { - std::lock_guard lock(mutex_layer_storage); - SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip]; - if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, insert_layer_idx - dtt_roof_tip); - append(l->polygons, std::move(added_roofs)); - } - } - } - - for (LineInformation line : lines) { - bool disable_ovalistation = mesh_config.min_radius < 3 * mesh_config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > 5; // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width - for (auto point_data : line) - addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, dtt_roof_tip != 0 || supports_roof, disable_ovalistation); - } - }; - - // 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; - { - const Polygons &overhang_raw = overhangs[layer_idx + z_distance_delta]; - // 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, mesh_config.min_radius * 1.75 + mesh_config.xy_min_distance, 0, 1); - //check_self_intersections(overhang_regular, "overhang_regular1"); - - // offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang - Polygons remaining_overhang = intersection( - diff(mesh_group_settings.support_offset == 0 ? - overhang_raw : - offset(union_ex(overhang_raw), mesh_group_settings.support_offset, jtMiter, 1.2), - offset(union_ex(overhang_regular), mesh_config.support_line_width * 0.5, jtMiter, 1.2)), - relevant_forbidden); - - // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. - //+mesh_config.support_line_width / 80 to avoid calculating very small (useless) offsets because of rounding errors. - //FIXME likely a better approach would be to find correspondences between the full overhang and the trimmed overhang - // and if there is no correspondence, project the missing points to the clipping curve. - for (coord_t extra_total_offset_acc = 0; ! remaining_overhang.empty() && extra_total_offset_acc + mesh_config.support_line_width / 8 < extra_outset; ) { - const coord_t offset_current_step = std::min( - extra_total_offset_acc + 2 * mesh_config.support_line_width > mesh_config.min_radius ? - mesh_config.support_line_width / 8 : - circle_length_to_half_linewidth_change, - extra_outset - extra_total_offset_acc); - extra_total_offset_acc += offset_current_step; - const Polygons &raw_collision = volumes.getCollision(0, layer_idx, true); - const coord_t offset_step = mesh_config.xy_min_distance + mesh_config.support_line_width; - // Reducing the remaining overhang by the areas already supported. - //FIXME 1.5 * extra_total_offset_acc seems to be too much, it may remove some remaining overhang without being supported at all. - remaining_overhang = diff(remaining_overhang, safe_offset_inc(overhang_regular, 1.5 * extra_total_offset_acc, raw_collision, offset_step, 0, 1)); - // Extending the overhangs by the inflated remaining overhangs. - overhang_regular = union_(overhang_regular, diff(safe_offset_inc(remaining_overhang, extra_total_offset_acc, raw_collision, offset_step, 0, 1), relevant_forbidden)); - //check_self_intersections(overhang_regular, "overhang_regular2"); - } - // If the xy distance overrides the z distance, some support needs to be inserted further down. - //=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the the setting description, it would not produce reasonable results. ) - if (! min_xy_dist) { - 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 - // the area that is valid a layer below is to small for support roof. - Polylines polylines = ensure_maximum_distance_polyline(generateLines(remaining_overhang, false, layer_idx), mesh_config.min_radius, 1); - if (polylines.size() <= 3) - // add the outer wall to ensure it is correct supported instead - polylines = ensure_maximum_distance_polyline(to_polylines(remaining_overhang), connect_length, 3); - for (const auto &line : polylines) { - LineInformation res_line; - for (Point p : line) - res_line.emplace_back(p, LineStatus::INVALID); - overhang_lines.emplace_back(res_line); - } - validate_range(overhang_lines); - } - 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 = mesh_config.support_rests_on_model ? - volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, min_xy_dist) : - volumes.getAvoidance(mesh_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. - auto evaluatePoint = [&](std::pair p) { return contains(relevant_forbidden_below, p.first); }; - - std::pair split = split_lines(overhang_lines, evaluatePoint); // keep all lines that are invalid - overhang_lines = split.first; - // Set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. - LineInformations fresh_valid_points = convert_lines_to_internal(volumes, config, convert_internal_to_lines(split.second), layer_idx - lag_ctr); - validate_range(fresh_valid_points); - - addLinesAsInfluenceAreas(fresh_valid_points, (force_tip_to_roof && lag_ctr <= support_roof_layers) ? support_roof_layers : 0, layer_idx - lag_ctr, false, roof_enabled ? support_roof_layers : 0); - } - } - } - - throw_on_cancel(); - - Polygons overhang_roofs; - std::vector> overhang_processing; - if (roof_enabled) { - static constexpr const coord_t support_roof_offset = 0; - overhang_roofs = safe_offset_inc(overhangs[layer_idx + z_distance_delta], support_roof_offset, relevant_forbidden, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, 0, 1); - if (mesh_group_settings.minimum_support_area > 0) - remove_small(overhang_roofs, mesh_group_settings.minimum_roof_area); - overhang_regular = diff(overhang_regular, overhang_roofs, ApplySafetyOffset::Yes); - //check_self_intersections(overhang_regular, "overhang_regular3"); - for (ExPolygon &roof_part : union_ex(overhang_roofs)) - overhang_processing.emplace_back(std::move(roof_part), true); - } - 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)) - overhang_processing.emplace_back(std::move(support_part), false); - - for (const std::pair &overhang_pair : overhang_processing) { - const bool roof_allowed_for_this_part = overhang_pair.second; - Polygons overhang_outset = to_polygons(overhang_pair.first); - const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), coord_t(total_length(overhang_outset) / connect_length))); - LineInformations overhang_lines; - Polygons last_overhang = overhang_outset; - size_t dtt_roof = 0; - // Sometimes roofs could be empty as the pattern does not generate lines if the area is narrow enough (i am looking at you, concentric infill). - // To catch these cases the added roofs are saved to be evaluated later. - std::vector added_roofs(support_roof_layers); - - // 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 - // 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 - if (roof_allowed_for_this_part) { - for (dtt_roof = 0; dtt_roof < support_roof_layers && layer_idx - dtt_roof >= 1; dtt_roof++) { - // here the roof is handled. If roof can not be added the branches will try to not move instead - Polygons forbidden_next; - { - const Polygons &forbidden_next_raw = mesh_config.support_rests_on_model ? - volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), min_xy_dist) : - volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::Fast, false, min_xy_dist); - // prevent rounding errors down the line - //FIXME maybe use SafetyOffset::Yes at the following diff() instead? - forbidden_next = offset(union_ex(forbidden_next_raw), scaled(0.005), jtMiter, 1.2); - } - Polygons overhang_outset_next = diff(overhang_outset, forbidden_next); - if (area(overhang_outset_next) < mesh_group_settings.minimum_roof_area) { - // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter - if (dtt_roof != 0) { - size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; - // Produce support head points supporting an interface layer: First produce the interface lines, then sample them. - overhang_lines = convert_lines_to_internal(volumes, config, - ensure_maximum_distance_polyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); - overhang_lines = split_lines(overhang_lines, - [&volumes, &config, layer_idx, dtt_before](const std::pair &p) - { return evaluate_point_for_next_layer_function(volumes, config, layer_idx - dtt_before, p); }) - .first; - } - break; - } - added_roofs[dtt_roof] = overhang_outset; - last_overhang = overhang_outset; - overhang_outset = overhang_outset_next; - } - } - - size_t layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; // 1 inside max and -1 outside to avoid underflow. layer_generation_dtt=dtt_roof-1 if dtt_roof!=0; - // if the roof should be valid, check that the area does generate lines. This is NOT guaranteed. - if (overhang_lines.empty() && dtt_roof != 0 && generateLines(overhang_outset, true, layer_idx - layer_generation_dtt).empty()) - for (size_t idx = 0; idx < dtt_roof; idx++) { - // check for every roof area that it has resulting lines. Remember idx 1 means the 2. layer of roof => higher idx == lower layer - if (generateLines(added_roofs[idx], true, layer_idx - idx).empty()) { - dtt_roof = idx; - layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; - break; - } - } - - { - std::lock_guard lock(mutex_layer_storage); - for (size_t idx = 0; idx < dtt_roof; ++ idx) - if (! added_roofs[idx].empty()) { - SupportGeneratorLayer *&l = top_contacts[layer_idx - idx]; - if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, layer_idx - idx); - // will be unioned in finalize_interface_and_support_areas() - append(l->polygons, std::move(added_roofs[idx])); - } - } - - 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. - // 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 - Polylines polylines = ensure_maximum_distance_polyline( - generateLines(overhang_outset, dtt_roof != 0, layer_idx - layer_generation_dtt), dtt_roof == 0 ? mesh_config.min_radius / 2 : connect_length, 1); - size_t point_count = 0; - for (const Polyline &poly : polylines) - point_count += poly.size(); - 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, - // as some support is better than none. - Polygons reduced_overhang_outset = offset(union_ex(overhang_outset), -mesh_config.support_line_width / 2.2, jtMiter, 1.2); - polylines = ensure_maximum_distance_polyline( - to_polylines( - ! reduced_overhang_outset.empty() && - area(offset(diff_ex(overhang_outset, reduced_overhang_outset), std::max(mesh_config.support_line_width, connect_length), jtMiter, 1.2)) < sqr(scaled(0.001)) ? - reduced_overhang_outset : - overhang_outset), - connect_length, min_support_points); - } - LayerIndex last_insert_layer = layer_idx - dtt_roof; - overhang_lines = convert_lines_to_internal(volumes, config, polylines, last_insert_layer); - } - - if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part && ! overhang_outset.empty()) { - // reached buildplate - std::lock_guard lock(mutex_layer_storage); - SupportGeneratorLayer*& l = top_contacts[0]; - if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, 0); - append(l->polygons, std::move(overhang_outset)); - } else // normal trees have to be generated - addLinesAsInfluenceAreas(overhang_lines, force_tip_to_roof ? support_roof_layers - dtt_roof : 0, layer_idx - dtt_roof, dtt_roof > 0, roof_enabled ? support_roof_layers - dtt_roof : 0); - throw_on_cancel(); - } - } - }); - if (raft_contact_layer_idx >= 0) { const size_t first_tree_layer = print_object.slicing_parameters().raft_layers() - 1; // Remove tree tips that start below the raft contact, @@ -1490,6 +1077,559 @@ static void generate_initial_areas( } } +class InterfacePlacer { +public: + InterfacePlacer(const SlicingParameters &slicing_parameters, const TreeModelVolumes &volumes, const TreeSupportSettings &config, bool force_tip_to_roof, size_t num_support_layers, + std::vector &move_bounds, SupportGeneratorLayerStorage &layer_storage, SupportGeneratorLayersPtr &top_contacts) : + slicing_parameters(slicing_parameters), volumes(volumes), config(config), force_tip_to_roof(force_tip_to_roof), + move_bounds(move_bounds), layer_storage(layer_storage), top_contacts(top_contacts) { + m_already_inserted.assign(num_support_layers, {}); + this->min_xy_dist = config.xy_distance > config.xy_min_distance; + } + const SlicingParameters &slicing_parameters; + const TreeModelVolumes &volumes; + const TreeSupportSettings &config; + bool force_tip_to_roof; + bool min_xy_dist; + + // Outputs + std::vector &move_bounds; + SupportGeneratorLayerStorage &layer_storage; + SupportGeneratorLayersPtr &top_contacts; + +private: + // Temps + static constexpr const auto m_base_radius = scaled(0.01); + const Polygon m_base_circle { make_circle(m_base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION) }; + + // Mutexes, guards + std::mutex m_mutex_movebounds; + std::mutex m_mutex_layer_storage; + std::vector> m_already_inserted; + +public: + void add_roof_unguarded(Polygons &&new_roofs, const size_t insert_layer_idx) + { + SupportGeneratorLayer*& l = top_contacts[insert_layer_idx]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, insert_layer_idx); + // will be unioned in finalize_interface_and_support_areas() + append(l->polygons, std::move(new_roofs)); + } + + void add_roof(Polygons &&new_roofs, const size_t insert_layer_idx) + { + std::lock_guard lock(m_mutex_layer_storage); + add_roof_unguarded(std::move(new_roofs), insert_layer_idx); + } + + void add_roofs(std::vector &&new_roofs, const size_t insert_layer_idx, const size_t dtt_roof) + { + if (! new_roofs.empty()) { + std::lock_guard lock(m_mutex_layer_storage); + for (size_t idx = 0; idx < dtt_roof; ++ idx) + if (! new_roofs[idx].empty()) + add_roof_unguarded(std::move(new_roofs[idx]), insert_layer_idx - idx); + } + } + + void add_roof_build_plate(Polygons &&overhang_areas) + { + std::lock_guard lock(m_mutex_layer_storage); + SupportGeneratorLayer*& l = top_contacts[0]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::TopContact, slicing_parameters, config, 0); + append(l->polygons, std::move(overhang_areas)); + } + + void add_points_along_lines( + // Insert points (tree tips or top contact interfaces) along these lines. + LineInformations lines, + // Start at this layer. + LayerIndex insert_layer_idx, + // Insert this number of interface layers. + size_t roof_tip_layers, + // True if an interface is already generated above these lines. + bool supports_roof, + // The element tries to not move until this dtt is reached. + size_t dont_move_until) + { + validate_range(lines); + // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible + size_t dtt_roof_tip; + for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; ++ dtt_roof_tip) { + size_t this_layer_idx = insert_layer_idx - dtt_roof_tip; + auto evaluateRoofWillGenerate = [&](const std::pair &p) { + //FIXME Vojtech: The circle is just shifted, it has a known size, the infill should fit all the time! + #if 0 + Polygon roof_circle; + for (Point corner : base_circle) + roof_circle.points.emplace_back(p.first + corner * config.min_radius); + return !generate_support_infill_lines({ roof_circle }, config, true, insert_layer_idx - dtt_roof_tip, config.support_roof_line_distance).empty(); + #else + return true; + #endif + }; + + { + std::pair split = + // keep all lines that are still valid on the next layer + split_lines(lines, [this, this_layer_idx](const std::pair &p) + { return evaluate_point_for_next_layer_function(volumes, config, this_layer_idx, p); }); + LineInformations points = std::move(split.second); + // Not all roofs are guaranteed to actually generate lines, so filter these out and add them as points. + split = split_lines(split.first, evaluateRoofWillGenerate); + lines = std::move(split.first); + append(points, split.second); + // 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, + // don't move until + roof_tip_layers - dtt_roof_tip, + // supports roof + dtt_roof_tip > 0, + // disable ovalization + false); + } + + // add all tips as roof to the roof storage + Polygons new_roofs; + for (const LineInformation &line : lines) + //FIXME sweep the tip radius along the line? + for (const std::pair &p : line) { + Polygon roof_circle{ m_base_circle }; + roof_circle.scale(config.min_radius / m_base_radius); + roof_circle.translate(p.first); + new_roofs.emplace_back(std::move(roof_circle)); + } + this->add_roof(std::move(new_roofs), this_layer_idx); + } + + for (const LineInformation &line : lines) { + // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. + // Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width + bool disable_ovalistation = config.min_radius < 3 * config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > 5; + for (const std::pair &point_data : line) + add_point_as_influence_area(point_data, insert_layer_idx - dtt_roof_tip, + // don't move until + dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, + // supports roof + dtt_roof_tip > 0 || supports_roof, + disable_ovalistation); + } + } + + void add_point_as_influence_area(std::pair p, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) + { + bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; + bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; + bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; + if (! config.support_rests_on_model && ! to_bp) { + BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; + tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly."sv, true); + return; + } + Polygons circle{ m_base_circle }; + circle.front().translate(p.first); + { + std::lock_guard critical_section_movebounds(m_mutex_movebounds); + Point hash_pos = p.first / ((config.min_radius + 1) / 10); + if (!m_already_inserted[insert_layer].count(hash_pos)) { + // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing + m_already_inserted[insert_layer].emplace(hash_pos); + static constexpr const size_t dtt = 0; + SupportElementState state; + state.target_height = insert_layer; + state.target_position = p.first; + state.next_position = p.first; + state.layer_idx = insert_layer; + state.effective_radius_height = dtt; + state.to_buildplate = to_bp; + state.distance_to_top = dtt; + state.result_on_layer = p.first; + assert(state.result_on_layer_is_set()); + state.increased_to_model_radius = 0; + state.to_model_gracious = gracious; + state.elephant_foot_increases = 0; + state.use_min_xy_dist = min_xy_dist; + state.supports_roof = roof; + state.dont_move_until = dont_move_until; + state.can_use_safe_radius = safe_radius; + state.missing_roof_layers = force_tip_to_roof ? dont_move_until : 0; + state.skip_ovalisation = skip_ovalisation; + move_bounds[insert_layer].emplace_back(state, std::move(circle)); + } + } + }; +}; + +// 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: +// 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 +// roofs aka interface layers by the tool path generator. +void sample_overhang_area( + // Area to support + Polygons &&overhang_area, + // If true, then the overhang_area is likely large and wide, thus it is worth to try + // to cover it with continuous interfaces supported by zig-zag patterned tree tips. + const bool large_horizontal_roof, + // Index of the top suport layer generated by this function. + const size_t layer_idx, + // Number of roof (contact, interface) layers between the overhang and tree tips. + const size_t num_support_roof_layers, + // + const coord_t connect_length, + // Configuration classes + const TreeSupportMeshGroupSettings &mesh_group_settings, + const SupportParameters &support_params, + // Configuration & Output + InterfacePlacer &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 + // 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 + // 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 = [&support_params, &mesh_group_settings](const Polygons &area, LayerIndex layer_idx) -> Polylines { + return generate_support_infill_lines(area, support_params, true, layer_idx, mesh_group_settings.support_roof_line_distance); + }; + + LineInformations overhang_lines; + size_t dtt_roof = 0; + size_t layer_generation_dtt = 0; + + if (large_horizontal_roof) { + assert(num_support_roof_layers > 0); + // Sometimes roofs could be empty as the pattern does not generate lines if the area is narrow enough (i am looking at you, concentric infill). + // To catch these cases the added roofs are saved to be evaluated later. + std::vector added_roofs(num_support_roof_layers); + Polygons last_overhang = overhang_area; + for (dtt_roof = 0; dtt_roof < num_support_roof_layers && layer_idx - dtt_roof >= 1; ++ dtt_roof) { + // here the roof is handled. If roof can not be added the branches will try to not move instead + Polygons forbidden_next; + { + const bool min_xy_dist = interface_placer.config.xy_distance > interface_placer.config.xy_min_distance; + const Polygons &forbidden_next_raw = interface_placer.config.support_rests_on_model ? + interface_placer.volumes.getCollision(interface_placer.config.getRadius(0), layer_idx - (dtt_roof + 1), min_xy_dist) : + interface_placer.volumes.getAvoidance(interface_placer.config.getRadius(0), layer_idx - (dtt_roof + 1), TreeModelVolumes::AvoidanceType::Fast, false, min_xy_dist); + // prevent rounding errors down the line + //FIXME maybe use SafetyOffset::Yes at the following diff() instead? + forbidden_next = offset(union_ex(forbidden_next_raw), scaled(0.005), jtMiter, 1.2); + } + Polygons overhang_area_next = diff(overhang_area, forbidden_next); + if (area(overhang_area_next) < mesh_group_settings.minimum_roof_area) { + // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter + if (dtt_roof != 0) { + size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; + // Produce support head points supporting an interface layer: First produce the interface lines, then sample them. + overhang_lines = split_lines( + convert_lines_to_internal(interface_placer.volumes, interface_placer.config, + ensure_maximum_distance_polyline(generate_roof_lines(last_overhang, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before), + [&interface_placer, layer_idx, dtt_before](const std::pair &p) + { return evaluate_point_for_next_layer_function(interface_placer.volumes, interface_placer.config, layer_idx - dtt_before, p); }) + .first; + } + break; + } + added_roofs[dtt_roof] = overhang_area; + last_overhang = std::move(overhang_area); + overhang_area = std::move(overhang_area_next); + } + + layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; // 1 inside max and -1 outside to avoid underflow. layer_generation_dtt=dtt_roof-1 if dtt_roof!=0; + // if the roof should be valid, check that the area does generate lines. This is NOT guaranteed. + if (overhang_lines.empty() && dtt_roof != 0 && generate_roof_lines(overhang_area, layer_idx - layer_generation_dtt).empty()) + for (size_t idx = 0; idx < dtt_roof; idx++) { + // check for every roof area that it has resulting lines. Remember idx 1 means the 2. layer of roof => higher idx == lower layer + if (generate_roof_lines(added_roofs[idx], layer_idx - idx).empty()) { + dtt_roof = idx; + layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; + break; + } + } + interface_placer.add_roofs(std::move(added_roofs), layer_idx, dtt_roof); + } + + 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. + // 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; + Polylines polylines = ensure_maximum_distance_polyline( + generate_support_infill_lines(overhang_area, support_params, supports_roof, layer_idx - layer_generation_dtt, + supports_roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance), + continuous_tips ? interface_placer.config.min_radius / 2 : connect_length, 1); + size_t point_count = 0; + for (const Polyline &poly : polylines) + point_count += poly.size(); + 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, + // 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); + polylines = ensure_maximum_distance_polyline( + to_polylines( + ! reduced_overhang_area.empty() && + area(offset(diff_ex(overhang_area, reduced_overhang_area), std::max(interface_placer.config.support_line_width, connect_length), jtMiter, 1.2)) < sqr(scaled(0.001)) ? + reduced_overhang_area : + overhang_area), + connect_length, min_support_points); + } + overhang_lines = convert_lines_to_internal(interface_placer.volumes, interface_placer.config, polylines, layer_idx - dtt_roof); + } + + if (int(dtt_roof) >= layer_idx && large_horizontal_roof) + // reached buildplate + interface_placer.add_roof_build_plate(std::move(overhang_area)); + else { + // normal trees have to be generated + const bool roof_enabled = num_support_roof_layers > 0; + interface_placer.add_points_along_lines( + // Sample along these lines + overhang_lines, + // First layer index to insert the tree tips or interfaces. + layer_idx - dtt_roof, + // Remaining roof tip layers. + interface_placer.force_tip_to_roof ? num_support_roof_layers - dtt_roof : 0, + // Supports roof already? + dtt_roof > 0, + // Don't move until the following distance to top is reached. + roof_enabled ? num_support_roof_layers - dtt_roof : 0); + } +} + +/*! + * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. + * + * Generates Points where the Model should be supported and creates the areas where these points have to be placed. + * + * \param mesh[in] The mesh that is currently processed. + * \param move_bounds[out] Storage for the influence areas. + * \param storage[in] Background storage, required for adding roofs. + */ +static void generate_initial_areas( + const PrintObject &print_object, + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + const std::vector &overhangs, + std::vector &move_bounds, + SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &top_interface_layers, + SupportGeneratorLayerStorage &layer_storage, + std::function throw_on_cancel) +{ + using AvoidanceType = TreeModelVolumes::AvoidanceType; + TreeSupportMeshGroupSettings mesh_group_settings(print_object); + SupportParameters support_params(print_object); + support_params.with_sheath = true; + support_params.support_density = 0; + + // To ensure z_distance_top_layers are left empty between the overhang (zeroth empty layer), the support has to be added z_distance_top_layers+1 layers below + const size_t z_distance_delta = config.z_distance_top_layers + 1; + + const bool min_xy_dist = config.xy_distance > config.xy_min_distance; + +#if 0 + if (mesh.overhang_areas.size() <= z_distance_delta) + return; +#endif + + 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. + // 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 : + sqrt(sqr(config.min_radius) - sqr(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. + //FIXME There is no account for the support overhang angle. + //FIXME There is no account for the width of the collision regions. + const coord_t extra_outset = std::max(coord_t(0), config.min_radius - config.support_line_width / 2) + (min_xy_dist ? config.support_line_width / 2 : 0) + //FIXME this is a heuristic value for support enforcers to work. +// + 10 * config.support_line_width; + ; + const size_t num_support_roof_layers = mesh_group_settings.support_roof_enable ? (mesh_group_settings.support_roof_height + config.layer_height / 2) / config.layer_height : 0; + const bool roof_enabled = num_support_roof_layers > 0; + const bool force_tip_to_roof = sqr(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area && roof_enabled; + //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. + const coord_t max_overhang_speed = mesh_group_settings.support_angle < 0.5 * M_PI ? coord_t(tan(mesh_group_settings.support_angle) * config.layer_height) : std::numeric_limits::max(); + // 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. + const coord_t max_overhang_insert_lag = config.z_distance_top_layers > 0 ? + std::max(round_up_divide(config.xy_distance, max_overhang_speed / 2), 2 * config.z_distance_top_layers) : + 0; + + size_t num_support_layers; + int raft_contact_layer_idx; + // Layers with their overhang regions. + std::vector> raw_overhangs; + + { + const size_t num_raft_layers = config.raft_layers.size(); + const size_t first_support_layer = std::max(int(num_raft_layers) - int(z_distance_delta), 1); + num_support_layers = size_t(std::max(0, int(print_object.layer_count()) + int(num_raft_layers) - int(z_distance_delta))); + raft_contact_layer_idx = generate_raft_contact(print_object, config, top_contacts, layer_storage); + // Enumerate layers for which the support tips may be generated from overhangs above. + raw_overhangs.reserve(num_support_layers - first_support_layer); + for (size_t layer_idx = first_support_layer; layer_idx < num_support_layers; ++ layer_idx) + if (const size_t overhang_idx = layer_idx + z_distance_delta; ! overhangs[overhang_idx].empty()) + raw_overhangs.push_back({ layer_idx, &overhangs[overhang_idx] }); + } + + InterfacePlacer interface_placer{ print_object.slicing_parameters(), volumes, config, force_tip_to_roof, num_support_layers, + // Outputs + move_bounds, layer_storage, top_contacts }; + + tbb::parallel_for(tbb::blocked_range(0, raw_overhangs.size()), + [&volumes, &config, &raw_overhangs, &mesh_group_settings, &support_params, + min_xy_dist, force_tip_to_roof, roof_enabled, num_support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, + &interface_placer, &throw_on_cancel](const tbb::blocked_range &range) { + for (size_t raw_overhang_idx = range.begin(); raw_overhang_idx < range.end(); ++ raw_overhang_idx) { + size_t layer_idx = raw_overhangs[raw_overhang_idx].first; + const Polygons &overhang_raw = *raw_overhangs[raw_overhang_idx].second; + + // take the least restrictive avoidance possible + Polygons relevant_forbidden; + { + const Polygons &relevant_forbidden_raw = config.support_rests_on_model ? + volumes.getCollision(config.getRadius(0), layer_idx, min_xy_dist) : + volumes.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::Fast, false, min_xy_dist); + // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. + 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 + // 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. + 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"); + + // offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang + Polygons remaining_overhang = intersection( + diff(mesh_group_settings.support_offset == 0 ? + overhang_raw : + offset(union_ex(overhang_raw), mesh_group_settings.support_offset, jtMiter, 1.2), + offset(union_ex(overhang_regular), config.support_line_width * 0.5, jtMiter, 1.2)), + relevant_forbidden); + + // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. + //+config.support_line_width / 80 to avoid calculating very small (useless) offsets because of rounding errors. + //FIXME likely a better approach would be to find correspondences between the full overhang and the trimmed overhang + // and if there is no correspondence, project the missing points to the clipping curve. + 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 : + circle_length_to_half_linewidth_change, + extra_outset - extra_total_offset_acc); + extra_total_offset_acc += offset_current_step; + const Polygons &raw_collision = volumes.getCollision(0, layer_idx, true); + const coord_t offset_step = config.xy_min_distance + config.support_line_width; + // Reducing the remaining overhang by the areas already supported. + //FIXME 1.5 * extra_total_offset_acc seems to be too much, it may remove some remaining overhang without being supported at all. + remaining_overhang = diff(remaining_overhang, safe_offset_inc(overhang_regular, 1.5 * extra_total_offset_acc, raw_collision, offset_step, 0, 1)); + // Extending the overhangs by the inflated remaining overhangs. + overhang_regular = union_(overhang_regular, diff(safe_offset_inc(remaining_overhang, extra_total_offset_acc, raw_collision, offset_step, 0, 1), relevant_forbidden)); + //check_self_intersections(overhang_regular, "overhang_regular2"); + } +#if 0 + // If the xy distance overrides the z distance, some support needs to be inserted further down. + //=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the the setting description, it would not produce reasonable results. ) + if (! min_xy_dist) { + 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 + // 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), + config.min_radius, 1); + if (polylines.size() <= 3) + // add the outer wall to ensure it is correct supported instead + polylines = ensure_maximum_distance_polyline(to_polylines(remaining_overhang), connect_length, 3); + for (const auto &line : polylines) { + LineInformation res_line; + for (Point p : line) + res_line.emplace_back(p, LineStatus::INVALID); + overhang_lines.emplace_back(res_line); + } + validate_range(overhang_lines); + } + 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 ? + 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. + auto evaluatePoint = [&](std::pair p) { return contains(relevant_forbidden_below, p.first); }; + + std::pair split = split_lines(overhang_lines, evaluatePoint); // keep all lines that are invalid + overhang_lines = split.first; + // Set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. + LineInformations fresh_valid_points = convert_lines_to_internal(volumes, config, convert_internal_to_lines(split.second), layer_idx - lag_ctr); + validate_range(fresh_valid_points); + + interface_placer.add_points_along_lines(fresh_valid_points, (force_tip_to_roof && lag_ctr <= num_support_roof_layers) ? num_support_roof_layers : 0, layer_idx - lag_ctr, false, roof_enabled ? num_support_roof_layers : 0); + } + } +#endif + } + + throw_on_cancel(); + + if (roof_enabled) { + // Try to support the overhangs by dense interfaces for num_support_roof_layers, cover the bottom most interface with tree tips. + static constexpr const coord_t support_roof_offset = 0; + Polygons overhang_roofs = safe_offset_inc(overhang_raw, support_roof_offset, relevant_forbidden, config.min_radius * 2 + config.xy_min_distance, 0, 1); + if (mesh_group_settings.minimum_support_area > 0) + remove_small(overhang_roofs, mesh_group_settings.minimum_roof_area); + overhang_regular = diff(overhang_regular, overhang_roofs, ApplySafetyOffset::Yes); + //check_self_intersections(overhang_regular, "overhang_regular3"); + for (ExPolygon &roof_part : union_ex(overhang_roofs)) { + sample_overhang_area(to_polygons(std::move(roof_part)), true, layer_idx, num_support_roof_layers, connect_length, + mesh_group_settings, support_params, interface_placer); + throw_on_cancel(); + } + } + // Either the roof is not enabled, then these are all the overhangs to be supported, + // 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)), + // Don't + false, layer_idx, num_support_roof_layers, connect_length, + mesh_group_settings, support_params, interface_placer); + throw_on_cancel(); + } + } + }); + + finalize_raft_contact(print_object, raft_contact_layer_idx, top_contacts, move_bounds); +} + static unsigned int move_inside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits::max()) { Point ret = from; @@ -3911,7 +4051,7 @@ static void organic_smooth_branches_avoid_collisions( for (size_t i = 0; i < projections.size(); ++ i) { const SupportElement &element = *elements_with_link_down[i].first; const int below = elements_with_link_down[i].second; - const bool locked = below == -1 && element.state.layer_idx > 0; + const bool locked = (below == -1 && element.state.layer_idx > 0) || element.state.locked(); if (! locked && pts[i] != projections[i]) { // Nudge the circle center away from the collision. Vec3d v{ projections[i].x() - pts[i].x(), projections[i].y() - pts[i].y(), projections[i].z() - pts[i].z() }; diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index ff3c0f8f3..f4ec76cda 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -122,7 +122,7 @@ struct SupportElementStateBits { bool use_min_xy_dist : 1; /*! - * \brief True if this Element or any parent provides support to a support roof. + * \brief True if this Element or any parent (element above) provides support to a support roof. */ bool supports_roof : 1; @@ -193,7 +193,7 @@ struct SupportElementState : public SupportElementStateBits double elephant_foot_increases; /*! - * \brief The element trys not to move until this dtt is reached, is set to 0 if the element had to move. + * \brief The element tries to not move until this dtt is reached, is set to 0 if the element had to move. */ uint32_t dont_move_until; @@ -218,6 +218,8 @@ struct SupportElementState : public SupportElementStateBits dst.skip_ovalisation = false; return dst; } + + [[nodiscard]] bool locked() const { return this->distance_to_top < this->dont_move_until; } }; struct SupportElement diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 5eb1d2087..c8c3923f7 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -291,7 +291,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) (config->opt_bool("support_material") || config->opt_int("support_material_enforce_layers") > 0); for (const std::string& key : { "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", - "support_tree_branch_diameter_angle", "support_tree_tip_diameter", "support_tree_top_rate" }) + "support_tree_branch_diameter_angle", "support_tree_tip_diameter", "support_tree_branch_distance", "support_tree_top_rate" }) toggle_field(key, has_organic_supports); for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6923aaff4..41a015e9f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1531,6 +1531,7 @@ void TabPrint::build() optgroup->append_single_option_line("support_tree_branch_diameter", category_path + "tree_branch_diameter"); optgroup->append_single_option_line("support_tree_branch_diameter_angle", category_path + "tree_branch_diameter_angle"); optgroup->append_single_option_line("support_tree_tip_diameter", category_path + "tree_tip_diameter"); + optgroup->append_single_option_line("support_tree_branch_distance", category_path + "tree_branch_distance"); optgroup->append_single_option_line("support_tree_top_rate", category_path + "tree_top_rate"); page = add_options_page(L("Speed"), "time");