From 7a1c118924dd61ddc80c02bf102a7fc47b098da1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 23 Sep 2022 16:37:33 +0200 Subject: [PATCH 1/4] Split object : Check if new objects don't have a zero volume Related to : * #8931 - Split to objects crashes Prusa Slicer * SPE-1221(https://dev.prusa3d.com/browse/SPE-1221) - Split to objects fail --- src/libslic3r/Model.cpp | 4 ++-- src/libslic3r/TriangleMesh.cpp | 8 ++++++++ src/libslic3r/TriangleMesh.hpp | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 9a2a0bb83..9f3c142f8 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1359,7 +1359,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects) size_t counter = 1; for (TriangleMesh &mesh : meshes) { // FIXME: crashes if not satisfied - if (mesh.facets_count() < 3) + if (mesh.facets_count() < 3 || mesh.has_zero_volume()) continue; // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed? @@ -1833,7 +1833,7 @@ size_t ModelVolume::split(unsigned int max_extruders) const Vec3d offset = this->get_offset(); for (TriangleMesh &mesh : meshes) { - if (mesh.empty()) + if (mesh.empty() || mesh.has_zero_volume()) // Repair may have removed unconnected triangles, thus emptying the mesh. continue; diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index da6fcf28f..df820fac9 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -378,6 +378,14 @@ bool TriangleMesh::is_splittable() const return its_is_splittable(this->its); } +bool TriangleMesh::has_zero_volume() const +{ + const Vec3d sz = size(); + const double volume_val = sz.x() * sz.y() * sz.z(); + + return is_approx(volume_val, 0.0); +} + std::vector TriangleMesh::split() const { std::vector itss = its_split(this->its); diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 85a8a24e1..f30776307 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -139,6 +139,7 @@ public: bool empty() const { return this->facets_count() == 0; } bool repaired() const; bool is_splittable() const; + bool has_zero_volume() const; // Estimate of the memory occupied by this structure, important for keeping an eye on the Undo / Redo stack allocation. size_t memsize() const; From f19882749fff72f39cae7a0e0fc2cd7d1c651d82 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 26 Sep 2022 09:38:08 +0200 Subject: [PATCH 2/4] #8941 - GCodeViewer - Extended processing of line G10 for RepRap firmware (set tool temperature) --- src/libslic3r/GCode/GCodeProcessor.cpp | 29 ++++++++++++++++++++++---- src/libslic3r/GCode/GCodeProcessor.hpp | 2 +- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 3c773c89a..3bc1f622d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1769,7 +1769,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool switch (cmd[1]) { case '1': switch (cmd[2]) { - case '0': { process_G10(line); break; } // Retract + case '0': { process_G10(line); break; } // Retract or Set tool temperature case '1': { process_G11(line); break; } // Unretract default: break; } @@ -3232,6 +3232,23 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line) { + if (m_flavor == gcfRepRapFirmware) { + // similar to M104/M109 + float new_temp; + if (line.has_value('S', new_temp)) { + size_t id = m_extruder_id; + float val; + if (line.has_value('P', val)) { + const size_t eid = static_cast(val); + if (eid < m_extruder_temps.size()) + id = eid; + } + + m_extruder_temps[id] = new_temp; + return; + } + } + // stores retract move store_move_vertex(EMoveType::Retract); } @@ -3441,18 +3458,22 @@ void GCodeProcessor::process_M108(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line) { float new_temp; + size_t id = (size_t)-1; if (line.has_value('R', new_temp)) { float val; if (line.has_value('T', val)) { const size_t eid = static_cast(val); if (eid < m_extruder_temps.size()) - m_extruder_temps[eid] = new_temp; + id = eid; } else - m_extruder_temps[m_extruder_id] = new_temp; + id = m_extruder_id; } else if (line.has_value('S', new_temp)) - m_extruder_temps[m_extruder_id] = new_temp; + id = m_extruder_id; + + if (id != (size_t)-1) + m_extruder_temps[id] = new_temp; } void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line) diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 484f41616..a7bf0cd07 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -674,7 +674,7 @@ namespace Slic3r { void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise); #endif // ENABLE_PROCESS_G2_G3_LINES - // Retract + // Retract or Set tool temperature void process_G10(const GCodeReader::GCodeLine& line); // Unretract From 87dcba3e307f44d2979ee07b460e526e580f354d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 2 Sep 2022 08:21:39 +0200 Subject: [PATCH 3/4] WIP TreeSupports: turned SupportElement::area from pointer to value. --- src/libslic3r/TreeSupport.cpp | 234 +++++++++++++++------------------- src/libslic3r/TreeSupport.hpp | 16 +-- 2 files changed, 114 insertions(+), 136 deletions(-) diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 56ccfd7d1..1f4ead589 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -457,12 +457,9 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo // if (m_config.branch_radius==2121) // BOOST_LOG_TRIVIAL(error) << "Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)"; - for (auto &layer : move_bounds) { - for (auto elem : layer) { - delete elem->area; + for (auto &layer : move_bounds) + for (auto elem : layer) delete elem; - } - } auto remove_undefined_layers = [](SupportGeneratorLayersPtr &layers) { layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); @@ -593,7 +590,7 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo */ [[nodiscard]] static bool evaluatePointForNextLayerFunction( const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, - size_t current_layer, std::pair &p) + size_t current_layer, const std::pair &p) { using AvoidanceType = TreeSupport::AvoidanceType; const bool min_xy_dist = config.xy_distance > config.xy_min_distance; @@ -616,47 +613,25 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo * \return A pair with which points are still valid in the first slot and which are not in the second slot. */ template -[[nodiscard]] static std::pair splitLines(LineInformations lines, EvaluatePointFn evaluatePoint) +[[nodiscard]] static std::pair splitLines(const LineInformations &lines, EvaluatePointFn evaluatePoint) { // assumes all Points on the current line are valid - LineInformations keep(1); - LineInformations set_free(1); - enum STATE - { - keeping, - freeing - }; - for (std::vector> line : lines) { - STATE current = keeping; + LineInformations keep; + LineInformations set_free; + for (const std::vector> &line : lines) { + bool current_keep = true; LineInformation resulting_line; - for (std::pair me : line) { - if (evaluatePoint(me)) { - if (keeping != current) { - if (!resulting_line.empty()) { - set_free.emplace_back(resulting_line); - resulting_line.clear(); - } - current = keeping; - } - resulting_line.emplace_back(me); - } else { - if (freeing != current) { - if (!resulting_line.empty()) { - keep.emplace_back(resulting_line); - resulting_line.clear(); - } - current = freeing; - } - resulting_line.emplace_back(me); + for (const std::pair &me : line) { + if (evaluatePoint(me) != current_keep) { + if (! resulting_line.empty()) + (current_keep ? &keep : &set_free)->emplace_back(std::move(resulting_line)); + current_keep = !current_keep; } + resulting_line.emplace_back(me); } - if (!resulting_line.empty()) { - if (current == keeping) - keep.emplace_back(resulting_line); - else - set_free.emplace_back(resulting_line); - } + if (! resulting_line.empty()) + (current_keep ? &keep : &set_free)->emplace_back(std::move(resulting_line)); } validate_range(keep); validate_range(set_free); @@ -1088,7 +1063,9 @@ void TreeSupport::generateInitialAreas( // 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)); + 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. @@ -1097,12 +1074,18 @@ void TreeSupport::generateInitialAreas( //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; + 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). - 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(); - const size_t max_overhang_insert_lag = std::max((size_t)round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers); // 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 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 = std::max(round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers); //FIXME size_t num_support_layers = print_object.layer_count(); @@ -1133,35 +1116,36 @@ void TreeSupport::generateInitialAreas( return generateSupportInfillLines(area, support_params, roof, layer_idx, support_infill_distance); }; - 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"; - TreeSupport::showError("Unable to add tip. Some overhang may not be supported correctly.", true); - return; - } - Polygon circle; - for (Point corner : base_circle) - circle.points.emplace_back(p.first + corner); - { - std::lock_guard critical_section_movebounds(mutex_movebounds); - if (! already_inserted[insert_layer].count(p.first / ((mesh_config.min_radius + 1) / 10))) { - // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing - already_inserted[insert_layer].emplace(p.first / ((mesh_config.min_radius + 1) / 10)); - SupportElement* elem = new SupportElement(dtt, insert_layer, p.first, to_bp, gracious, min_xy_dist, dont_move_until, roof, safe_radius, force_tip_to_roof, skip_ovalisation); - elem->area = new Polygons(); - validate_range(circle); - elem->area->emplace_back(std::move(circle)); - move_bounds[insert_layer].emplace(elem); - } - } - }; - + // 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"; + TreeSupport::showError("Unable to add tip. Some overhang may not be supported correctly.", 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); + move_bounds[insert_layer].emplace( + new SupportElement(dtt, insert_layer, p.first, to_bp, gracious, min_xy_dist, dont_move_until, roof, safe_radius, force_tip_to_roof, skip_ovalisation, 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; @@ -1179,30 +1163,30 @@ void TreeSupport::generateInitialAreas( #endif }; - std::pair split = - // keep all lines that are still valid on the next layer - splitLines(lines, [this, insert_layer_idx, dtt_roof_tip](std::pair &p){ return evaluatePointForNextLayerFunction(m_volumes, m_config, insert_layer_idx - dtt_roof_tip, p); }); - - for (LineInformation line : split.second) // add all points that would not be valid - for (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); - - // not all roofs are guaranteed to actually generate lines, so filter these out and add them as points - split = splitLines(split.first, evaluateRoofWillGenerate); - lines = split.first; - - for (LineInformation line : split.second) - for (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); + { + std::pair split = + // keep all lines that are still valid on the next layer + splitLines(lines, [this, insert_layer_idx, dtt_roof_tip](const std::pair &p){ return evaluatePointForNextLayerFunction(m_volumes, m_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 = splitLines(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 (LineInformation line : lines) - for (std::pair p : line) { - Polygon roof_circle; - for (Point corner : base_circle) - roof_circle.points.emplace_back(p.first + corner * mesh_config.min_radius / base_radius); - added_roofs.emplace_back(roof_circle); + 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); @@ -1290,7 +1274,8 @@ void TreeSupport::generateInitialAreas( std::pair split = splitLines(overhang_lines, evaluatePoint); // keep all lines that are invalid overhang_lines = split.first; - LineInformations fresh_valid_points = convertLinesToInternal(m_volumes, m_config, convertInternalToLines(split.second), layer_idx - lag_ctr); // set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. + // 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 = convertLinesToInternal(m_volumes, m_config, convertInternalToLines(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); @@ -1344,18 +1329,19 @@ void TreeSupport::generateInitialAreas( m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), min_xy_dist)) : m_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 - size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; 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 = convertLinesToInternal(m_volumes, m_config, ensureMaximumDistancePolyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); overhang_lines = splitLines(overhang_lines, - [this, layer_idx, dtt_before](std::pair &p){ return evaluatePointForNextLayerFunction(m_volumes, m_config, layer_idx - dtt_before, p); }).first; + [this, layer_idx, dtt_before](const std::pair &p){ return evaluatePointForNextLayerFunction(m_volumes, m_config, layer_idx - dtt_before, p); }).first; } break; } @@ -1825,7 +1811,7 @@ std::optional TreeSupport::increaseSingleArea(AreaI polygons_simplify(increased, scaled(0.025)); } else // if no movement is done the areas keep parent area as no move == offset(0) - increased = *parent->area; + increased = parent->area; if (mergelayer || current_elem.to_buildplate) { to_bp_data = safeUnion(diff_clipped(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); @@ -2030,7 +2016,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to } order = new_order; } - if (elem.to_buildplate || (elem.to_model_gracious && intersection(*parent->area, m_volumes.getPlaceableAreas(radius, layer_idx)).empty())) // error case + if (elem.to_buildplate || (elem.to_model_gracious && intersection(parent->area, m_volumes.getPlaceableAreas(radius, layer_idx)).empty())) // error case { // it is normal that we wont be able to find a new area at some point in time if we wont be able to reach layer 0 aka have to connect with the model insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move }, true); @@ -2049,12 +2035,12 @@ void TreeSupport::increaseAreas(std::unordered_map& to 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 // was never made for precision in the single digit micron range. - offset_slow = safeOffsetInc(*parent->area, extra_speed + extra_slow_speed + m_config.maximum_move_distance_slow, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2); + offset_slow = safeOffsetInc(parent->area, extra_speed + extra_slow_speed + m_config.maximum_move_distance_slow, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2); } if ((settings.increase_speed != slow_speed) && offset_fast.empty()) { if (offset_independant_faster) - offset_fast = safeOffsetInc(*parent->area, extra_speed + m_config.maximum_move_distance, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 1); + offset_fast = safeOffsetInc(parent->area, extra_speed + m_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 = m_config.maximum_move_distance - (m_config.maximum_move_distance_slow + extra_slow_speed); offset_fast = safeOffsetInc(offset_slow, delta_slow_fast, wall_restriction, safe_movement_distance, safe_movement_distance + radius, offset_independant_faster ? 2 : 1); @@ -2065,8 +2051,8 @@ void TreeSupport::increaseAreas(std::unordered_map& to 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 - Polygons lines_offset = offset(to_polylines(*parent->area), scaled(0.005), jtMiter, 1.2); - Polygons base_error_area = union_(*parent->area, lines_offset); + Polygons lines_offset = offset(to_polylines(parent->area), scaled(0.005), jtMiter, 1.2); + Polygons base_error_area = union_(parent->area, lines_offset); result = increaseSingleArea(settings, layer_idx, parent, base_error_area, to_bp_data, to_model_data, inc_wo_collision, (m_config.maximum_move_distance + extra_speed) * 1.5, mergelayer); #ifdef TREE_SUPPORT_SHOW_ERRORS BOOST_LOG_TRIVIAL(error) @@ -2118,10 +2104,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to { std::lock_guard critical_section_newLayer(critical_sections); if (bypass_merge) { - validate_range(max_influence_area); - Polygons* new_area = new Polygons(max_influence_area); - SupportElement* next = new SupportElement(elem, new_area); - bypass_merge_areas.emplace_back(next); + bypass_merge_areas.emplace_back(new SupportElement(elem, std::move(max_influence_area))); } else { influence_areas.emplace(elem, max_influence_area); if (elem.to_buildplate) @@ -2130,8 +2113,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to to_model_areas.emplace(elem, to_model_data); } } - } - else + } else parent->result_on_layer = Point(-1, -1); // 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). } }); @@ -2205,11 +2187,10 @@ void TreeSupport::createLayerPathing(std::vector>& mov const SupportElement &elem = tup.first; validate_range(tup.second); validate_range(safeUnion(tup.second)); - Polygons* new_area = new Polygons(safeUnion(tup.second)); - SupportElement* next = new SupportElement(elem, new_area); - move_bounds[layer_idx - 1].emplace(next); - - if (area(*new_area) < tiny_area_threshold) { + Polygons new_area = safeUnion(tup.second); + double new_area_area = area(new_area); + move_bounds[layer_idx - 1].emplace(new SupportElement(elem, std::move(new_area))); + if (new_area_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.to_buildplate; TreeSupport::showError("Insert error of area after merge.\n", true); } @@ -2217,7 +2198,7 @@ void TreeSupport::createLayerPathing(std::vector>& mov // Place already fully constructed elements in the output. for (SupportElement* elem : bypass_merge_areas) { - if (area(*elem->area) < tiny_area_threshold) { + if (area(elem->area) < tiny_area_threshold) { BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; TreeSupport::showError("Insert error of area after bypassing merge.\n", true); } @@ -2251,8 +2232,8 @@ void TreeSupport::setPointsOnAreas(const SupportElement* elem) continue; Point from = elem->result_on_layer; - if (! contains(*next_elem->area, from)) { - moveInside(*next_elem->area, from, 0); // Move inside has edgecases (see tests) so DONT use Polygons.inside to confirm correct move, Error with distance 0 is <= 1 + if (! contains(next_elem->area, from)) { + moveInside(next_elem->area, from, 0); // 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. While this seems like a problem it may for example occur after merges. } next_elem->result_on_layer = from; @@ -2274,7 +2255,7 @@ bool TreeSupport::setToModelContact(std::vector>& move for (LayerIndex layer_check = layer_idx; check->next_height >= layer_check; layer_check++) { - if (! intersection(*check->area, m_volumes.getPlaceableAreas(m_config.getCollisionRadius(*check), layer_check)).empty()) { + if (! intersection(check->area, m_volumes.getPlaceableAreas(m_config.getCollisionRadius(*check), layer_check)).empty()) { set = true; last_successfull_layer = layer_check; } @@ -2299,7 +2280,6 @@ bool TreeSupport::setToModelContact(std::vector>& move for (LayerIndex layer = layer_idx; layer <= first_elem->next_height; layer++) { move_bounds[layer].erase(checked[layer - layer_idx]); - delete checked[layer - layer_idx]->area; delete checked[layer - layer_idx]; } } @@ -2315,14 +2295,13 @@ bool TreeSupport::setToModelContact(std::vector>& move for (LayerIndex layer = layer_idx + 1; layer < last_successfull_layer - 1; layer++) { move_bounds[layer].erase(checked[layer - layer_idx]); - delete checked[layer - layer_idx]->area; delete checked[layer - layer_idx]; } // Guess a point inside the influence area, in which the branch will be placed in. Point best = checked[last_successfull_layer - layer_idx]->next_position; - if (! contains(*checked[last_successfull_layer - layer_idx]->area, best)) - moveInside(*checked[last_successfull_layer - layer_idx]->area, best); + if (! contains(checked[last_successfull_layer - layer_idx]->area, best)) + moveInside(checked[last_successfull_layer - layer_idx]->area, best); checked[last_successfull_layer - layer_idx]->result_on_layer = best; BOOST_LOG_TRIVIAL(debug) << "Added gracious Support On Model Point (" << best.x() << "," << best.y() << "). The current layer is " << last_successfull_layer; @@ -2332,8 +2311,8 @@ bool TreeSupport::setToModelContact(std::vector>& move else // can not add graceful => just place it here and hope for the best { Point best = first_elem->next_position; - if (! contains(*first_elem->area, best)) - moveInside(*first_elem->area, best); + if (! contains(first_elem->area, best)) + moveInside(first_elem->area, best); first_elem->result_on_layer = best; first_elem->to_model_gracious = false; BOOST_LOG_TRIVIAL(debug) << "Added NON gracious Support On Model Point (" << best.x() << "," << best.y() << "). The current layer is " << layer_idx; @@ -2346,8 +2325,8 @@ void TreeSupport::createNodesFromArea(std::vector>& mo // 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. for (SupportElement* init : move_bounds[0]) { Point p = init->next_position; - if (! contains(*init->area, p)) - moveInside(*init->area, p, 0); + if (! contains(init->area, p)) + moveInside(init->area, p, 0); init->result_on_layer = p; setPointsOnAreas(init); // also set the parent nodes, as these will be required for the first iteration of the loop below } @@ -2384,7 +2363,6 @@ void TreeSupport::createNodesFromArea(std::vector>& mo // delete all not needed support elements for (SupportElement* del : remove) { move_bounds[layer_idx].erase(del); - delete del->area; delete del; } remove.clear(); diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 75c32e54d..579835686 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -111,16 +111,16 @@ public: { explicit SupportElement( coord_t distance_to_top, size_t target_height, Point target_position, bool to_buildplate, bool to_model_gracious, bool use_min_xy_dist, size_t dont_move_until, - bool supports_roof, bool can_use_safe_radius, bool force_tips_to_roof, bool skip_ovalisation) : + bool supports_roof, bool can_use_safe_radius, bool force_tips_to_roof, bool skip_ovalisation, Polygons &&newArea) : target_height(target_height), target_position(target_position), next_position(target_position), next_height(target_height), effective_radius_height(distance_to_top), - to_buildplate(to_buildplate), distance_to_top(distance_to_top), area(nullptr), result_on_layer(target_position), increased_to_model_radius(0), to_model_gracious(to_model_gracious), + to_buildplate(to_buildplate), distance_to_top(distance_to_top), result_on_layer(target_position), increased_to_model_radius(0), to_model_gracious(to_model_gracious), elephant_foot_increases(0), use_min_xy_dist(use_min_xy_dist), supports_roof(supports_roof), dont_move_until(dont_move_until), can_use_safe_radius(can_use_safe_radius), - last_area_increase(AreaIncreaseSettings{ AvoidanceType::Fast, 0, false, false, false, false }), missing_roof_layers(force_tips_to_roof ? dont_move_until : 0), skip_ovalisation(skip_ovalisation) + last_area_increase(AreaIncreaseSettings{ AvoidanceType::Fast, 0, false, false, false, false }), missing_roof_layers(force_tips_to_roof ? dont_move_until : 0), skip_ovalisation(skip_ovalisation), + area(std::move(newArea)) { } - - explicit SupportElement(const SupportElement& elem, Polygons* newArea = nullptr) + explicit SupportElement(const SupportElement& elem, Polygons &&newArea) : // copy constructor with possibility to set a new area target_height(elem.target_height), target_position(elem.target_position), @@ -129,7 +129,7 @@ public: effective_radius_height(elem.effective_radius_height), to_buildplate(elem.to_buildplate), distance_to_top(elem.distance_to_top), - area(newArea != nullptr ? newArea : elem.area), + area(std::move(newArea)), result_on_layer(elem.result_on_layer), increased_to_model_radius(elem.increased_to_model_radius), to_model_gracious(elem.to_model_gracious), @@ -178,7 +178,7 @@ public: explicit SupportElement( const SupportElement& first, const SupportElement& second, size_t next_height, Point next_position, coord_t increased_to_model_radius, const TreeSupportSettings& config) : - next_position(next_position), next_height(next_height), area(nullptr), increased_to_model_radius(increased_to_model_radius), + next_position(next_position), next_height(next_height), increased_to_model_radius(increased_to_model_radius), use_min_xy_dist(first.use_min_xy_dist || second.use_min_xy_dist), supports_roof(first.supports_roof || second.supports_roof), dont_move_until(std::max(first.dont_move_until, second.dont_move_until)), can_use_safe_radius(first.can_use_safe_radius || second.can_use_safe_radius), missing_roof_layers(std::min(first.missing_roof_layers, second.missing_roof_layers)), skip_ovalisation(false) @@ -265,7 +265,7 @@ public: * \brief The resulting influence area. * Will only be set in the results of createLayerPathing, and will be nullptr inside! */ - Polygons* area; + Polygons area; /*! * \brief The resulting center point around which a circle will be drawn later. From 2b3d4b28684819da452791f389cb8489caf0fd00 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 26 Sep 2022 11:20:00 +0200 Subject: [PATCH 4/4] WIP TreeSupports: 1) Reworked the merging code to use an AABB tree for better locality. The old code sorted lexicographically, the new code splits bounding boxes by the longest axis. 2) Refactored to a functional style with better const correctness. 3) Reduced memory allocation pressure by replacing std::set with vectors, in place merging etc. --- src/libslic3r/AABBTreeIndirect.hpp | 8 +- src/libslic3r/PrintObject.cpp | 3 +- src/libslic3r/TreeModelVolumes.cpp | 25 +- src/libslic3r/TreeModelVolumes.hpp | 20 +- src/libslic3r/TreeSupport.cpp | 2026 ++++++++++++++++------------ src/libslic3r/TreeSupport.hpp | 1335 +++++++----------- 6 files changed, 1741 insertions(+), 1676 deletions(-) diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index f2847579c..3db74b8b9 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -83,6 +83,13 @@ public: // to split around. template void build(std::vector &&input) + { + this->build_modify_input(input); + input.clear(); + } + + template + void build_modify_input(std::vector &input) { if (input.empty()) clear(); @@ -91,7 +98,6 @@ public: m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node()); build_recursive(input, 0, 0, input.size() - 1); } - input.clear(); } const std::vector& nodes() const { return m_nodes; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f4b69bb81..7ea55a3a4 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2198,8 +2198,7 @@ void PrintObject::combine_infill() void PrintObject::_generate_support_material() { if (m_config.support_material_style == smsTree) { - TreeSupport tree_support; - tree_support.generateSupportAreas(*this); + fff_tree_support_generate(*this, std::function([this](){ this->throw_if_canceled(); })); } else { PrintObjectSupportMaterial support_material(this, m_slicing_params); support_material.generate(*this); diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index aa968d996..ad27e7871 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -23,7 +23,7 @@ #include #include -namespace Slic3r +namespace Slic3r::FFFTreeSupport { // or warning @@ -106,7 +106,10 @@ TreeModelVolumes::TreeModelVolumes( const PrintObject &print_object, const BuildVolume &build_volume, const coord_t max_move, const coord_t max_move_slow, size_t current_mesh_idx, - double progress_multiplier, double progress_offset, const std::vector& additional_excluded_areas) : +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + double progress_multiplier, double progress_offset, +#endif // SLIC3R_TREESUPPORTS_PROGRESS + const std::vector& additional_excluded_areas) : // -2 to avoid rounding errors m_max_move{ std::max(max_move - 2, 0) }, m_max_move_slow{ std::max(max_move_slow - 2, 0) }, #ifdef SLIC3R_TREESUPPORTS_PROGRESS @@ -161,7 +164,7 @@ TreeModelVolumes::TreeModelVolumes( m_min_resolution = std::min(m_min_resolution, data_pair.first.resolution); } - const TreeSupport::TreeSupportSettings config{ m_layer_outlines[m_current_outline_idx].first }; + const TreeSupportSettings config{ m_layer_outlines[m_current_outline_idx].first }; m_current_min_xy_dist = config.xy_min_distance; m_current_min_xy_dist_delta = config.xy_distance - m_current_min_xy_dist; assert(m_current_min_xy_dist_delta >= 0); @@ -206,7 +209,7 @@ void TreeModelVolumes::precalculate(const coord_t max_layer) // Get the config corresponding to one mesh that is in the current group. Which one has to be irrelevant. // Not the prettiest way to do this, but it ensures some calculations that may be a bit more complex // like inital layer diameter are only done in once. - TreeSupport::TreeSupportSettings config(m_layer_outlines[m_current_outline_idx].first); + TreeSupportSettings config(m_layer_outlines[m_current_outline_idx].first); { // calculate which radius each layer in the tip may have. @@ -297,7 +300,7 @@ const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerI return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Collision requested.", false); + tree_supports_show_error("Not precalculated Collision requested.", false); } const_cast(this)->calculateCollision(radius, layer_idx); return getCollision(orig_radius, layer_idx, min_xy_dist); @@ -312,7 +315,7 @@ const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerInde return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision holefree at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Holefree Collision requested.", false); + tree_supports_show_error("Not precalculated Holefree Collision requested.", false); } const_cast(this)->calculateCollisionHolefree({ radius, layer_idx }); return getCollisionHolefree(radius, layer_idx); @@ -336,10 +339,10 @@ const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerI if (m_precalculated) { if (to_model) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance to model at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Avoidance(to model) requested.", false); + tree_supports_show_error("Not precalculated Avoidance(to model) requested.", false); } else { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Avoidance(to buildplate) requested.", false); + tree_supports_show_error("Not precalculated Avoidance(to buildplate) requested.", false); } } const_cast(this)->calculateAvoidance({ radius, layer_idx }, ! to_model, to_model); @@ -357,7 +360,7 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L return (*result).get(); if (m_precalculated) { 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!"; - TreeSupport::showError("Not precalculated Placeable areas requested.", false); + tree_supports_show_error("Not precalculated Placeable areas requested.", false); } const_cast(this)->calculatePlaceables(radius, layer_idx); return getPlaceableAreas(orig_radius, layer_idx); @@ -380,7 +383,7 @@ const Polygons& TreeModelVolumes::getWallRestriction(const coord_t orig_radius, return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Wall restricions at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError( + tree_supports_show_error( min_xy_dist ? "Not precalculated Wall restriction of minimum xy distance requested )." : "Not precalculated Wall restriction requested )." @@ -774,4 +777,4 @@ coord_t TreeModelVolumes::ceilRadius(const coord_t radius) const return out; } -} +} // namespace Slic3r::FFFTreeSupport diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index f87204cf5..eea271bd4 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -22,11 +22,14 @@ namespace Slic3r { -using LayerIndex = int; - class BuildVolume; class PrintObject; +namespace FFFTreeSupport +{ + +using LayerIndex = int; + struct TreeSupportMeshGroupSettings { TreeSupportMeshGroupSettings() = default; explicit TreeSupportMeshGroupSettings(const PrintObject &print_object); @@ -194,15 +197,19 @@ class TreeModelVolumes public: TreeModelVolumes() = default; explicit TreeModelVolumes(const PrintObject &print_object, const BuildVolume &build_volume, - coord_t max_move, coord_t max_move_slow, size_t current_mesh_idx, double progress_multiplier, - double progress_offset, const std::vector &additional_excluded_areas = {}); + coord_t max_move, coord_t max_move_slow, size_t current_mesh_idx, +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + double progress_multiplier, + double progress_offset, +#endif // SLIC3R_TREESUPPORTS_PROGRESS + const std::vector &additional_excluded_areas = {}); TreeModelVolumes(TreeModelVolumes&&) = default; TreeModelVolumes& operator=(TreeModelVolumes&&) = default; TreeModelVolumes(const TreeModelVolumes&) = delete; TreeModelVolumes& operator=(const TreeModelVolumes&) = delete; - enum class AvoidanceType + enum class AvoidanceType : int8_t { Slow, FastSafe, @@ -605,6 +612,7 @@ private: #endif // SLIC3R_TREESUPPORTS_PROGRESS }; -} +} // namespace FFFTreeSupport +} // namespace Slic3r #endif //slic3r_TreeModelVolumes_hpp diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 1f4ead589..4892564b5 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -7,6 +7,7 @@ // CuraEngine is released under the terms of the AGPLv3 or higher. #include "TreeSupport.hpp" +#include "AABBTreeIndirect.hpp" #include "BuildVolume.hpp" #include "ClipperUtils.hpp" #include "EdgeGrid.hpp" @@ -38,6 +39,9 @@ namespace Slic3r { +namespace FFFTreeSupport +{ + enum class LineStatus { INVALID, @@ -165,9 +169,9 @@ static inline void clip_for_diff(const Polygon &src, const BoundingBox &bbox, Po static constexpr const auto tiny_area_threshold = sqr(scaled(0.001)); -static std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) +static std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) { - std::vector>> grouped_meshes; + std::vector>> grouped_meshes; //FIXME this is ugly, it does not belong here. for (size_t object_id = 0; object_id < print_object_ids.size(); ++ object_id) { @@ -175,7 +179,7 @@ static std::vector(0.1) that is the minimum line width - TreeSupport::TreeSupportSettings::soluble = true; + TreeSupportSettings::soluble = true; } size_t largest_printed_mesh_idx = 0; @@ -192,7 +196,7 @@ static std::vector &overhangs, const TreeSupport::TreeSupportSettings &config, const std::vector &object_ids, TreeModelVolumes &volumes) +[[nodiscard]] static LayerIndex precalculate(const Print &print, const std::vector &overhangs, const TreeSupportSettings &config, const std::vector &object_ids, TreeModelVolumes &volumes) { // calculate top most layer that is relevant for support LayerIndex max_layer = 0; @@ -349,176 +354,6 @@ void TreeSupport::showError(std::string message, bool critical) return max_layer; } -//FIXME this is an ugly wrapper interface for a single print object and a phony build volume. -void TreeSupport::generateSupportAreas(PrintObject& print_object) -{ - size_t idx = 0; - for (PrintObject *po : print_object.print()->objects()) { - if (po == &print_object) - break; - ++ idx; - } - this->generateSupportAreas(*print_object.print(), BuildVolume(Pointfs{ Vec2d{ -300., -300. }, Vec2d{ -300., +300. }, Vec2d{ +300., +300. }, Vec2d{ +300., -300. } }, 0.), { idx }); -} - -void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_volume, const std::vector &print_object_ids) -{ - g_showed_critical_error = false; - g_showed_performance_warning = false; - - std::vector>> grouped_meshes = group_meshes(print, print_object_ids); - if (grouped_meshes.empty()) - return; - - size_t counter = 0; - - // Process every mesh group. These groups can not be processed parallel, as the processing in each group is parallelized, and nested parallelization is disables and slow. - for (std::pair> &processing : grouped_meshes) - { - // process each combination of meshes - // this struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generateInitialAreas have knowledge of the existence of multiple meshes being processed. - //FIXME this is a copy - m_config = processing.first; - BOOST_LOG_TRIVIAL(info) << "Processing support tree mesh group " << counter + 1 << " of " << grouped_meshes.size() << " containing " << grouped_meshes[counter].second.size() << " meshes."; - auto t_start = std::chrono::high_resolution_clock::now(); -#if 0 - std::vector exclude(num_support_layers); - // get all already existing support areas and exclude them - tbb::parallel_for(tbb::blocked_range(0, num_support_layers), - [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - Polygons exlude_at_layer; - append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_bottom); - append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_roof); - for (auto part : storage.support.supportLayers[layer_idx].support_infill_parts) - append(exlude_at_layer, part.outline); - exclude[layer_idx] = union_(exlude_at_layer); - } - }); -#endif -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - m_progress_multiplier = 1.0 / double(m_grouped_meshes.size()); - m_progress_offset = counter == 0 ? 0 : TREE_PROGRESS_TOTAL * (double(counter) * m_progress_multiplier); -#endif // SLIC3R_TREESUPPORT_PROGRESS - PrintObject &print_object = *print.get_object(processing.second.front()); - m_volumes = TreeModelVolumes(print_object, build_volume, m_config.maximum_move_distance, m_config.maximum_move_distance_slow, processing.second.front(), m_progress_multiplier, m_progress_offset, /* additional_excluded_areas */{}); - - //FIXME generating overhangs just for the furst mesh of the group. - assert(processing.second.size() == 1); - std::vector overhangs = generate_overhangs(*print.get_object(processing.second.front())); - - // ### Precalculate avoidances, collision etc. - size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, m_volumes); - if (num_support_layers == 0) - continue; - - auto t_precalc = std::chrono::high_resolution_clock::now(); - - // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in drawAreas - std::vector> move_bounds(num_support_layers); - - // ### Place tips of the support tree - SupportGeneratorLayersPtr bottom_contacts(num_support_layers, nullptr); - SupportGeneratorLayersPtr top_contacts(num_support_layers, nullptr); - SupportGeneratorLayersPtr top_interface_layers(num_support_layers, nullptr); - SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr); - SupportGeneratorLayerStorage layer_storage; - - for (size_t mesh_idx : processing.second) - generateInitialAreas(*print.get_object(mesh_idx), overhangs, move_bounds, top_contacts, top_interface_layers, layer_storage); - auto t_gen = std::chrono::high_resolution_clock::now(); - - // ### Propagate the influence areas downwards. - createLayerPathing(move_bounds); - auto t_path = std::chrono::high_resolution_clock::now(); - - // ### Set a point in each influence area - createNodesFromArea(move_bounds); - auto t_place = std::chrono::high_resolution_clock::now(); - - // ### draw these points as circles - drawAreas(*print.get_object(processing.second.front()), overhangs, move_bounds, - bottom_contacts, top_contacts, intermediate_layers, layer_storage); - - auto t_draw = std::chrono::high_resolution_clock::now(); - auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); - auto dur_gen = 0.001 * std::chrono::duration_cast(t_gen - t_precalc).count(); - auto dur_path = 0.001 * std::chrono::duration_cast(t_path - t_gen).count(); - auto dur_place = 0.001 * std::chrono::duration_cast(t_place - t_path).count(); - auto dur_draw = 0.001 * std::chrono::duration_cast(t_draw - t_place).count(); - auto dur_total = 0.001 * std::chrono::duration_cast(t_draw - t_start).count(); - BOOST_LOG_TRIVIAL(info) << - "Total time used creating Tree support for the currently grouped meshes: " << dur_total << " ms. " - "Different subtasks:\nCalculating Avoidance: " << dur_pre_gen << " ms " - "Creating inital influence areas: " << dur_gen << " ms " - "Influence area creation: " << dur_path << "ms " - "Placement of Points in InfluenceAreas: " << dur_place << "ms " - "Drawing result as support " << dur_draw << " ms"; -// if (m_config.branch_radius==2121) -// BOOST_LOG_TRIVIAL(error) << "Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)"; - - for (auto &layer : move_bounds) - for (auto elem : layer) - delete elem; - - auto remove_undefined_layers = [](SupportGeneratorLayersPtr &layers) { - layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); - }; - remove_undefined_layers(bottom_contacts); - remove_undefined_layers(top_contacts); - remove_undefined_layers(intermediate_layers); - - // Produce the support G-code. - // Used by both classic and tree supports. - SupportParameters support_params(print_object); - support_params.with_sheath = true; - support_params.support_density = 0; - SupportGeneratorLayersPtr interface_layers, base_interface_layers; - SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); -#if 1 //#ifdef SLIC3R_DEBUG - SupportGeneratorLayersPtr layers_sorted = -#endif // SLIC3R_DEBUG - generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); - // Don't fill in the tree supports, make them hollow with just a single sheath line. - 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); - - #if 0 -//#ifdef SLIC3R_DEBUG - { - static int iRun = 0; - ++ iRun; - size_t layer_id = 0; - for (int i = 0; i < int(layers_sorted.size());) { - // Find the last layer with roughly the same print_z, find the minimum layer height of all. - // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - int j = i + 1; - coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - bool empty = layers_sorted[i]->polygons.empty(); - for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) - if (!layers_sorted[j]->polygons.empty()) - empty = false; - if (!empty) { - export_print_z_polygons_to_svg( - debug_out_path("support-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), - layers_sorted.data() + i, j - i); - export_print_z_polygons_and_extrusions_to_svg( - debug_out_path("support-w-fills-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), - layers_sorted.data() + i, j - i, - *print_object.support_layers()[layer_id]); - ++layer_id; - } - i = j; - } - } -#endif /* SLIC3R_DEBUG */ - - ++ counter; - } - -// storage.support.generated = true; -} - /*! * \brief Converts a Polygons object representing a line into the internal format. * @@ -526,9 +361,9 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo * \param layer_idx[in] The current layer. * \return All lines of the \p polylines object, with information for each point regarding in which avoidance it is currently valid in. */ -// Called by TreeSupport::generateInitialAreas() -[[nodiscard]] static LineInformations convertLinesToInternal( - const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, +// Called by generate_initial_areas() +[[nodiscard]] static LineInformations convert_lines_to_internal( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const Polylines &polylines, LayerIndex layer_idx) { const bool min_xy_dist = config.xy_distance > config.xy_min_distance; @@ -569,7 +404,7 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo * \param lines[in] The lines that will be converted. * \return All lines of the \p lines object as a Polygons object. */ -[[nodiscard]] static Polylines convertInternalToLines(LineInformations lines) +[[nodiscard]] static Polylines convert_internal_to_lines(LineInformations lines) { Polylines result; for (LineInformation line : lines) { @@ -583,16 +418,16 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo } /*! - * \brief Evaluates if a point has to be added now. Required for a splitLines call in generateInitialAreas. + * \brief Evaluates if a point has to be added now. Required for a split_lines call in generate_initial_areas(). * * \param current_layer[in] The layer on which the point lies, point and its status. * \return whether the point is valid. */ -[[nodiscard]] static bool evaluatePointForNextLayerFunction( - const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, +[[nodiscard]] static bool evaluate_point_for_next_layer_function( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, size_t current_layer, const std::pair &p) { - using AvoidanceType = TreeSupport::AvoidanceType; + using AvoidanceType = TreeModelVolumes::AvoidanceType; const bool min_xy_dist = config.xy_distance > config.xy_min_distance; if (! contains(volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FastSafe : AvoidanceType::Fast, false, min_xy_dist), p.first)) return true; @@ -613,7 +448,7 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo * \return A pair with which points are still valid in the first slot and which are not in the second slot. */ template -[[nodiscard]] static std::pair splitLines(const LineInformations &lines, EvaluatePointFn evaluatePoint) +[[nodiscard]] static std::pair split_lines(const LineInformations &lines, EvaluatePointFn evaluatePoint) { // assumes all Points on the current line are valid @@ -695,7 +530,7 @@ static std::optional> polyline_sample_next_point_at_dis * \param min_points[in] The amount of points that have to be placed. If not enough can be placed the distance will be reduced to place this many points. * \return A Polygons object containing the evenly spaced points. Does not represent an area, more a collection of points on lines. */ -[[nodiscard]] static Polylines ensureMaximumDistancePolyline(const Polylines &input, double distance, size_t min_points) +[[nodiscard]] static Polylines ensure_maximum_distance_polyline(const Polylines &input, double distance, size_t min_points) { Polylines result; for (Polyline part : input) { @@ -767,7 +602,7 @@ static std::optional> polyline_sample_next_point_at_dis // 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 << ") is smaller than 100"; - TreeSupport::showError("Encountered issue while placing tips. Some tips may be missing.", true); + tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing.", true); if (next_distance > 2 * current_distance) // This case should never happen, but better safe than sorry. break; @@ -799,7 +634,7 @@ static std::optional> polyline_sample_next_point_at_dis * * \return A Polygons object that represents the resulting infill lines. */ -[[nodiscard]] static Polylines generateSupportInfillLines( +[[nodiscard]] static Polylines generate_support_infill_lines( const Polygons &polygon, const SupportParameters &support_params, bool roof, LayerIndex layer_idx, coord_t support_infill_distance) { @@ -894,7 +729,7 @@ static std::optional> polyline_sample_next_point_at_dis * \param second[in] The second Polygon. * \return The union of both Polygons */ -[[nodiscard]] static Polygons safeUnion(const Polygons first, const Polygons second = Polygons()) +[[nodiscard]] static Polygons safe_union(const Polygons first, const Polygons second = {}) { // unionPolygons can slowly remove Polygons under certain circumstances, because of rounding issues (Polygons that have a thin area). // This does not cause a problem when actually using it on large areas, but as influence areas (representing centerpoints) can be very thin, this does occur so this ugly workaround is needed @@ -937,10 +772,10 @@ static std::optional> polyline_sample_next_point_at_dis * \param min_amount_offset[in] How many steps have to be done at least. As this uses round offset this increases the amount of vertices, which may be required if Polygons get very small. Required as arcTolerance is not exposed in offset, which should result with a similar result. * \return The resulting Polygons object. */ -[[nodiscard]] static Polygons safeOffsetInc(const Polygons& me, coord_t distance, const Polygons& collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset) +[[nodiscard]] static Polygons safe_offset_inc(const Polygons& me, coord_t distance, const Polygons& collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset) { bool do_final_difference = last_step_offset_without_check == 0; - Polygons ret = safeUnion(me); // ensure sane input + Polygons ret = safe_union(me); // ensure sane input // Trim the collision polygons with the region of interest for diff() efficiency. Polygons collision_trimmed_buffer; @@ -954,7 +789,7 @@ static std::optional> polyline_sample_next_point_at_dis 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!"; - TreeSupport::showError("Negative offset distance... How did you manage this ?", true); + tree_supports_show_error("Negative offset distance... How did you manage this ?", true); return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); } @@ -1033,14 +868,26 @@ inline SupportGeneratorLayer& layer_allocate( return layer_initialize(layer_storage.back(), layer_type, slicing_params, layer_idx); } -void TreeSupport::generateInitialAreas( +/*! + * \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) { + 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); @@ -1074,7 +921,7 @@ void TreeSupport::generateInitialAreas( //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 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). @@ -1093,7 +940,7 @@ void TreeSupport::generateInitialAreas( std::mutex mutex_layer_storage, mutex_movebounds; tbb::parallel_for(tbb::blocked_range(1, num_support_layers - z_distance_delta), - [this, &print_object, &overhangs, &mesh_config, &mesh_group_settings, &support_params, + [&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](const tbb::blocked_range &range) { @@ -1104,16 +951,16 @@ void TreeSupport::generateInitialAreas( Polygons relevant_forbidden; { const Polygons &relevant_forbidden_raw = (mesh_config.support_rests_on_model ? - (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, true, min_xy_dist) : - m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, min_xy_dist)) : - m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, false, min_xy_dist)); + (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, true, min_xy_dist) : + 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 generateSupportInfillLines(area, support_params, roof, layer_idx, support_infill_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 @@ -1129,7 +976,7 @@ void TreeSupport::generateInitialAreas( 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"; - TreeSupport::showError("Unable to add tip. Some overhang may not be supported correctly.", true); + tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly.", true); return; } Polygons circle{ base_circle }; @@ -1140,8 +987,26 @@ void TreeSupport::generateInitialAreas( 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); - move_bounds[insert_layer].emplace( - new SupportElement(dtt, insert_layer, p.first, to_bp, gracious, min_xy_dist, dont_move_until, roof, safe_radius, force_tip_to_roof, skip_ovalisation, std::move(circle))); + SupportElementState state; + state.target_height = insert_layer; + state.target_position = p.first; + state.next_position = p.first; + state.next_height = 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(new SupportElement(state, std::move(circle))); } } }; @@ -1157,7 +1022,7 @@ void TreeSupport::generateInitialAreas( Polygon roof_circle; for (Point corner : base_circle) roof_circle.points.emplace_back(p.first + corner * mesh_config.min_radius); - return !generateSupportInfillLines({ roof_circle }, mesh_config, true, insert_layer_idx - dtt_roof_tip, mesh_config.support_roof_line_distance).empty(); + 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 @@ -1166,10 +1031,11 @@ void TreeSupport::generateInitialAreas( { std::pair split = // keep all lines that are still valid on the next layer - splitLines(lines, [this, insert_layer_idx, dtt_roof_tip](const std::pair &p){ return evaluatePointForNextLayerFunction(m_volumes, m_config, insert_layer_idx - dtt_roof_tip, p); }); + 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 = splitLines(split.first, evaluateRoofWillGenerate); + split = split_lines(split.first, evaluateRoofWillGenerate); lines = std::move(split.first); append(points, split.second); // add all points that would not be valid @@ -1215,7 +1081,7 @@ void TreeSupport::generateInitialAreas( const Polygons &overhang_raw = overhangs[layer_idx + z_distance_delta]; overhang_regular = mesh_group_settings.support_offset == 0 ? overhang_raw : - safeOffsetInc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, mesh_config.min_radius * 1.75 + mesh_config.xy_min_distance, 0, 1); + 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); // 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 ? @@ -1235,13 +1101,13 @@ void TreeSupport::generateInitialAreas( circle_length_to_half_linewidth_change, extra_outset - extra_total_offset_acc); extra_total_offset_acc += offset_current_step; - const Polygons &raw_collision = m_volumes.getCollision(0, layer_idx, true); + 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, safeOffsetInc(overhang_regular, 1.5 * extra_total_offset_acc, raw_collision, offset_step, 0, 1)); + 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(safeOffsetInc(remaining_overhang, extra_total_offset_acc, raw_collision, offset_step, 0, 1), relevant_forbidden)); + overhang_regular = union_(overhang_regular, diff(safe_offset_inc(remaining_overhang, extra_total_offset_acc, raw_collision, offset_step, 0, 1), relevant_forbidden)); } // 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. ) @@ -1254,10 +1120,10 @@ void TreeSupport::generateInitialAreas( // 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 = ensureMaximumDistancePolyline(generateLines(remaining_overhang, false, layer_idx), mesh_config.min_radius, 1); + 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 = ensureMaximumDistancePolyline(to_polylines(remaining_overhang), connect_length, 3); + polylines = ensure_maximum_distance_polyline(to_polylines(remaining_overhang), connect_length, 3); for (const auto &line : polylines) { LineInformation res_line; for (Point p : line) @@ -1268,14 +1134,17 @@ void TreeSupport::generateInitialAreas( } 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 ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, true, min_xy_dist) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, min_xy_dist)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, false, min_xy_dist)); + const Polygons &relevant_forbidden_below = (mesh_config.support_rests_on_model ? + (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, true, min_xy_dist) : + 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 = splitLines(overhang_lines, evaluatePoint); // keep all lines that are invalid + 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 = convertLinesToInternal(m_volumes, m_config, convertInternalToLines(split.second), layer_idx - lag_ctr); + 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); @@ -1287,7 +1156,7 @@ void TreeSupport::generateInitialAreas( std::vector> overhang_processing; if (roof_enabled) { static constexpr const coord_t support_roof_offset = 0; - overhang_roofs = safeOffsetInc(overhangs[layer_idx + z_distance_delta], support_roof_offset, relevant_forbidden, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, 0, 1); + 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); @@ -1325,9 +1194,9 @@ void TreeSupport::generateInitialAreas( { const Polygons &forbidden_next_raw = mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? - m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::Fast, true, min_xy_dist) : - m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), min_xy_dist)) : - m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::Fast, false, min_xy_dist); + volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::Fast, true, min_xy_dist) : + 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); @@ -1338,10 +1207,12 @@ void TreeSupport::generateInitialAreas( 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 = convertLinesToInternal(m_volumes, m_config, - ensureMaximumDistancePolyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); - overhang_lines = splitLines(overhang_lines, - [this, layer_idx, dtt_before](const std::pair &p){ return evaluatePointForNextLayerFunction(m_volumes, m_config, layer_idx - dtt_before, p); }).first; + 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; } @@ -1370,7 +1241,7 @@ void TreeSupport::generateInitialAreas( SupportGeneratorLayer *&l = top_contacts[layer_idx - idx]; if (l == nullptr) l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), layer_idx - idx); - // will be unioned in finalizeInterfaceAndSupportAreas + // will be unioned in finalize_interface_and_support_areas() append(l->polygons, std::move(added_roofs[idx])); } } @@ -1379,7 +1250,8 @@ void TreeSupport::generateInitialAreas( // 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 = ensureMaximumDistancePolyline(generateLines(overhang_outset, dtt_roof != 0, layer_idx - layer_generation_dtt), dtt_roof == 0 ? mesh_config.min_radius / 2 : connect_length, 1); + 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(); @@ -1389,14 +1261,16 @@ void TreeSupport::generateInitialAreas( // 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 = ensureMaximumDistancePolyline( - 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)) ? + 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 = convertLinesToInternal(m_volumes, m_config, polylines, last_insert_layer); + 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()) { @@ -1413,7 +1287,7 @@ void TreeSupport::generateInitialAreas( }); } -static unsigned int moveInside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits::max()) +static unsigned int move_inside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits::max()) { Point ret = from; double bestDist2 = std::numeric_limits::max(); @@ -1517,320 +1391,82 @@ static unsigned int moveInside(const Polygons &polygons, Point &from, int distan } /*! - * \brief Merges Influence Areas if possible. + * \brief Checks if an influence area contains a valid subsection and returns the corresponding metadata and the new Influence area. * - * Branches which do overlap have to be merged. This helper merges all elements in input with the elements into reduced_new_layer. - * Elements in input_aabb are merged together if possible, while elements reduced_new_layer_aabb are not checked against each other. + * Calculates an influence areas of the layer below, based on the influence area of one element on the current layer. + * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in we would change our gracious or to_buildplate status the influence areas are instead increased by maximum_move_distance_slow. + * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. * - * \param reduced_aabb[in,out] The already processed elements. - * \param input_aabb[in] Not yet processed elements - * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. Value is the influence area where the center of a circle of support may be placed. - * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. - * Value is the influence area where the center of a circle of support may be placed. - * \param influence_areas[in] The influence areas without avoidance removed. - * \param insert_bp_areas[out] Elements to be inserted into the main dictionary after the Helper terminates. - * \param insert_model_areas[out] Elements to be inserted into the secondary dictionary after the Helper terminates. - * \param insert_influence[out] Elements to be inserted into the dictionary containing the largest possibly valid influence area (ignoring if the area may not be there because of avoidance) - * \param erase[out] Elements that should be deleted from the above dictionaries. - * \param layer_idx[in] The Index of the current Layer. + * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. + * + * \param settings[in] Which settings have to be used to check validity. + * \param layer_idx[in] Number of the current layer. + * \param parent[in] The metadata of the parents influence area. + * \param relevant_offset[in] The maximal possible influence area. No guarantee regarding validity with current layer collision required, as it is ensured in-function! + * \param to_bp_data[out] The part of the Influence area that can reach the buildplate. + * \param to_model_data[out] The part of the Influence area that do not have to reach the buildplate. This has overlap with new_layer_data. + * \param increased[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. + * \param overspeed[in] How much should the already offset area be offset again. Usually this is 0. + * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. + * \return A valid support element for the next layer regarding the calculated influence areas. Empty if no influence are can be created using the supplied influence area and settings. */ -static void mergeHelper( - const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, - std::map& reduced_aabb, std::map& input_aabb, - const std::unordered_map& to_bp_areas, const std::unordered_map& to_model_areas, - const std::map& influence_areas, - std::unordered_map& insert_bp_areas, std::unordered_map& insert_model_areas, - std::unordered_map& insert_influence, std::vector& erase, const LayerIndex layer_idx) +[[nodiscard]] static std::optional increase_single_area( + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + const AreaIncreaseSettings &settings, + const LayerIndex layer_idx, + const SupportElement &parent, + const Polygons &relevant_offset, + Polygons &to_bp_data, + Polygons &to_model_data, + Polygons &increased, + const coord_t overspeed, + const bool mergelayer) { - using SupportElement = TreeSupport::SupportElement; - - const bool first_merge_iteration = reduced_aabb.empty(); // If this is the first iteration, all elements in input have to be merged with each other - for (std::map::iterator influence_iter = input_aabb.begin(); influence_iter != input_aabb.end(); influence_iter++) - { - bool merged = false; - BoundingBox influence_aabb = influence_iter->second; - for (std::map::iterator reduced_check_iter = reduced_aabb.begin(); reduced_check_iter != reduced_aabb.end(); reduced_check_iter++) - { - // As every area has to be checked for overlaps with other areas, some fast heuristic is needed to abort early if clearly possible - // This is so performance critical that using a map lookup instead of the direct access of the cached AABBs can have a surprisingly large performance impact - BoundingBox aabb = reduced_check_iter->second; - if (aabb.overlap(influence_aabb)) { - if (!first_merge_iteration && input_aabb.count(reduced_check_iter->first)) - break; // Do not try to merge elements that already should have been merged. Done for potential performance improvement. - - bool merging_gracious_and_non_gracious = reduced_check_iter->first.to_model_gracious != influence_iter->first.to_model_gracious; // we do not want to merge a gracious with a non gracious area as bad placement could negatively impact the dependability of the whole subtree - bool merging_to_bp = reduced_check_iter->first.to_buildplate && influence_iter->first.to_buildplate; - bool merging_min_and_regular_xy = reduced_check_iter->first.use_min_xy_dist != influence_iter->first.use_min_xy_dist; // 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 drawArea generated circles, this assumption could be wrong. - coord_t increased_to_model_radius = 0; - size_t larger_to_model_dtt = 0; - - if (!merging_to_bp) { - coord_t infl_radius = config.getRadius(influence_iter->first); // get the real radius increase as the user does not care for the collision model. - coord_t redu_radius = config.getRadius(reduced_check_iter->first); - if (reduced_check_iter->first.to_buildplate != influence_iter->first.to_buildplate) { - if (reduced_check_iter->first.to_buildplate) { - if (infl_radius < redu_radius) - increased_to_model_radius = influence_iter->first.increased_to_model_radius + redu_radius - infl_radius; - } else { - if (infl_radius > redu_radius) - increased_to_model_radius = reduced_check_iter->first.increased_to_model_radius + infl_radius - redu_radius; - } - } - larger_to_model_dtt = std::max(influence_iter->first.distance_to_top, reduced_check_iter->first.distance_to_top); - } - - // 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 (merging_min_and_regular_xy || merging_gracious_and_non_gracious || increased_to_model_radius > config.max_to_model_radius_increase || (!merging_to_bp && larger_to_model_dtt < config.min_dtt_to_model && !reduced_check_iter->first.supports_roof && !influence_iter->first.supports_roof)) - continue; - - Polygons relevant_infl; - Polygons relevant_redu; - if (merging_to_bp) { - relevant_infl = to_bp_areas.count(influence_iter->first) ? to_bp_areas.at(influence_iter->first) : Polygons(); // influence_iter->first is a new element => not required to check if it was changed - relevant_redu = insert_bp_areas.count(reduced_check_iter->first) ? insert_bp_areas[reduced_check_iter->first] : (to_bp_areas.count(reduced_check_iter->first) ? to_bp_areas.at(reduced_check_iter->first) : Polygons()); - } else { - relevant_infl = to_model_areas.count(influence_iter->first) ? to_model_areas.at(influence_iter->first) : Polygons(); - relevant_redu = insert_model_areas.count(reduced_check_iter->first) ? insert_model_areas[reduced_check_iter->first] : (to_model_areas.count(reduced_check_iter->first) ? to_model_areas.at(reduced_check_iter->first) : Polygons()); - } - - const bool red_bigger = config.getCollisionRadius(reduced_check_iter->first) > config.getCollisionRadius(influence_iter->first); - std::pair smaller_rad = red_bigger ? std::pair(influence_iter->first, relevant_infl) : std::pair(reduced_check_iter->first, relevant_redu); - std::pair bigger_rad = red_bigger ? std::pair(reduced_check_iter->first, relevant_redu) : std::pair(influence_iter->first, relevant_infl); - const coord_t real_radius_delta = std::abs(config.getRadius(bigger_rad.first) - config.getRadius(smaller_rad.first)); - const coord_t smaller_collision_radius = config.getCollisionRadius(smaller_rad.first); - - // the area of the bigger radius is used to ensure correct placement regarding the relevant avoidance, so if that would change an invalid area may be created - if (!bigger_rad.first.can_use_safe_radius && smaller_rad.first.can_use_safe_radius) - continue; - - // 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 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. - bool use_min_radius = bigger_rad.first.use_min_xy_dist && smaller_rad.first.use_min_xy_dist; - - // The idea is that the influence area with the smaller collision radius is increased by the radius difference. - // If this area has any intersections with the influence area of the larger collision radius, a branch (of the larger collision radius) placed in this intersection, has already engulfed the branch of the smaller collision radius. - // Because of this a merge may happen even if the influence areas (that represent possible center points of branches) do not intersect yet. - // Remember that collision radius <= real radius as otherwise this assumption would be false. - Polygons small_rad_increased_by_big_minus_small = safeOffsetInc(smaller_rad.second, real_radius_delta, volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); // -3 avoids possible rounding errors - Polygons intersect = intersection(small_rad_increased_by_big_minus_small, bigger_rad.second); - - if (area(intersect) > 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) - if (area(offset(intersect, scaled(-0.025), jtMiter, 1.2)) <= tiny_area_threshold) // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). While 25 was guessed as enough, i did not have reason to change it. - continue; - - // Do the actual merge now that the branches are confirmed to be able to intersect. - - // calculate which point is closest to the point of the last merge (or tip center if no merge above it has happened) - // used at the end to estimate where to best place the branch on the bottom most layer - // could be replaced with a random point inside the new area - Point new_pos = reduced_check_iter->first.next_position; - if (! contains(intersect, new_pos)) - moveInside(intersect, new_pos); - - if (increased_to_model_radius == 0) - increased_to_model_radius = std::max(reduced_check_iter->first.increased_to_model_radius, influence_iter->first.increased_to_model_radius); - - SupportElement key(reduced_check_iter->first, influence_iter->first, layer_idx - 1, new_pos, increased_to_model_radius, config); - - Polygons intersect_influence; - Polygons infl_small = insert_influence.count(smaller_rad.first) ? insert_influence[smaller_rad.first] : (influence_areas.count(smaller_rad.first) ? influence_areas.at(smaller_rad.first) : Polygons()); - Polygons infl_big = insert_influence.count(bigger_rad.first) ? insert_influence[bigger_rad.first] : (influence_areas.count(bigger_rad.first) ? influence_areas.at(bigger_rad.first) : Polygons()); - Polygons small_rad_increased_by_big_minus_small_infl = safeOffsetInc(infl_small, real_radius_delta, volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); - intersect_influence = intersection(small_rad_increased_by_big_minus_small_infl, infl_big); // if the one with the bigger radius with the lower radius removed overlaps we can merge - intersect_influence = safeUnion(intersect_influence, intersect); // Rounding errors again. Do not ask me where or why. - - Polygons intersect_sec; - if (merging_to_bp && config.support_rests_on_model) { - if (key.to_model_gracious) { - Polygons sec_small = insert_model_areas.count(smaller_rad.first) ? insert_model_areas[smaller_rad.first] : (to_model_areas.count(smaller_rad.first) ? to_model_areas.at(smaller_rad.first) : Polygons()); - Polygons sec_big = insert_model_areas.count(bigger_rad.first) ? insert_model_areas[bigger_rad.first] : (to_model_areas.count(bigger_rad.first) ? to_model_areas.at(bigger_rad.first) : Polygons()); - Polygons small_rad_increased_by_big_minus_small_sec = safeOffsetInc(sec_small, real_radius_delta, volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); - intersect_sec = intersection(small_rad_increased_by_big_minus_small_sec, sec_big); // if the one with the bigger radius with the lower radius removed overlaps we can merge - intersect_influence = safeUnion(intersect_influence, intersect_sec); // still rounding errors - } else - intersect_sec = intersect_influence; - } - - // remove the now merged elements from all buckets, as they do not exist anymore in their old form - insert_bp_areas.erase(reduced_check_iter->first); - insert_bp_areas.erase(influence_iter->first); - insert_model_areas.erase(reduced_check_iter->first); - insert_model_areas.erase(influence_iter->first); - insert_influence.erase(reduced_check_iter->first); - insert_influence.erase(influence_iter->first); - - (merging_to_bp ? insert_bp_areas : insert_model_areas).emplace(key, intersect); - if (merging_to_bp && config.support_rests_on_model) - insert_model_areas.emplace(key, intersect_sec); - insert_influence.emplace(key, intersect_influence); - - erase.emplace_back(reduced_check_iter->first); - erase.emplace_back(influence_iter->first); - Polygons merge = diff_clipped(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound, scaled(0.01)), volumes.getCollision(0, layer_idx - 1, false)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. - - reduced_aabb.erase(reduced_check_iter->first); // this invalidates reduced_check_iter - reduced_aabb.emplace(key, get_extents(merge)); - - merged = true; - break; - } - } - } - - if (!merged) - reduced_aabb[influence_iter->first] = influence_aabb; - } -} - -/*! - * \brief Merges Influence Areas if possible. - * - * Branches which do overlap have to be merged. This manages the helper and uses a divide and conquer approach to parallelize this problem. This parallelization can at most accelerate the merging by a factor of 2. - * - * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. - * Value is the influence area where the center of a circle of support may be placed. - * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. - * Value is the influence area where the center of a circle of support may be placed. - * \param influence_areas[in] The Elements of the current Layer without avoidances removed. This is the largest possible influence area for this layer. - * Value is the influence area where the center of a circle of support may be placed. - * \param layer_idx[in] The current layer. - */ -static void mergeInfluenceAreas( - const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, - std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, LayerIndex layer_idx) -{ - using SupportElement = TreeSupport::SupportElement; - /* - * Idea behind this is that the calculation of merges can be accelerated a bit using divide and conquer: - * If two groups of areas are already merged, only all elements in group 2 have to be merged into group one. - * This can only accelerate by factor 2 (as half the work is merging the last two groups). - * The actual merge logic is found in mergeHelper. This function only manages parallelization of different mergeHelper calls. - */ - - const size_t input_size = influence_areas.size(); - if (input_size == 0) - return; - - size_t num_threads = std::max(size_t(1), size_t(std::thread::hardware_concurrency())); // For some reason hardware concurrency can return 0; - constexpr int min_elements_per_bucket = 2; - - // max_bucket_count is input_size/min_elements_per_bucket round down to the next 2^n. - // The rounding to 2^n is to ensure improved performance, as every iteration two buckets will be merged, halving the amount of buckets. - // If halving would cause an uneven count, e.g. 3 Then bucket 0 and 1 would have to be merged, and in the next iteration the last remaining buckets. This is assumed to not be optimal performance-wise. - const size_t max_bucket_count = std::pow(2, std::floor(std::log(round_up_divide(input_size, min_elements_per_bucket)))); - int bucket_count = std::min(max_bucket_count, num_threads); // do not use more buckets than available threads. - - // To achieve that every element in a bucket is already correctly merged with other elements in this bucket - // an extra empty bucket is created for each bucket, and the elements are merged into the empty one. - // Each thread will then process two buckets by merging all elements in the second bucket into the first one as mergeHelper will disable not trying to merge elements from the same bucket in this case. - std::vector> buckets_area(2 * bucket_count); - std::vector> buckets_aabb(2 * bucket_count); - - size_t position = 0, counter = 0; - const size_t over_elements = input_size % bucket_count; - const size_t elements_per_step = input_size / bucket_count; - - // split the data in x parts to be able to divide and conquer - // the first "over_elements" of buckets gets elements_per_step+1 elements - for (std::map::iterator iter = influence_areas.begin(); iter != influence_areas.end(); ++ iter) { - buckets_area[position * 2 + 1].emplace(iter->first, iter->second); // only use every second bucket beginning with 1 as this makes the parallel call later easier as we assume everything in a bucket i%2==0 is already processed - ++ counter; - if ((counter == elements_per_step && position >= over_elements) || counter > elements_per_step) { - position++; - counter = 0; - } - } - - // precalculate the AABBs from the influence areas. - tbb::parallel_for(tbb::blocked_range(0, bucket_count), - [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - // +=2 as in the beginning only uneven buckets will be filled - size_t bucket_idx = 2 * idx + 1; - for (const std::pair& input_pair : buckets_area[bucket_idx]) - buckets_aabb[bucket_idx].emplace(input_pair.first, get_extents(input_pair.second).inflated(config.getRadius(input_pair.first))); - } - }); - - while (buckets_area.size() > 1) { - // Some temporary storage, of elements that have to be inserted or removed from the background storage. Only one per two buckets required - std::vector> insert_main(buckets_area.size() / 2); - std::vector> insert_secondary(buckets_area.size() / 2); - std::vector> insert_influence(buckets_area.size() / 2); - std::vector> erase(buckets_area.size() / 2); - - tbb::parallel_for(tbb::blocked_range(0, buckets_area.size() / 2), - [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - const size_t bucket_pair_idx = idx * 2; - // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets - mergeHelper(volumes, config, buckets_aabb[bucket_pair_idx], buckets_aabb[bucket_pair_idx + 1], to_bp_areas, to_model_areas, influence_areas, insert_main[bucket_pair_idx / 2], insert_secondary[bucket_pair_idx / 2], insert_influence[bucket_pair_idx / 2], erase[bucket_pair_idx / 2], layer_idx); - // clear now irrelevant max_bucket_count, and delete them later - buckets_area[bucket_pair_idx + 1].clear(); - buckets_aabb[bucket_pair_idx + 1].clear(); - } - }); - - for (size_t i = 0; i + 1 < buckets_area.size(); i += 2) { - for (SupportElement &del : erase[i / 2]) { - to_bp_areas.erase(del); - to_model_areas.erase(del); - influence_areas.erase(del); - } - for (const std::pair &tup : insert_main[i / 2]) - to_bp_areas.emplace(std::move(tup)); - for (const std::pair &tup : insert_secondary[i / 2]) - to_model_areas.emplace(std::move(tup)); - for (const std::pair &tup : insert_influence[i / 2]) - influence_areas.emplace(std::move(tup)); - } - - buckets_area.erase(std::remove_if(buckets_area.begin(), buckets_area.end(), [&](const std::map &x) { return x.empty(); }), buckets_area.end()); - buckets_aabb.erase(std::remove_if(buckets_aabb.begin(), buckets_aabb.end(), [&](const std::map &x) { return x.empty(); }), buckets_aabb.end()); - } -} - - -std::optional TreeSupport::increaseSingleArea(AreaIncreaseSettings settings, LayerIndex layer_idx, SupportElement* parent, const Polygons& relevant_offset, Polygons& to_bp_data, Polygons& to_model_data, Polygons& increased, const coord_t overspeed, const bool mergelayer) -{ - SupportElement current_elem(parent); // also increases DTT by one + SupportElementState current_elem{ SupportElementState::propagate_down(parent.state) }; Polygons check_layer_data; if (settings.increase_radius) current_elem.effective_radius_height += 1; - coord_t radius = m_config.getCollisionRadius(current_elem); + coord_t radius = config.getCollisionRadius(current_elem); if (settings.move) { increased = relevant_offset; if (overspeed > 0) { - const coord_t safe_movement_distance = (current_elem.use_min_xy_dist ? m_config.xy_min_distance : m_config.xy_distance) + (std::min(m_config.z_distance_top_layers, m_config.z_distance_bottom_layers) > 0 ? m_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 = safeOffsetInc(increased, overspeed, m_volumes.getWallRestriction(m_config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist), safe_movement_distance, safe_movement_distance + radius, 1); + 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(config.getCollisionRadius(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)); } else // if no movement is done the areas keep parent area as no move == offset(0) - increased = parent->area; + increased = parent.influence_area; if (mergelayer || current_elem.to_buildplate) { - to_bp_data = safeUnion(diff_clipped(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); + to_bp_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); 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 " << current_elem.target_height << " with radius " << radius; + 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; } } - if (m_config.support_rests_on_model) { + if (config.support_rests_on_model) { if (mergelayer || current_elem.to_model_gracious) - to_model_data = safeUnion(diff_clipped(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); + to_model_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); 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 " << current_elem.target_height << " with radius " << radius; + 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 - to_model_data = safeUnion(diff_clipped(increased, m_volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance))); + to_model_data = safe_union(diff_clipped(increased, volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance))); } } @@ -1838,83 +1474,155 @@ std::optional TreeSupport::increaseSingleArea(AreaI if (settings.increase_radius && area(check_layer_data) > tiny_area_threshold) { auto validWithRadius = [&](coord_t next_radius) { - if (m_volumes.ceilRadius(next_radius, settings.use_min_distance) <= m_volumes.ceilRadius(radius, settings.use_min_distance)) + if (volumes.ceilRadius(next_radius, settings.use_min_distance) <= volumes.ceilRadius(radius, settings.use_min_distance)) return true; Polygons to_bp_data_2; if (current_elem.to_buildplate) - to_bp_data_2 = diff_clipped(increased, m_volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)); // regular union as output will not be used later => this area should always be a subset of the safeUnion one (i think) + // regular union as output will not be used later => this area should always be a subset of the safe_union one (i think) + 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 (m_config.support_rests_on_model && !current_elem.to_buildplate) + if (config.support_rests_on_model && !current_elem.to_buildplate) to_model_data_2 = diff_clipped(increased, current_elem.to_model_gracious ? - m_volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : - m_volumes.getCollision(next_radius, layer_idx - 1, settings.use_min_distance)); + 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; return area(check_layer_data_2) > tiny_area_threshold; }; - coord_t ceil_radius_before = m_volumes.ceilRadius(radius, settings.use_min_distance); + coord_t ceil_radius_before = volumes.ceilRadius(radius, settings.use_min_distance); - if (m_config.getCollisionRadius(current_elem) < m_config.increase_radius_until_radius && m_config.getCollisionRadius(current_elem) < m_config.getRadius(current_elem)) { - coord_t target_radius = std::min(m_config.getRadius(current_elem), m_config.increase_radius_until_radius); - coord_t current_ceil_radius = m_volumes.getRadiusNextCeil(radius, settings.use_min_distance); + if (config.getCollisionRadius(current_elem) < config.increase_radius_until_radius && config.getCollisionRadius(current_elem) < config.getRadius(current_elem)) { + coord_t target_radius = std::min(config.getRadius(current_elem), config.increase_radius_until_radius); + coord_t current_ceil_radius = volumes.getRadiusNextCeil(radius, settings.use_min_distance); - while (current_ceil_radius < target_radius && validWithRadius(m_volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance))) - current_ceil_radius = m_volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance); + 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 && m_config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= current_ceil_radius && m_config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= m_config.getRadius(current_elem)) - resulting_eff_dtt++; + 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) <= config.getRadius(current_elem)) + ++ resulting_eff_dtt; current_elem.effective_radius_height = resulting_eff_dtt; } - radius = m_config.getCollisionRadius(current_elem); + radius = config.getCollisionRadius(current_elem); - const coord_t foot_radius_increase = m_config.branch_radius * (std::max(m_config.diameter_scale_bp_radius - m_config.diameter_angle_scale_factor, 0.0)); - // 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(m_config.recommendedMinRadius(layer_idx - 1) - m_config.getRadius(current_elem)) / foot_radius_increase); + const coord_t foot_radius_increase = config.branch_radius * (std::max(config.diameter_scale_bp_radius - config.diameter_angle_scale_factor, 0.0)); + // 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) - config.getRadius(current_elem)) / foot_radius_increase); //FIXME bool increase_bp_foot = planned_foot_increase > 0 && current_elem.to_buildplate; // bool increase_bp_foot = false; - if (increase_bp_foot && m_config.getRadius(current_elem) >= m_config.branch_radius && m_config.getRadius(current_elem) >= m_config.increase_radius_until_radius) - if (validWithRadius(m_config.getRadius(current_elem.effective_radius_height, current_elem.elephant_foot_increases + planned_foot_increase))) { + if (increase_bp_foot && config.getRadius(current_elem) >= config.branch_radius && config.getRadius(current_elem) >= config.increase_radius_until_radius) + if (validWithRadius(config.getRadius(current_elem.effective_radius_height, current_elem.elephant_foot_increases + planned_foot_increase))) { current_elem.elephant_foot_increases += planned_foot_increase; - radius = m_config.getCollisionRadius(current_elem); + radius = config.getCollisionRadius(current_elem); } - if (ceil_radius_before != m_volumes.ceilRadius(radius, settings.use_min_distance)) { + if (ceil_radius_before != volumes.ceilRadius(radius, settings.use_min_distance)) { if (current_elem.to_buildplate) - to_bp_data = safeUnion(diff_clipped(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); - if (m_config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) - to_model_data = safeUnion(diff_clipped(increased, + 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 ? - m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : - m_volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance) + 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 " << m_volumes.ceilRadius(m_config.getCollisionRadius(current_elem), settings.use_min_distance); - TreeSupport::showError("Area lost catching up radius. May not cause visible malformation.", true); + BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << + volumes.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance); + tree_supports_show_error("Area lost catching up radius. May not cause visible malformation.", true); } } } - return area(check_layer_data) > tiny_area_threshold ? std::optional(current_elem) : std::optional(); + return area(check_layer_data) > tiny_area_threshold ? std::optional(current_elem) : std::optional(); } -void TreeSupport::increaseAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, std::vector& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, const bool mergelayer) +struct SupportElementInfluenceAreas { + // All influence areas: both to build plate and model. + Polygons influence_areas; + // Influence areas just to build plate. + Polygons to_bp_areas; + // Influence areas just to model. + Polygons to_model_areas; + + void clear() { + this->influence_areas.clear(); + this->to_bp_areas.clear(); + this->to_model_areas.clear(); + } +}; + +struct SupportElementMerging { + SupportElementState state; + /*! + * \brief All elements in the layer above the current one that are supported by this element + */ + boost::container::small_vector parents; + + SupportElementInfluenceAreas areas; + // Bounding box of all influence areas. + Eigen::AlignedBox bbox_data; + + 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) + { 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. + // Not needed, thus zero is returned. + static size_t idx() { return 0; } +}; + +// #define TREESUPPORT_DEBUG_SVG + +/*! + * \brief Increases influence areas as far as required. + * + * Calculates influence areas of the layer below, based on the influence areas of the current layer. + * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in it would change the gracious or to_buildplate status, the influence areas are instead increased by maximum_move_distance. + * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. + * + * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. + * + * \param to_bp_areas[out] Influence areas that can reach the buildplate + * \param to_model_areas[out] Influence areas that do not have to reach the buildplate. This has overlap with new_layer_data, as areas that can reach the buildplate are also considered valid areas to the model. + * This redundancy is required if a to_buildplate influence area is allowed to merge with a to model influence area. + * \param influence_areas[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. + * \param bypass_merge_areas[out] Influence areas ready to be added to the layer below that do not need merging. + * \param last_layer[in] Influence areas of the current layer. + * \param layer_idx[in] Number of the current layer. + * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. + */ +static void increase_areas_one_layer( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, + std::vector &merging_areas, const LayerIndex layer_idx, const bool mergelayer) { - std::mutex critical_sections; - tbb::parallel_for(tbb::blocked_range(0, last_layer.size()), + using AvoidanceType = TreeModelVolumes::AvoidanceType; + + tbb::parallel_for(tbb::blocked_range(0, merging_areas.size()), [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - SupportElement* parent = last_layer[idx]; + for (size_t merging_area_idx = range.begin(); merging_area_idx < range.end(); ++ merging_area_idx) { + SupportElementMerging &merging_area = merging_areas[merging_area_idx]; + assert(merging_area.parents.size() == 1); + SupportElement &parent = *merging_area.parents.front(); + SupportElementState elem = SupportElementState::propagate_down(parent.state); + 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(config.getCollisionRadius(parent.state), layer_idx, parent.state.use_min_xy_dist); - SupportElement elem(parent); // also increases dtt - - const Polygons &wall_restriction = m_volumes.getWallRestriction(m_config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist); // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. +#ifdef TREESUPPORT_DEBUG_SVG + SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-%d-%ld.svg", layer_idx, int(merging_area_idx)), + { { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } }, + { { union_ex(parent.influence_area) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif // TREESUPPORT_DEBUG_SVG Polygons to_bp_data, to_model_data; - coord_t radius = m_config.getCollisionRadius(elem); + coord_t radius = config.getCollisionRadius(elem); // When the radius increases, the outer "support wall" of the branch will have been moved farther away from the center (as this is the definition of radius). // As it is not specified that the support_tree_angle has to be one of the center of the branch, it is here seen as the smaller angle of the outer wall of the branch, to the outer wall of the same branch one layer above. @@ -1923,9 +1631,9 @@ void TreeSupport::increaseAreas(std::unordered_map& to coord_t extra_speed = 5; // The extra speed is added to both movement distances. Also move 5 microns faster than allowed to avoid rounding errors, this may cause issues at VERY VERY small layer heights. coord_t extra_slow_speed = 0; // Only added to the slow movement distance. - const coord_t ceiled_parent_radius = m_volumes.ceilRadius(m_config.getCollisionRadius(*parent), parent->use_min_xy_dist); - coord_t projected_radius_increased = m_config.getRadius(parent->effective_radius_height + 1, parent->elephant_foot_increases); - coord_t projected_radius_delta = projected_radius_increased - m_config.getCollisionRadius(*parent); + const coord_t ceiled_parent_radius = volumes.ceilRadius(config.getCollisionRadius(parent.state), parent.state.use_min_xy_dist); + coord_t projected_radius_increased = config.getRadius(parent.state.effective_radius_height + 1, parent.state.elephant_foot_increases); + coord_t projected_radius_delta = projected_radius_increased - config.getCollisionRadius(parent.state); // When z distance is more than one layer up and down the Collision used to calculate the wall restriction will always include the wall (and not just the xy_min_distance) of the layer above and below like this (d = blocked area because of z distance): /* @@ -1934,24 +1642,30 @@ void TreeSupport::increaseAreas(std::unordered_map& to * layer z-1:dddddxxxxxxxxxx * For more detailed visualisation see calculateWallRestrictions */ - const coord_t safe_movement_distance = (elem.use_min_xy_dist ? m_config.xy_min_distance : m_config.xy_distance) + (std::min(m_config.z_distance_top_layers, m_config.z_distance_bottom_layers) > 0 ? m_config.min_feature_size : 0); - if (ceiled_parent_radius == m_volumes.ceilRadius(projected_radius_increased, parent->use_min_xy_dist) || projected_radius_increased < m_config.increase_radius_until_radius) + 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) || + 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; else // if a guaranteed radius increase is not possible, only increase the slow speed - extra_slow_speed += std::min(projected_radius_delta, (m_config.maximum_move_distance + extra_speed) - (m_config.maximum_move_distance_slow + extra_slow_speed)); // Ensure that the slow movement distance can not become larger than the fast one. + // 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 (m_config.layer_start_bp_radius > layer_idx && m_config.recommendedMinRadius(layer_idx - 1) < m_config.getRadius(elem.effective_radius_height + 1, elem.elephant_foot_increases)) { + 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 == m_volumes.ceilRadius(m_config.getRadius(parent->effective_radius_height + 1, parent->elephant_foot_increases + 1), parent->use_min_xy_dist)) - extra_speed += m_config.branch_radius * m_config.diameter_scale_bp_radius; + 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)) + extra_speed += config.branch_radius * config.diameter_scale_bp_radius; else - extra_slow_speed += std::min(coord_t(m_config.branch_radius * m_config.diameter_scale_bp_radius), m_config.maximum_move_distance - (m_config.maximum_move_distance_slow + extra_slow_speed)); + extra_slow_speed += std::min(coord_t(config.branch_radius * config.diameter_scale_bp_radius), + config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed)); } - const coord_t fast_speed = m_config.maximum_move_distance + extra_speed; - const coord_t slow_speed = m_config.maximum_move_distance_slow + extra_speed + extra_slow_speed; + const coord_t fast_speed = config.maximum_move_distance + extra_speed; + const coord_t slow_speed = config.maximum_move_distance_slow + extra_speed + extra_slow_speed; Polygons offset_slow, offset_fast; @@ -1970,54 +1684,53 @@ void TreeSupport::increaseAreas(std::unordered_map& to } }; - const bool parent_moved_slow = elem.last_area_increase.increase_speed < m_config.maximum_move_distance; + const bool parent_moved_slow = elem.last_area_increase.increase_speed < config.maximum_move_distance; const bool avoidance_speed_mismatch = parent_moved_slow && elem.last_area_increase.type != AvoidanceType::Slow; - 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 >= m_config.tip_layers || parent_moved_slow)) - { + 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 < m_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 < m_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); + 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); } // branch may still go though a hole, so a check has to be done whether the hole was already passed, and the regular avoidance can be used. - if (!elem.can_use_safe_radius) - { - // if the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. + if (!elem.can_use_safe_radius) { + // if the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. + // This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. // order.emplace_back(AvoidanceType::Slow,!increase_radius,no_error,!use_min_radius,move); insertSetting({ AvoidanceType::Slow, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we go through the hole - // in many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. This CAN cause a branch to go though a hole it otherwise may have avoided. - if (elem.distance_to_top < round_up_divide(m_config.tip_layers, size_t(2))) + // in many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. + // This CAN cause a branch to go though a hole it otherwise may have avoided. + if (elem.distance_to_top < round_up_divide(config.tip_layers, size_t(2))) insertSetting({ AvoidanceType::Fast, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); insertSetting({ AvoidanceType::FastSafe, fast_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we manage to avoid the hole insertSetting({ AvoidanceType::FastSafe, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); - } - else - { + } 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, which looks similar to a layer shift and can reduce stability. + // 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 - if (elem.distance_to_top < m_config.tip_layers) - { + if (elem.distance_to_top < config.tip_layers) insertSetting({ AvoidanceType::FastSafe, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); - } insertSetting({ AvoidanceType::FastSafe, fast_speed, increase_radius, no_error, !use_min_radius, move }, true); // b insertSetting({ AvoidanceType::FastSafe, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); } - if (elem.use_min_xy_dist) - { + if (elem.use_min_xy_dist) { std::vector new_order; - // if the branch currently has to use min_xy_dist check if the configuration would also be valid with the regular xy_distance before checking with use_min_radius (Only happens when Support Distance priority is z overrides xy ) - for (AreaIncreaseSettings settings : order) - { + // if the branch currently has to use min_xy_dist check if the configuration would also be valid + // with the regular xy_distance before checking with use_min_radius (Only happens when Support Distance priority is z overrides xy ) + for (AreaIncreaseSettings settings : order) { new_order.emplace_back(settings); new_order.push_back({ settings.type, settings.increase_speed, settings.increase_radius, settings.no_error, use_min_radius, settings.move }); } order = new_order; } - if (elem.to_buildplate || (elem.to_model_gracious && intersection(parent->area, m_volumes.getPlaceableAreas(radius, layer_idx)).empty())) // error case - { + if (elem.to_buildplate || (elem.to_model_gracious && intersection(parent.influence_area, volumes.getPlaceableAreas(radius, layer_idx)).empty())) { + // error case // it is normal that we wont be able to find a new area at some point in time if we wont be able to reach layer 0 aka have to connect with the model insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move }, true); } @@ -2028,32 +1741,45 @@ void TreeSupport::increaseAreas(std::unordered_map& to Polygons inc_wo_collision; // Check whether it is faster to calculate the area increased with the fast speed independently from the slow area, or time could be saved by reusing the slow area to calculate the fast one. // Calculated by comparing the steps saved when calcualting idependently with the saved steps when not. - bool offset_independant_faster = (radius / safe_movement_distance - (((m_config.maximum_move_distance + extra_speed) < (radius + safe_movement_distance)) ? 1 : 0)) > (round_up_divide((extra_speed + extra_slow_speed + m_config.maximum_move_distance_slow), safe_movement_distance)); - for (AreaIncreaseSettings settings : order) - { + bool offset_independant_faster = radius / safe_movement_distance - int(config.maximum_move_distance + extra_speed < radius + safe_movement_distance) > + round_up_divide((extra_speed + extra_slow_speed + config.maximum_move_distance_slow), safe_movement_distance); + for (const AreaIncreaseSettings &settings : order) { if (settings.move) { - if (offset_slow.empty() && (settings.increase_speed == slow_speed || !offset_independant_faster)) { + 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 // was never made for precision in the single digit micron range. - offset_slow = safeOffsetInc(parent->area, extra_speed + extra_slow_speed + m_config.maximum_move_distance_slow, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2); + 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)), + { { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } }, + { { union_ex(offset_slow) }, { "offset_slow", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif // TREESUPPORT_DEBUG_SVG } - - if ((settings.increase_speed != slow_speed) && offset_fast.empty()) { + if (offset_fast.empty() && settings.increase_speed != slow_speed) { if (offset_independant_faster) - offset_fast = safeOffsetInc(parent->area, extra_speed + m_config.maximum_move_distance, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 1); + 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 = m_config.maximum_move_distance - (m_config.maximum_move_distance_slow + extra_slow_speed); - offset_fast = safeOffsetInc(offset_slow, delta_slow_fast, wall_restriction, safe_movement_distance, safe_movement_distance + radius, offset_independant_faster ? 2 : 1); + const coord_t delta_slow_fast = config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed); + offset_fast = safe_offset_inc(offset_slow, delta_slow_fast, wall_restriction, safe_movement_distance, safe_movement_distance + radius, offset_independant_faster ? 2 : 1); } +#ifdef TREESUPPORT_DEBUG_SVG + SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-fast-%d-%ld.svg", layer_idx, int(merging_area_idx)), + { { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } }, + { { union_ex(offset_fast) }, { "offset_fast", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif // TREESUPPORT_DEBUG_SVG } } - std::optional result; + std::optional result; + inc_wo_collision.clear(); 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 - Polygons lines_offset = offset(to_polylines(parent->area), scaled(0.005), jtMiter, 1.2); - Polygons base_error_area = union_(parent->area, lines_offset); - result = increaseSingleArea(settings, layer_idx, parent, base_error_area, to_bp_data, to_model_data, inc_wo_collision, (m_config.maximum_move_distance + extra_speed) * 1.5, mergelayer); + 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, + 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) #else // TREE_SUPPORT_SHOW_ERRORS @@ -2063,24 +1789,25 @@ void TreeSupport::increaseAreas(std::unordered_map& to "Radius: " << radius << " at layer: " << layer_idx - 1 << " NextTarget: " << elem.next_height << " 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 << " gracious " << elem.to_model_gracious << " safe " << elem.can_use_safe_radius << " until move " << elem.dont_move_until << " \n " - "Parent " << parent << ": Radius: " << m_config.getCollisionRadius(*parent) << " at layer: " << layer_idx << " NextTarget: " << parent->next_height << - " Distance to top: " << parent->distance_to_top << " Elephant foot increases " << parent->elephant_foot_increases << " use_min_xy_dist " << parent->use_min_xy_dist << - " to buildplate " << parent->to_buildplate << " gracious " << parent->to_model_gracious << " safe " << parent->can_use_safe_radius << " until move " << parent->dont_move_until; - showError("Potentially lost branch!", true); + "Parent " << &parent << ": Radius: " << config.getCollisionRadius(parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.next_height << + " 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 << + " to buildplate " << parent.state.to_buildplate << " gracious " << parent.state.to_model_gracious << " safe " << parent.state.can_use_safe_radius << " until move " << parent.state.dont_move_until; + tree_supports_show_error("Potentially lost branch!", true); } else - result = increaseSingleArea(settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); + result = increase_single_area(volumes, config, settings, layer_idx, parent, + settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); - if (result) - { + if (result) { elem = *result; - radius = m_config.getCollisionRadius(elem); + radius = config.getCollisionRadius(elem); elem.last_area_increase = settings; add = true; - bypass_merge = !settings.move || (settings.use_min_distance && elem.distance_to_top < m_config.tip_layers); // do not merge if the branch should not move or the priority has to be to get farther away from the model. + // do not merge if the branch should not move or the priority has to be to get farther away from the model. + bypass_merge = !settings.move || (settings.use_min_distance && elem.distance_to_top < config.tip_layers); if (settings.move) elem.dont_move_until = 0; else - elem.result_on_layer = parent->result_on_layer; + elem.result_on_layer = parent.state.result_on_layer; elem.can_use_safe_radius = settings.type != AvoidanceType::Fast; @@ -2094,156 +1821,553 @@ void TreeSupport::increaseAreas(std::unordered_map& to #endif // TREE_SUPPORT_SHOW_ERRORS << "Trying to keep area by moving faster than intended: Success"; break; - } - else if (!settings.no_error) + } else if (!settings.no_error) BOOST_LOG_TRIVIAL(error) << "Trying to keep area by moving faster than intended: FAILURE! WRONG BRANCHES LIKLY!"; } if (add) { - Polygons max_influence_area = safeUnion(diff_clipped(inc_wo_collision, m_volumes.getCollision(radius, layer_idx - 1, elem.use_min_xy_dist)), safeUnion(to_bp_data, to_model_data)); // union seems useless, but some rounding errors somewhere can cause to_bp_data to be slightly bigger than it should be - { - std::lock_guard critical_section_newLayer(critical_sections); - if (bypass_merge) { - bypass_merge_areas.emplace_back(new SupportElement(elem, std::move(max_influence_area))); - } else { - influence_areas.emplace(elem, max_influence_area); - if (elem.to_buildplate) - to_bp_areas.emplace(elem, to_bp_data); - if (m_config.support_rests_on_model) - to_model_areas.emplace(elem, to_model_data); - } + // Union seems useless, but some rounding errors somewhere can cause to_bp_data to be slightly bigger than it should be. + assert(! inc_wo_collision.empty() || ! to_bp_data.empty() || ! to_model_data.empty()); + Polygons max_influence_area = safe_union( + diff_clipped(inc_wo_collision, volumes.getCollision(radius, layer_idx - 1, elem.use_min_xy_dist)), + safe_union(to_bp_data, to_model_data)); + merging_area.state = elem; + assert(!max_influence_area.empty()); + merging_area.set_bbox(get_extents(max_influence_area)); + merging_area.areas.influence_areas = std::move(max_influence_area); + if (! bypass_merge) { + if (elem.to_buildplate) + merging_area.areas.to_bp_areas = std::move(to_bp_data); + if (config.support_rests_on_model) + merging_area.areas.to_model_areas = std::move(to_model_data); } - } else - parent->result_on_layer = Point(-1, -1); // 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). + } else { + // 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(); + } } }); } +[[nodiscard]] static SupportElementState merge_support_element_states( + const SupportElementState &first, const SupportElementState &second, const Point &next_position, const coord_t next_height, + const TreeSupportSettings &config) +{ + SupportElementState out; + out.next_position = next_position; + out.next_height = next_height; + out.use_min_xy_dist = first.use_min_xy_dist || second.use_min_xy_dist; + out.supports_roof = first.supports_roof || second.supports_roof; + out.dont_move_until = std::max(first.dont_move_until, second.dont_move_until); + out.can_use_safe_radius = first.can_use_safe_radius || second.can_use_safe_radius; + out.missing_roof_layers = std::min(first.missing_roof_layers, second.missing_roof_layers); + out.skip_ovalisation = false; + if (first.target_height > second.target_height) { + out.target_height = first.target_height; + out.target_position = first.target_position; + } else { + out.target_height = second.target_height; + out.target_position = second.target_position; + } + out.effective_radius_height = std::max(first.effective_radius_height, second.effective_radius_height); + out.distance_to_top = std::max(first.distance_to_top, second.distance_to_top); -void TreeSupport::createLayerPathing(std::vector>& move_bounds) + out.to_buildplate = first.to_buildplate && second.to_buildplate; + out.to_model_gracious = first.to_model_gracious && second.to_model_gracious; // valid as we do not merge non-gracious with gracious + + out.elephant_foot_increases = 0; + if (config.diameter_scale_bp_radius > 0) { + coord_t foot_increase_radius = std::abs(std::max(config.getCollisionRadius(second), config.getCollisionRadius(first)) - config.getCollisionRadius(out)); + // 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.branch_radius * (config.diameter_scale_bp_radius - config.diameter_angle_scale_factor)); + } + + // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. + out.last_area_increase = { + std::min(first.last_area_increase.type, second.last_area_increase.type), + std::min(first.last_area_increase.increase_speed, second.last_area_increase.increase_speed), + first.last_area_increase.increase_radius || second.last_area_increase.increase_radius, + first.last_area_increase.no_error || second.last_area_increase.no_error, + first.last_area_increase.use_min_distance && second.last_area_increase.use_min_distance, + first.last_area_increase.move || second.last_area_increase.move }; + + return out; +} + +static bool merge_influence_areas_two_elements( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const LayerIndex layer_idx, + SupportElementMerging &dst, SupportElementMerging &src) +{ + // 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 + // 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; + + if (merging_gracious_and_non_gracious || merging_min_and_regular_xy) + return false; + + const bool dst_radius_bigger = config.getCollisionRadius(dst.state) > config.getCollisionRadius(src.state); + const SupportElementMerging &smaller_rad = dst_radius_bigger ? src : dst; + const SupportElementMerging &bigger_rad = dst_radius_bigger ? dst : src; + const coord_t real_radius_delta = std::abs(config.getRadius(bigger_rad.state) - config.getRadius(smaller_rad.state)); + { + // Testing intersection of bounding boxes. + // Expand the smaller radius branch bounding box to match the lambda intersect_small_with_bigger() below. + // Because the lambda intersect_small_with_bigger() applies a rounded offset, a snug offset of the bounding box + // is sufficient. On the other side, if a mitered offset was used by the lambda, + // the bounding box expansion would have to account for the mitered extension of the sharp corners. + Eigen::AlignedBox smaller_bbox = smaller_rad.bbox(); + smaller_bbox.min() -= Point{ real_radius_delta, real_radius_delta }; + smaller_bbox.max() += Point{ real_radius_delta, real_radius_delta }; + if (! smaller_bbox.intersects(bigger_rad.bbox())) + return false; + } + + // Accumulator of a radius increase of a "to model" branch by merging in a "to build plate" branch. + coord_t increased_to_model_radius = 0; + const bool merging_to_bp = dst.state.to_buildplate && src.state.to_buildplate; + if (! merging_to_bp) { + // Get the real radius increase as the user does not care for the collision model. + if (dst.state.to_buildplate != src.state.to_buildplate) { + // Merging a "to build plate" branch with a "to model" branch. + // Don't allow merging a thick "to build plate" branch into a thinner "to model" branch. + const coord_t rdst = config.getRadius(dst.state); + const coord_t rsrc = config.getRadius(src.state); + if (dst.state.to_buildplate) { + if (rsrc < rdst) + increased_to_model_radius = src.state.increased_to_model_radius + rdst - rsrc; + } else { + if (rsrc > rdst) + increased_to_model_radius = dst.state.increased_to_model_radius + rsrc - rdst; + } + 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 + // they will even been drawn the merge is skipped + 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; + } + + // Area of the bigger radius is used to ensure correct placement regarding the relevant avoidance, + // so if that would change an invalid area may be created. + if (! bigger_rad.state.can_use_safe_radius && smaller_rad.state.can_use_safe_radius) + 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 + // 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; + + // The idea is that the influence area with the smaller collision radius is increased by the radius difference. + // If this area has any intersections with the influence area of the larger collision radius, a branch (of the larger collision radius) placed in this intersection, has already engulfed the branch of the smaller collision radius. + // Because of this a merge may happen even if the influence areas (that represent possible center points of branches) do not intersect yet. + // Remember that collision radius <= real radius as otherwise this assumption would be false. + const coord_t smaller_collision_radius = config.getCollisionRadius(smaller_rad.state); + const Polygons &collision = volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius); + auto intersect_small_with_bigger = [real_radius_delta, smaller_collision_radius, &collision, &config](const Polygons &small, const Polygons &bigger) { + return intersection( + safe_offset_inc( + small, real_radius_delta, collision, + // -3 avoids possible rounding errors + 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0), + bigger); + }; + Polygons intersect = intersect_small_with_bigger( + merging_to_bp ? smaller_rad.areas.to_bp_areas : smaller_rad.areas.to_model_areas, + merging_to_bp ? bigger_rad.areas.to_bp_areas : bigger_rad.areas.to_model_areas); + + // 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). + if (area(intersect) <= tiny_area_threshold) + return false; + + // While 0.025 was guessed as enough, i did not have reason to change it. + if (area(offset(intersect, scaled(-0.025), jtMiter, 1.2)) <= tiny_area_threshold) + return false; + + // Do the actual merge now that the branches are confirmed to be able to intersect. + // calculate which point is closest to the point of the last merge (or tip center if no merge above it has happened) + // used at the end to estimate where to best place the branch on the bottom most layer + // could be replaced with a random point inside the new area + Point new_pos = dst.state.next_position; + if (! contains(intersect, new_pos)) + move_inside(intersect, new_pos); + + 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 ? + // 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; + + // Rather unioning with "intersect" due to some rounding errors. + Polygons influence_areas = safe_union( + intersect_small_with_bigger(smaller_rad.areas.influence_areas, bigger_rad.areas.influence_areas), + intersect); + + Polygons to_model_areas; + if (merging_to_bp && config.support_rests_on_model) + to_model_areas = new_state.to_model_gracious ? + // Rather unioning with "intersect" due to some rounding errors. + safe_union( + intersect_small_with_bigger(smaller_rad.areas.to_model_areas, bigger_rad.areas.to_model_areas), + intersect) : + influence_areas; + + dst.parents.insert(dst.parents.end(), src.parents.begin(), src.parents.end()); + dst.state = new_state; + dst.areas.influence_areas = std::move(influence_areas); + dst.areas.to_bp_areas.clear(); + dst.areas.to_model_areas.clear(); + if (merging_to_bp) { + dst.areas.to_bp_areas = std::move(intersect); + if (config.support_rests_on_model) + dst.areas.to_model_areas = std::move(to_model_areas); + } else + dst.areas.to_model_areas = std::move(intersect); + // Update the bounding box. + BoundingBox bbox(get_extents(dst.areas.influence_areas)); + bbox.merge(get_extents(dst.areas.to_bp_areas)); + bbox.merge(get_extents(dst.areas.to_model_areas)); + dst.set_bbox(bbox); + // Clear the source data. + src.areas.clear(); + src.parents.clear(); + return true; +} + +/*! + * \brief Merges Influence Areas if possible. + * + * Branches which do overlap have to be merged. This helper merges all elements in input with the elements into reduced_new_layer. + * Elements in input_aabb are merged together if possible, while elements reduced_new_layer_aabb are not checked against each other. + * + * \param reduced_aabb[in,out] The already processed elements. + * \param input_aabb[in] Not yet processed elements + * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. Value is the influence area where the center of a circle of support may be placed. + * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. + * Value is the influence area where the center of a circle of support may be placed. + * \param influence_areas[in] The influence areas without avoidance removed. + * \param insert_bp_areas[out] Elements to be inserted into the main dictionary after the Helper terminates. + * \param insert_model_areas[out] Elements to be inserted into the secondary dictionary after the Helper terminates. + * \param insert_influence[out] Elements to be inserted into the dictionary containing the largest possibly valid influence area (ignoring if the area may not be there because of avoidance) + * \param erase[out] Elements that should be deleted from the above dictionaries. + * \param layer_idx[in] The Index of the current Layer. + */ + +static SupportElementMerging* merge_influence_areas_leaves( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const LayerIndex layer_idx, + SupportElementMerging * const dst_begin, SupportElementMerging *dst_end) +{ + // Merging at the lowest level of the AABB tree. Checking one against each other, O(n^2). + assert(dst_begin < dst_end); + for (SupportElementMerging *i = dst_begin; i + 1 < dst_end;) { + for (SupportElementMerging *j = i + 1; j != dst_end;) + if (merge_influence_areas_two_elements(volumes, config, layer_idx, *i, *j)) { + // i was merged with j, j is empty. + if (j != -- dst_end) + *j = std::move(*dst_end); + goto merged; + } else + ++ j; + // not merged + ++ i; + merged: + ; + } + return dst_end; +} + +static SupportElementMerging* merge_influence_areas_two_sets( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const LayerIndex layer_idx, + SupportElementMerging * const dst_begin, SupportElementMerging * dst_end, + SupportElementMerging * src_begin, SupportElementMerging * const src_end) +{ + // 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, + // src follows dst. + assert(src_begin < src_end); + assert(dst_begin < dst_end); + assert(dst_end <= src_begin); + for (SupportElementMerging *src = src_begin; src != src_end; ++ src) { + SupportElementMerging *dst = dst_begin; + SupportElementMerging *merged = nullptr; + for (; dst != dst_end; ++ dst) + if (merge_influence_areas_two_elements(volumes, config, layer_idx, *dst, *src)) { + merged = dst ++; + if (src != src_begin) + // Compactify src. + *src = std::move(*src_begin); + ++ src_begin; + break; + } + for (; dst != dst_end;) + if (merge_influence_areas_two_elements(volumes, config, layer_idx, *merged, *dst)) { + // Compactify dst. + if (dst != -- dst_end) + *dst = std::move(*dst_end); + } else + ++ dst; + } + // Compactify src elements that were not merged with dst to the end of dst. + assert(dst_end <= src_begin); + if (dst_end == src_begin) + dst_end = src_end; + else + while (src_begin != src_end) + *dst_end ++ = std::move(*src_begin ++); + + return dst_end; +} + +/*! + * \brief Merges Influence Areas at one layer if possible. + * + * Branches which do overlap have to be merged. This manages the helper and uses a divide and conquer approach to parallelize this problem. This parallelization can at most accelerate the merging by a factor of 2. + * + * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. + * Value is the influence area where the center of a circle of support may be placed. + * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. + * Value is the influence area where the center of a circle of support may be placed. + * \param influence_areas[in] The Elements of the current Layer without avoidances removed. This is the largest possible influence area for this layer. + * Value is the influence area where the center of a circle of support may be placed. + * \param layer_idx[in] The current layer. + */ +static void merge_influence_areas( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const LayerIndex layer_idx, + std::vector &influence_areas) +{ + const size_t input_size = influence_areas.size(); + if (input_size == 0) + return; + + // Merging by divide & conquer. + // The majority of time is consumed by Clipper polygon operations, intersection is accelerated by bounding boxes. + // Sorting input into an AABB tree helps to perform most of the intersections at first iterations, + // thus reducing computation when merging larger subtrees. + // The actual merge logic is found in merge_influence_areas_two_sets. + + // Build an AABB tree over the influence areas. + //FIXME A full tree does not need to be built, the lowest level branches will be always bucketed. + // However the additional time consumed is negligible. + AABBTreeIndirect::Tree<2, coord_t> tree; + // Sort influence_areas in place. + tree.build_modify_input(influence_areas); + + // Prepare the initial buckets as ranges of influence areas. The initial buckets contain power of 2 influence areas to follow + // the branching of the AABB tree. + // Vectors of ranges of influence areas, following the branching of the AABB tree: + std::vector> buckets; + // Initial number of buckets for 1st round of merging. + size_t num_buckets_initial; + { + // How many buckets per first merge iteration? + const size_t num_threads = tbb::this_task_arena::max_concurrency(); + // 4 buckets per thread if possible, + const size_t num_buckets_min = (input_size + 2) / 4; + // 2 buckets per thread otherwise. + const size_t num_buckets_max = input_size / 2; + num_buckets_initial = num_buckets_min >= num_threads ? num_buckets_min : num_buckets_max; + const size_t bucket_size = num_buckets_min >= num_threads ? 4 : 2; + // Fill in the buckets. + SupportElementMerging *it = influence_areas.data(); + // Reserve one more bucket to keep a single influence area which will not be merged in the first iteration. + buckets.reserve(num_buckets_initial + 1); + for (size_t i = 0; i < num_buckets_initial; ++ i, it += bucket_size) + buckets.emplace_back(std::make_pair(it, it + bucket_size)); + SupportElementMerging *it_end = influence_areas.data() + influence_areas.size(); + if (buckets.back().second >= it_end) { + // Last bucket is less than size 4, but bigger than size 1. + buckets.back().second = std::min(buckets.back().second, it_end); + } else { + // Last bucket is size 1, it will not be merged in the first iteration. + assert(it + 1 == it_end); + buckets.emplace_back(std::make_pair(it, it_end)); + } + } + + // 1st merge iteration, merge one with each other. + tbb::parallel_for(tbb::blocked_range(0, num_buckets_initial), + [&](const tbb::blocked_range &range) { + for (size_t idx = range.begin(); idx < range.end(); ++ idx) { + const size_t bucket_pair_idx = idx * 2; + // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets + buckets[idx].second = merge_influence_areas_leaves(volumes, config, layer_idx, buckets[idx].first, buckets[idx].second); + } + }); + + // Further merge iterations, merging one AABB subtree with another one, hopefully minimizing intersections between the elements + // of each of the subtree. + while (buckets.size() > 1) { + tbb::parallel_for(tbb::blocked_range(0, buckets.size() / 2), + [&](const tbb::blocked_range &range) { + for (size_t idx = range.begin(); idx < range.end(); ++ idx) { + const size_t bucket_pair_idx = idx * 2; + // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets + buckets[bucket_pair_idx].second = merge_influence_areas_two_sets(volumes, config, layer_idx, + buckets[bucket_pair_idx].first, buckets[bucket_pair_idx].second, + buckets[bucket_pair_idx + 1].first, buckets[bucket_pair_idx + 1].second); + } + }); + // Remove odd buckets, which were merged into even buckets. + size_t new_size = (buckets.size() + 1) / 2; + for (size_t i = 1; i < new_size; ++ i) + buckets[i] = std::move(buckets[i * 2]); + buckets.erase(buckets.begin() + new_size, buckets.end()); + } +} + +/*! + * \brief Propagates influence downwards, and merges overlapping ones. + * + * \param move_bounds[in,out] All currently existing influence areas + */ +static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupportSettings &config, std::vector> &move_bounds) { #ifdef SLIC3R_TREESUPPORTS_PROGRESS const double data_size_inverse = 1 / double(move_bounds.size()); double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES; #endif // SLIC3R_TREESUPPORTS_PROGRESS - auto dur_inc = std::chrono::duration_values::zero(); - auto dur_merge = std::chrono::duration_values::zero(); + auto dur_inc = std::chrono::duration_values::zero(); + auto dur_total = std::chrono::duration_values::zero(); - LayerIndex last_merge = move_bounds.size(); + LayerIndex last_merge_layer_idx = move_bounds.size(); bool new_element = false; - size_t max_merge_every_x_layers = std::min(std::min(5000 / (std::max(m_config.maximum_move_distance, coord_t(100))), 1000 / std::max(m_config.maximum_move_distance_slow, coord_t(20))), 3000 / m_config.layer_height); // Ensures at least one merge operation per 3mm height, 50 layers, 1 mm movement of slow speed or 5mm movement of fast speed (whatever is lowest). Values were guessed. + // Ensures at least one merge operation per 3mm height, 50 layers, 1 mm movement of slow speed or 5mm movement of fast speed (whatever is lowest). Values were guessed. + size_t max_merge_every_x_layers = std::min(std::min(5000 / (std::max(config.maximum_move_distance, coord_t(100))), 1000 / std::max(config.maximum_move_distance_slow, coord_t(20))), 3000 / config.layer_height); size_t merge_every_x_layers = 1; // Calculate the influence areas for each layer below (Top down) // This is done by first increasing the influence area by the allowed movement distance, and merging them with other influence areas if possible for (int layer_idx = int(move_bounds.size()) - 1; layer_idx > 0; -- layer_idx) - { - // merging is expensive and only parallelized to a max speedup of 2. As such it may be useful in some cases to only merge every few layers to improve performance. - bool merge_this_layer = size_t(last_merge - layer_idx) >= merge_every_x_layers; + if (const std::set &prev_layer = move_bounds[layer_idx]; ! prev_layer.empty()) { + // merging is expensive and only parallelized to a max speedup of 2. As such it may be useful in some cases to only merge every few layers to improve performance. + bool had_new_element = new_element; + const bool merge_this_layer = had_new_element || size_t(last_merge_layer_idx - layer_idx) >= merge_every_x_layers; + if (had_new_element) + merge_every_x_layers = 1; + const auto ta = std::chrono::high_resolution_clock::now(); - if (new_element) - { - merge_this_layer = true; - merge_every_x_layers = 1; - } + // ### Increase the influence areas by the allowed movement distance + std::vector influence_areas; + influence_areas.reserve(prev_layer.size()); + std::transform(prev_layer.begin(), prev_layer.end(), std::back_inserter(influence_areas), + [](SupportElement* el) { + assert(! el->influence_area.empty()); + boost::container::small_vector parents; + parents.emplace_back(el); + return SupportElementMerging{ el->state, parents }; + }); + increase_areas_one_layer(volumes, config, influence_areas, layer_idx, merge_this_layer); - std::map influence_areas; // Over this map will be iterated when merging, as such it has to be ordered to ensure deterministic results. - std::unordered_map to_bp_areas, to_model_areas; // The area of these SupportElement is not set, to avoid to much allocation and deallocation on the heap - std::vector bypass_merge_areas; // Different to the other maps of SupportElements as these here have the area already set, as they are already to be inserted into move_bounds. + // Place already fully constructed elements to the output, remove them from influence_areas. + std::set &this_layer = move_bounds[layer_idx - 1]; + influence_areas.erase(std::remove_if(influence_areas.begin(), influence_areas.end(), + [&this_layer, layer_idx](SupportElementMerging &elem) { + if (elem.areas.influence_areas.empty()) + // This area was removed completely due to collisions. + 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; + tree_supports_show_error("Insert error of area after bypassing merge.\n", true); + } + // Move the area to output. + this_layer.emplace(new SupportElement(elem.state, std::move(elem.parents), std::move(elem.areas.influence_areas))); + return true; + } + // Keep the area. + return false; + }), + influence_areas.end()); - auto ta = std::chrono::high_resolution_clock::now(); - - std::vector last_layer; - last_layer.insert(last_layer.begin(), move_bounds[layer_idx].begin(), move_bounds[layer_idx].end()); - - // ### Increase the influence areas by the allowed movement distance - increaseAreas(to_bp_areas, to_model_areas, influence_areas, bypass_merge_areas, last_layer, layer_idx, merge_this_layer); - - auto tb = std::chrono::high_resolution_clock::now(); - if (merge_this_layer) - { - bool reduced_by_merging = false; - size_t count_before_merge = influence_areas.size(); - // ### Calculate which influence areas overlap, and merge them into a new influence area (simplified: an intersection of influence areas that have such an intersection) - mergeInfluenceAreas(m_volumes, m_config, to_bp_areas, to_model_areas, influence_areas, layer_idx); - - last_merge = layer_idx; - reduced_by_merging = count_before_merge > influence_areas.size(); - if (!reduced_by_merging && !new_element) - { - merge_every_x_layers = std::min(max_merge_every_x_layers, merge_every_x_layers + 1); + dur_inc += std::chrono::high_resolution_clock::now() - ta; + new_element = ! move_bounds[layer_idx - 1].empty(); + if (merge_this_layer) { + bool reduced_by_merging = false; + if (size_t count_before_merge = influence_areas.size(); count_before_merge > 1) { + // ### Calculate which influence areas overlap, and merge them into a new influence area (simplified: an intersection of influence areas that have such an intersection) + merge_influence_areas(volumes, config, layer_idx, influence_areas); + reduced_by_merging = count_before_merge > influence_areas.size(); + } + last_merge_layer_idx = layer_idx; + if (! reduced_by_merging && ! had_new_element) + merge_every_x_layers = std::min(max_merge_every_x_layers, merge_every_x_layers + 1); } - } - auto tc = std::chrono::high_resolution_clock::now(); - dur_inc += tb - ta; - dur_merge += tc - tb; + dur_total += std::chrono::high_resolution_clock::now() - ta; - new_element = !move_bounds[layer_idx - 1].empty(); + // Save calculated elements to output, and allocate Polygons on heap, as they will not be changed again. + for (SupportElementMerging &elem : influence_areas) + 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; + tree_supports_show_error("Insert error of area after merge.\n", true); + } + this_layer.emplace(new SupportElement(elem.state, std::move(elem.parents), std::move(new_area))); + } - // Save calculated elements to output, and allocate Polygons on heap, as they will not be changed again. - for (const std::pair &tup : influence_areas) { - const SupportElement &elem = tup.first; - validate_range(tup.second); - validate_range(safeUnion(tup.second)); - Polygons new_area = safeUnion(tup.second); - double new_area_area = area(new_area); - move_bounds[layer_idx - 1].emplace(new SupportElement(elem, std::move(new_area))); - if (new_area_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.to_buildplate; - TreeSupport::showError("Insert error of area after merge.\n", true); - } + #ifdef SLIC3R_TREESUPPORTS_PROGRESS + progress_total += data_size_inverse * TREE_PROGRESS_AREA_CALC; + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); + #endif } - // Place already fully constructed elements in the output. - for (SupportElement* elem : bypass_merge_areas) { - if (area(elem->area) < tiny_area_threshold) { - BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; - TreeSupport::showError("Insert error of area after bypassing merge.\n", true); - } - move_bounds[layer_idx - 1].emplace(elem); - } - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - progress_total += data_size_inverse * TREE_PROGRESS_AREA_CALC; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); -#endif - } - - BOOST_LOG_TRIVIAL(info) << "Time spent with creating influence areas' subtasks: Increasing areas " << dur_inc.count() / 1000000 << " ms merging areas: " << dur_merge.count() / 1000000 << " ms"; + 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"; } - -void TreeSupport::setPointsOnAreas(const SupportElement* elem) +/*! + * \brief Sets the result_on_layer for all parents based on the SupportElement supplied. + * + * \param elem[in] The SupportElements, which parent's position should be determined. + */ +static void set_points_on_areas(const SupportElement* elem) { // Based on the branch center point of the current layer, the point on the next (further up) layer is calculated. - if (elem->result_on_layer == Point(-1, -1)) - { + if (! elem->state.result_on_layer_is_set()) { BOOST_LOG_TRIVIAL(error) << "Uninitialized support element"; - TreeSupport::showError("Uninitialized support element. A branch may be missing.\n", true); + tree_supports_show_error("Uninitialized support element. A branch may be missing.\n", true); return; } for (SupportElement* next_elem : elem->parents) - { - if (next_elem->result_on_layer != Point(-1, -1)) // 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. - continue; - - Point from = elem->result_on_layer; - if (! contains(next_elem->area, from)) { - moveInside(next_elem->area, from, 0); // 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. While this seems like a problem it may for example occur after merges. + // 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()) { + Point from = elem->state.result_on_layer; + if (! contains(next_elem->influence_area, from)) { + move_inside(next_elem->influence_area, from, 0); // 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. While this seems like a problem it may for example occur after merges. + } + next_elem->state.result_on_layer = from; + // do not call recursive because then amount of layers would be restricted by the stack size } - next_elem->result_on_layer = from; - // do not call recursive because then amount of layers would be restricted by the stack size - } } -bool TreeSupport::setToModelContact(std::vector>& move_bounds, SupportElement* first_elem, const LayerIndex layer_idx) +/*! + * \brief Get the best point to connect to the model and set the result_on_layer of the relevant SupportElement accordingly. + * + * \param move_bounds[in,out] All currently existing influence areas + * \param first_elem[in,out] SupportElement that did not have its result_on_layer set meaning that it does not have a child element. + * \param layer_idx[in] The current layer. + * \return Should elem be deleted. + */ +static bool set_to_model_contact(const TreeModelVolumes &volumes, const TreeSupportSettings &config, std::vector> &move_bounds, SupportElement *first_elem, const LayerIndex layer_idx) { - if (first_elem->to_model_gracious) + if (first_elem->state.to_model_gracious) { SupportElement* check = first_elem; @@ -2252,57 +2376,46 @@ bool TreeSupport::setToModelContact(std::vector>& move bool set = false; // check for every layer upwards, up to the point where this influence area was created (either by initial insert or merge) if the branch could be placed on it, and highest up layer index. - - for (LayerIndex layer_check = layer_idx; check->next_height >= layer_check; layer_check++) - { - if (! intersection(check->area, m_volumes.getPlaceableAreas(m_config.getCollisionRadius(*check), layer_check)).empty()) { + for (LayerIndex layer_check = layer_idx; check->state.next_height >= layer_check; ++ layer_check) { + if (! intersection(check->influence_area, volumes.getPlaceableAreas(config.getCollisionRadius(check->state), layer_check)).empty()) { set = true; last_successfull_layer = layer_check; } checked.emplace_back(check); if (check->parents.size() == 1) - { check = check->parents[0]; - } else - { - break; // reached merge point - } + // reached merge point + break; } // Could not find valid placement, even though it should exist => error handling - if (!set) - { - if (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL) - { + if (!set) { + if (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL) { BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << layer_idx << ": REMOVING BRANCH"; - TreeSupport::showError("Could not fine valid placement on model! Removing this branch...", true); - for (LayerIndex layer = layer_idx; layer <= first_elem->next_height; layer++) - { + tree_supports_show_error("Could not fine valid placement on model! Removing this branch...", true); + for (LayerIndex layer = layer_idx; layer <= first_elem->state.next_height; ++ layer) { move_bounds[layer].erase(checked[layer - layer_idx]); delete checked[layer - layer_idx]; } - } - else - { + } else { BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << layer_idx; - TreeSupport::showError("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches.", true); - first_elem->to_model_gracious = false; - return setToModelContact(move_bounds, first_elem, layer_idx); + tree_supports_show_error("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches.", true); + first_elem->state.to_model_gracious = false; + return set_to_model_contact(volumes, config, move_bounds, first_elem, layer_idx); } } - for (LayerIndex layer = layer_idx + 1; layer < last_successfull_layer - 1; layer++) - { + for (LayerIndex layer = layer_idx + 1; layer < last_successfull_layer - 1; ++ layer) { move_bounds[layer].erase(checked[layer - layer_idx]); delete checked[layer - layer_idx]; } // Guess a point inside the influence area, in which the branch will be placed in. - Point best = checked[last_successfull_layer - layer_idx]->next_position; - if (! contains(checked[last_successfull_layer - layer_idx]->area, best)) - moveInside(checked[last_successfull_layer - layer_idx]->area, best); - checked[last_successfull_layer - layer_idx]->result_on_layer = best; + Point best = checked[last_successfull_layer - layer_idx]->state.next_position; + if (! contains(checked[last_successfull_layer - layer_idx]->influence_area, best)) + move_inside(checked[last_successfull_layer - layer_idx]->influence_area, best); + checked[last_successfull_layer - layer_idx]->state.result_on_layer = best; BOOST_LOG_TRIVIAL(debug) << "Added gracious Support On Model Point (" << best.x() << "," << best.y() << "). The current layer is " << last_successfull_layer; @@ -2310,25 +2423,33 @@ bool TreeSupport::setToModelContact(std::vector>& move } else // can not add graceful => just place it here and hope for the best { - Point best = first_elem->next_position; - if (! contains(first_elem->area, best)) - moveInside(first_elem->area, best); - first_elem->result_on_layer = best; - first_elem->to_model_gracious = false; + Point best = first_elem->state.next_position; + if (! contains(first_elem->influence_area, best)) + move_inside(first_elem->influence_area, best); + first_elem->state.result_on_layer = best; + first_elem->state.to_model_gracious = false; BOOST_LOG_TRIVIAL(debug) << "Added NON gracious Support On Model Point (" << best.x() << "," << best.y() << "). The current layer is " << layer_idx; return false; } } -void TreeSupport::createNodesFromArea(std::vector>& move_bounds) +/*! + * \brief Set the result_on_layer point for all influence areas + * + * \param move_bounds[in,out] All currently existing influence areas + */ +static void create_nodes_from_area( + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + std::vector> &move_bounds) { // 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. for (SupportElement* init : move_bounds[0]) { - Point p = init->next_position; - if (! contains(init->area, p)) - moveInside(init->area, p, 0); - init->result_on_layer = p; - setPointsOnAreas(init); // also set the parent nodes, as these will be required for the first iteration of the loop below + Point p = init->state.next_position; + if (! contains(init->influence_area, p)) + move_inside(init->influence_area, p, 0); + init->state.result_on_layer = p; + set_points_on_areas(init); // also set the parent nodes, as these will be required for the first iteration of the loop below } for (LayerIndex layer_idx = 1; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { @@ -2336,28 +2457,30 @@ void TreeSupport::createNodesFromArea(std::vector>& mo for (SupportElement* elem : move_bounds[layer_idx]) { bool removed = false; // check if the resulting center point is not yet set - if (elem->result_on_layer == Point(-1, -1)) { - if (elem->to_buildplate || (!elem->to_buildplate && elem->distance_to_top < m_config.min_dtt_to_model && !elem->supports_roof)) { - if (elem->to_buildplate) { - BOOST_LOG_TRIVIAL(error) << "Uninitialized Influence area targeting " << elem->target_position.x() << "," << elem->target_position.y() << ") " - "at target_height: " << elem->target_height << " layer: " << layer_idx; - TreeSupport::showError("Uninitialized support element! A branch could be missing or exist partially.", true); + if (! elem->state.result_on_layer_is_set()) { + if (elem->state.to_buildplate || (!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() << ") " + "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.", true); } remove.emplace(elem); // we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set removed = true; for (SupportElement* parent : elem->parents) - parent->result_on_layer = Point(-1, -1); // When the roof was not able to generate downwards enough, the top elements may have not moved, and have result_on_layer already set. As this branch needs to be removed => all parents result_on_layer have to be invalidated. + // When the roof was not able to generate downwards enough, the top elements may have not moved, and have result_on_layer already set. + // As this branch needs to be removed => all parents result_on_layer have to be invalidated. + parent->state.result_on_layer_reset(); continue; } else { // set the point where the branch will be placed on the model - removed = setToModelContact(move_bounds, elem, layer_idx); + removed = set_to_model_contact(volumes, config, move_bounds, elem, layer_idx); if (removed) remove.emplace(elem); } } if (!removed) - setPointsOnAreas(elem); // element is valid now setting points in the layer above + set_points_on_areas(elem); // element is valid now setting points in the layer above } // delete all not needed support elements @@ -2369,7 +2492,16 @@ void TreeSupport::createNodesFromArea(std::vector>& mo } } -void TreeSupport::generateBranchAreas( +/*! + * \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, std::vector> &linear_data, std::vector> &layer_tree_polygons, const std::map &inverse_tree_order) @@ -2380,7 +2512,7 @@ void TreeSupport::generateBranchAreas( #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(m_config.branch_radius, SUPPORT_TREE_CIRCLE_RESOLUTION); + const Polygon branch_circle = make_circle(config.branch_radius, SUPPORT_TREE_CIRCLE_RESOLUTION); std::vector linear_inserts(linear_data.size()); #ifdef SLIC3R_TREESUPPORTS_PROGRESS @@ -2395,25 +2527,25 @@ void TreeSupport::generateBranchAreas( const SupportElement *elem = linear_data[idx].second; const auto it_elem = inverse_tree_order.find(const_cast(elem)); const SupportElement* child_elem = it_elem == inverse_tree_order.end() ? nullptr : it_elem->second; - const coord_t radius = m_config.getRadius(*elem); + const coord_t radius = config.getRadius(*elem); 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 (!elem->skip_ovalisation) { + if (!elem->state.skip_ovalisation) { if (child_elem != nullptr) { - const Point movement = child_elem->result_on_layer - elem->result_on_layer; + const Point movement = child_elem->state.result_on_layer - elem->state.result_on_layer; movement_directions.emplace_back(movement, radius); } for (SupportElement *parent : elem->parents) { - const Point movement = parent->result_on_layer - elem->result_on_layer; - movement_directions.emplace_back(movement, std::max(m_config.getRadius(*parent), m_config.support_line_width)); - parent_uses_min |= parent->use_min_xy_dist; + const Point movement = parent->state.result_on_layer - elem->state.result_on_layer; + movement_directions.emplace_back(movement, std::max(config.getRadius(*parent), config.support_line_width)); + parent_uses_min |= parent->state.use_min_xy_dist; } } double max_speed = 0; - auto generateArea = [&volumes = m_volumes, layer_idx, elem, &branch_circle, branch_radius = m_config.branch_radius, support_line_width = m_config.support_line_width, &movement_directions, &max_speed, parent_uses_min]( + auto generateArea = [&volumes, layer_idx, elem, &branch_circle, branch_radius = config.branch_radius, support_line_width = config.support_line_width, &movement_directions, &max_speed, parent_uses_min]( coord_t aoffset) { Polygons poly; @@ -2423,7 +2555,7 @@ void TreeSupport::generateBranchAreas( // 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 = elem->result_on_layer + movement.first / 2; + Point center_position = elem->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)); @@ -2441,7 +2573,9 @@ void TreeSupport::generateBranchAreas( } poly = diff_clipped(offset(union_(poly), std::min(coord_t(50), support_line_width / 4), jtMiter, 1.2), - volumes.getCollision(0, layer_idx, parent_uses_min || elem->use_min_xy_dist)); // 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. + // 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. + volumes.getCollision(0, layer_idx, parent_uses_min || elem->state.use_min_xy_dist)); return poly; }; @@ -2450,33 +2584,33 @@ void TreeSupport::generateBranchAreas( // ensure branch area will not overlap with model/collision. This can happen because of e.g. ovalization or increase_until_radius. linear_inserts[idx] = generateArea(0); - if (fast_relative_movement || m_config.getRadius(*elem) - m_config.getCollisionRadius(*elem) > m_config.support_line_width) { + if (fast_relative_movement || config.getRadius(*elem) - config.getCollisionRadius(elem->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(linear_inserts[idx], -m_config.support_line_width / 2); + ExPolygons nozzle_path = offset_ex(linear_inserts[idx], -config.support_line_width / 2); if (nozzle_path.size() > 1) { // Just try to make the area a tiny bit larger. - linear_inserts[idx] = generateArea(m_config.support_line_width / 2); - nozzle_path = offset_ex(linear_inserts[idx], -m_config.support_line_width / 2); + linear_inserts[idx] = generateArea(config.support_line_width / 2); + nozzle_path = offset_ex(linear_inserts[idx], -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) { Polygons polygons_with_correct_center; for (ExPolygon &part : nozzle_path) { - if (part.contains(elem->result_on_layer)) + if (part.contains(elem->state.result_on_layer)) polygons_with_correct_center = union_(polygons_with_correct_center, part); else { // try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... - Point from = elem->result_on_layer; + Point from = elem->state.result_on_layer; Polygons to = to_polygons(std::move(part)); - moveInside(to, from, 0); - if ((elem->result_on_layer - from).cast().norm() < scaled(0.025)) + move_inside(to, from, 0); + if ((elem->state.result_on_layer - from).cast().norm() < scaled(0.025)) polygons_with_correct_center = union_(polygons_with_correct_center, to); } } // Increase the area again, to ensure the nozzle path when calculated later is very similar to the one assumed above. - linear_inserts[idx] = offset(polygons_with_correct_center, m_config.support_line_width / 2, jtMiter, 1.2); - linear_inserts[idx] = diff_clipped(linear_inserts[idx], m_volumes.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist)); + linear_inserts[idx] = offset(polygons_with_correct_center, config.support_line_width / 2, jtMiter, 1.2); + linear_inserts[idx] = diff_clipped(linear_inserts[idx], volumes.getCollision(0, linear_data[idx].first, parent_uses_min || elem->state.use_min_xy_dist)); } } } @@ -2496,13 +2630,18 @@ void TreeSupport::generateBranchAreas( layer_tree_polygons[linear_data[i].first].emplace(linear_data[i].second, linear_inserts[i]); } -void TreeSupport::smoothBranchAreas(std::vector>& layer_tree_polygons) +/*! + * \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> &layer_tree_polygons) { #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 + m_config.support_line_width / 2; // this is the upper limit a radius may change per layer. +1 to avoid rounding errors + 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(layer_tree_polygons.size()) - 1; ++ layer_idx) { @@ -2517,15 +2656,15 @@ void TreeSupport::smoothBranchAreas(std::vectorparents) - if (m_config.getRadius(*parent) != m_config.getCollisionRadius(*parent)) { + if (config.getRadius(parent->state) != config.getCollisionRadius(parent->state)) { do_something = true; - max_outer_wall_distance = std::max(max_outer_wall_distance, (data_pair.first->result_on_layer - parent->result_on_layer).cast().norm() - (m_config.getRadius(*data_pair.first) - m_config.getRadius(*parent))); + max_outer_wall_distance = std::max(max_outer_wall_distance, (data_pair.first->state.result_on_layer - parent->state.result_on_layer).cast().norm() - (config.getRadius(*data_pair.first) - config.getRadius(*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) { Polygons max_allowed_area = offset(data_pair.second, float(max_outer_wall_distance), jtMiter, 1.2); for (SupportElement* parent : data_pair.first->parents) - if (m_config.getRadius(*parent) != m_config.getCollisionRadius(*parent)) + if (config.getRadius(parent->state) != config.getCollisionRadius(parent->state)) update_next[processing_idx].emplace_back(std::pair(parent, intersection(layer_tree_polygons[layer_idx + 1][parent], max_allowed_area))); } } @@ -2558,13 +2697,13 @@ void TreeSupport::smoothBranchAreas(std::vectorparents[idx]; coord_t max_outer_line_increase = max_radius_change_per_layer; Polygons result = offset(layer_tree_polygons[layer_idx + 1][parent], max_outer_line_increase, jtMiter, 1.2); - Point direction = data_pair.first->result_on_layer - parent->result_on_layer; + Point direction = data_pair.first->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 || updated_last_iteration.count(parent) || m_config.getCollisionRadius(*parent) != m_config.getRadius(*parent); + do_something = do_something || updated_last_iteration.count(parent) || config.getCollisionRadius(parent->state) != config.getRadius(parent->state); } if (do_something) { @@ -2576,7 +2715,7 @@ void TreeSupport::smoothBranchAreas(std::vector data_pair : update_next) + for (const std::pair &data_pair : update_next) if (data_pair.first != nullptr) { updated_last_iteration.emplace(data_pair.first); layer_tree_polygons[layer_idx][data_pair.first] = data_pair.second; @@ -2589,7 +2728,16 @@ void TreeSupport::smoothBranchAreas(std::vector> &layer_tree_polygons, const std::vector> &linear_data, std::vector>> &dropped_down_areas, @@ -2599,12 +2747,12 @@ void TreeSupport::dropNonGraciousAreas( [&](const tbb::blocked_range &range) { for (size_t idx = range.begin(); idx < range.end(); ++ idx) { SupportElement* elem = linear_data[idx].second; - bool non_gracious_model_contact = !elem->to_model_gracious && !inverse_tree_order.count(elem); // if a element has no child, it connects to whatever is below as no support further down for it will exist. + bool non_gracious_model_contact = !elem->state.to_model_gracious && !inverse_tree_order.count(elem); // if a element has no child, it connects to whatever is below as no support further down for it will exist. if (non_gracious_model_contact) { Polygons rest_support = layer_tree_polygons[linear_data[idx].first][elem]; LayerIndex counter = 1; while (area(rest_support) > tiny_area_threshold && counter < linear_data[idx].first) { - rest_support = diff_clipped(rest_support, m_volumes.getCollision(0, linear_data[idx].first - counter, false)); + rest_support = diff_clipped(rest_support, volumes.getCollision(0, linear_data[idx].first - counter, false)); dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support); counter++; } @@ -2613,8 +2761,17 @@ void TreeSupport::dropNonGraciousAreas( }); } -void TreeSupport::finalizeInterfaceAndSupportAreas( +/*! + * \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, @@ -2624,7 +2781,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( SupportGeneratorLayersPtr &intermediate_layers, SupportGeneratorLayerStorage &layer_storage) { - InterfacePreference interface_pref = m_config.interface_preference; // InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; + InterfacePreference interface_pref = config.interface_preference; // InterfacePreference::SupportLinesOverwriteInterface; #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; @@ -2636,11 +2793,11 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. - support_layer_storage[layer_idx] = smooth_outward(union_(support_layer_storage[layer_idx]), m_config.support_line_width); //FIXME was .smooth(50); + support_layer_storage[layer_idx] = smooth_outward(union_(support_layer_storage[layer_idx]), 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. - support_layer_storage[layer_idx] = polygons_simplify(support_layer_storage[layer_idx], std::min(scaled(0.03), double(m_config.resolution))); + support_layer_storage[layer_idx] = polygons_simplify(support_layer_storage[layer_idx], std::min(scaled(0.03), double(config.resolution))); // Subtract support lines of the branches from the roof SupportGeneratorLayer*& support_roof = top_contacts[layer_idx]; if (! support_roof_storage[layer_idx].empty() || support_roof != nullptr) { @@ -2653,60 +2810,60 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( if (! support_roof->polygons.empty() && area(intersection(support_layer_storage[layer_idx], support_roof->polygons)) > tiny_area_threshold) { switch (interface_pref) { - case InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT: + case InterfacePreference::InterfaceAreaOverwritesSupport: support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], support_roof->polygons); break; - case InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE: + case InterfacePreference::SupportAreaOverwritesInterface: support_roof->polygons = diff(support_roof->polygons, support_layer_storage[layer_idx]); break; //FIXME #if 1 - case InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT: - case InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE: + case InterfacePreference::InterfaceLinesOverwriteSupport: + case InterfacePreference::SupportLinesOverwriteInterface: assert(false); [[fallthrough]]; #else - case InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT: + 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( - generateSupportInfillLines(support_roof->polygons, true, layer_idx, m_config.support_roof_line_distance)), - m_config.support_roof_line_width / 2); + generate_support_infill_lines(support_roof->polygons, true, layer_idx, config.support_roof_line_distance)), + config.support_roof_line_width / 2); support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], interface_lines); break; } - case InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE: + 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( - generateSupportInfillLines(support_layer_storage[layer_idx], false, layer_idx, m_config.support_line_distance, true)), - m_config.support_line_width / 2)); + generate_support_infill_lines(support_layer_storage[layer_idx], 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: + case InterfacePreference::Nothing: break; } } } // Subtract support floors from the support area and add them to the support floor instead. - if (m_config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) { + if (config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) { SupportGeneratorLayer*& support_bottom = bottom_contacts[layer_idx]; Polygons layer_outset = diff_clipped( - m_config.support_bottom_offset > 0 ? offset(support_layer_storage[layer_idx], m_config.support_bottom_offset, jtMiter, 1.2) : support_layer_storage[layer_idx], - m_volumes.getCollision(0, layer_idx, false)); + config.support_bottom_offset > 0 ? offset(support_layer_storage[layer_idx], config.support_bottom_offset, jtMiter, 1.2) : support_layer_storage[layer_idx], + volumes.getCollision(0, layer_idx, false)); Polygons floor_layer; size_t layers_below = 0; - while (layers_below <= m_config.support_bottom_layers) { - // one sample at 0 layers below, another at m_config.support_bottom_layers. In-between samples at m_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(m_config.z_distance_bottom_layers))); + 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 < m_config.support_bottom_layers) - layers_below = std::min(layers_below + m_config.performance_interface_skip_layers, m_config.support_bottom_layers); + if (layers_below < config.support_bottom_layers) + layers_below = std::min(layers_below + config.performance_interface_skip_layers, config.support_bottom_layers); else break; } @@ -2743,8 +2900,16 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( }); } -void TreeSupport::drawAreas( +/*! + * \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, @@ -2760,10 +2925,11 @@ void TreeSupport::drawAreas( // These were fixed by creating less initial points, but i do not see a good reason to remove a working performance optimization. for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { for (SupportElement* elem : move_bounds[layer_idx]) { - if ((layer_idx > 0 && ((!inverese_tree_order.count(elem) && elem->target_height == layer_idx) || (inverese_tree_order.count(elem) && inverese_tree_order[elem]->result_on_layer == Point(-1, -1))))) // we either come from nowhere at the final layer or we had invalid parents 2. should never happen but just to be sure + // We either come from nowhere at the final layer or we had invalid parents 2. should never happen but just to be sure + if ((layer_idx > 0 && ((!inverese_tree_order.count(elem) && elem->state.target_height == layer_idx) || (inverese_tree_order.count(elem) && ! inverese_tree_order[elem]->state.result_on_layer_is_set())))) continue; for (SupportElement* par : elem->parents) - if (par->result_on_layer != Point(-1, -1)) + if (par->state.result_on_layer_is_set()) inverese_tree_order.emplace(par, elem); linear_data.emplace_back(layer_idx, elem); } @@ -2772,14 +2938,14 @@ void TreeSupport::drawAreas( std::vector> layer_tree_polygons(move_bounds.size()); // reorder the processed data by layers again. The map also could be a vector>. auto t_start = std::chrono::high_resolution_clock::now(); // Generate the circles that will be the branches. - generateBranchAreas(linear_data, layer_tree_polygons, inverese_tree_order); + generate_branch_areas(volumes, config, linear_data, layer_tree_polygons, inverese_tree_order); 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. - smoothBranchAreas(layer_tree_polygons); + smooth_branch_areas(config, layer_tree_polygons); auto t_smooth = std::chrono::high_resolution_clock::now(); // drop down all trees that connect non gracefully with the model std::vector>> dropped_down_areas(linear_data.size()); - dropNonGraciousAreas(layer_tree_polygons, linear_data, dropped_down_areas, inverese_tree_order); + drop_non_gracious_areas(volumes, layer_tree_polygons, linear_data, dropped_down_areas, inverese_tree_order); auto t_drop = std::chrono::high_resolution_clock::now(); // single threaded combining all dropped down support areas to the right layers. ONLY COPYS DATA! for (coord_t i = 0; i < static_cast(dropped_down_areas.size()); i++) @@ -2794,16 +2960,16 @@ void TreeSupport::drawAreas( size_t cnt_roofs = 0; size_t cnt_layers = 0; for (const std::pair &data_pair : this_layer_tree_polygons) - ++ (data_pair.first->missing_roof_layers > data_pair.first->distance_to_top ? cnt_roofs : cnt_layers); + ++ (data_pair.first->state.missing_roof_layers > data_pair.first->state.distance_to_top ? cnt_roofs : cnt_layers); this_roofs.reserve(this_roofs.size() + cnt_roofs); this_layers.reserve(this_layers.size() + cnt_layers); for (const std::pair &data_pair : this_layer_tree_polygons) { auto &src = const_cast(data_pair.second); - std::move(std::begin(src), std::end(src), std::back_inserter(data_pair.first->missing_roof_layers > data_pair.first->distance_to_top ? this_roofs : this_layers)); + std::move(std::begin(src), std::end(src), std::back_inserter(data_pair.first->state.missing_roof_layers > data_pair.first->state.distance_to_top ? this_roofs : this_layers)); } } - finalizeInterfaceAndSupportAreas(print_object, overhangs, support_layer_storage, support_roof_storage, + finalize_interface_and_support_areas(print_object, volumes, config, overhangs, support_layer_storage, support_roof_storage, bottom_contacts, top_contacts, intermediate_layers, layer_storage); auto t_end = std::chrono::high_resolution_clock::now(); @@ -2813,10 +2979,208 @@ void TreeSupport::drawAreas( auto dur_finalize = 0.001 * std::chrono::duration_cast(t_end - t_drop).count(); BOOST_LOG_TRIVIAL(info) << - "Time used for drawing subfuctions: generateBranchAreas: " << dur_gen_tips << " ms " - "smoothBranchAreas: " << dur_smooth << " ms " - "dropNonGraciousAreas: " << dur_drop << " ms " - "finalizeInterfaceAndSupportAreas " << dur_finalize << " ms"; + "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"; +} + +/*! + * \brief Create the areas that need support. + * + * These areas are stored inside the given SliceDataStorage object. + * \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) +{ + g_showed_critical_error = false; + g_showed_performance_warning = false; + + // Settings with the indexes of meshes that use these settings. + std::vector>> grouped_meshes = group_meshes(print, print_object_ids); + if (grouped_meshes.empty()) + return; + + size_t counter = 0; + + // Process every mesh group. These groups can not be processed parallel, as the processing in each group is parallelized, and nested parallelization is disables and slow. + for (std::pair> &processing : grouped_meshes) + { + // process each combination of meshes + // this struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generate_initial_areas() have knowledge of the existence of multiple meshes being processed. + //FIXME this is a copy + // Contains config settings to avoid loading them in every function. This was done to improve readability of the code. + const TreeSupportSettings &config = processing.first; + BOOST_LOG_TRIVIAL(info) << "Processing support tree mesh group " << counter + 1 << " of " << grouped_meshes.size() << " containing " << grouped_meshes[counter].second.size() << " meshes."; + auto t_start = std::chrono::high_resolution_clock::now(); +#if 0 + std::vector exclude(num_support_layers); + // get all already existing support areas and exclude them + tbb::parallel_for(tbb::blocked_range(0, num_support_layers), + [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + Polygons exlude_at_layer; + append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_bottom); + append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_roof); + for (auto part : storage.support.supportLayers[layer_idx].support_infill_parts) + append(exlude_at_layer, part.outline); + exclude[layer_idx] = union_(exlude_at_layer); + } + }); +#endif +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + m_progress_multiplier = 1.0 / double(grouped_meshes.size()); + m_progress_offset = counter == 0 ? 0 : TREE_PROGRESS_TOTAL * (double(counter) * m_progress_multiplier); +#endif // SLIC3R_TREESUPPORT_PROGRESS + PrintObject &print_object = *print.get_object(processing.second.front()); + // 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, +#endif // SLIC3R_TREESUPPORTS_PROGRESS + /* additional_excluded_areas */{} }; + + //FIXME generating overhangs just for the furst mesh of the group. + assert(processing.second.size() == 1); + std::vector overhangs = generate_overhangs(*print.get_object(processing.second.front())); + + // ### Precalculate avoidances, collision etc. + size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes); + if (num_support_layers == 0) + continue; + + auto t_precalc = std::chrono::high_resolution_clock::now(); + + // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas + std::vector> move_bounds(num_support_layers); + + // ### Place tips of the support tree + SupportGeneratorLayersPtr bottom_contacts(num_support_layers, nullptr); + SupportGeneratorLayersPtr top_contacts(num_support_layers, nullptr); + SupportGeneratorLayersPtr top_interface_layers(num_support_layers, nullptr); + SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr); + SupportGeneratorLayerStorage layer_storage; + + for (size_t mesh_idx : processing.second) + generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs, move_bounds, top_contacts, top_interface_layers, layer_storage); + auto t_gen = std::chrono::high_resolution_clock::now(); + +#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]) + append(polys, area->influence_area); + if (auto begin = move_bounds[layer_idx].begin(); begin != move_bounds[layer_idx].end()) + SVG::export_expolygons(debug_out_path("treesupport-initial_areas-%d.svg", layer_idx), + { { { union_ex(volumes.getWallRestriction(config.getCollisionRadius((*begin)->state), layer_idx, (*begin)->state.use_min_xy_dist)) }, + { "wall_restricrictions", "gray", 0.5f } }, + { { union_ex(polys) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); + } +#endif // TREESUPPORT_DEBUG_SVG + + // ### Propagate the influence areas downwards. This is an inherently serial operation. + create_layer_pathing(volumes, config, move_bounds); + auto t_path = std::chrono::high_resolution_clock::now(); + + // ### Set a point in each influence area + create_nodes_from_area(volumes, config, move_bounds); + auto t_place = std::chrono::high_resolution_clock::now(); + + // ### draw these points as circles + draw_areas(*print.get_object(processing.second.front()), volumes, config, overhangs, move_bounds, + bottom_contacts, top_contacts, intermediate_layers, layer_storage); + + auto t_draw = std::chrono::high_resolution_clock::now(); + auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); + auto dur_gen = 0.001 * std::chrono::duration_cast(t_gen - t_precalc).count(); + auto dur_path = 0.001 * std::chrono::duration_cast(t_path - t_gen).count(); + auto dur_place = 0.001 * std::chrono::duration_cast(t_place - t_path).count(); + auto dur_draw = 0.001 * std::chrono::duration_cast(t_draw - t_place).count(); + auto dur_total = 0.001 * std::chrono::duration_cast(t_draw - t_start).count(); + BOOST_LOG_TRIVIAL(info) << + "Total time used creating Tree support for the currently grouped meshes: " << dur_total << " ms. " + "Different subtasks:\nCalculating Avoidance: " << dur_pre_gen << " ms " + "Creating inital influence areas: " << dur_gen << " ms " + "Influence area creation: " << dur_path << "ms " + "Placement of Points in InfluenceAreas: " << dur_place << "ms " + "Drawing result as support " << dur_draw << " ms"; +// if (config.branch_radius==2121) +// BOOST_LOG_TRIVIAL(error) << "Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)"; + + for (auto &layer : move_bounds) + for (auto elem : layer) + delete elem; + + auto remove_undefined_layers = [](SupportGeneratorLayersPtr &layers) { + layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); + }; + remove_undefined_layers(bottom_contacts); + remove_undefined_layers(top_contacts); + remove_undefined_layers(intermediate_layers); + + // Produce the support G-code. + // Used by both classic and tree supports. + SupportParameters support_params(print_object); + support_params.with_sheath = true; + support_params.support_density = 0; + SupportGeneratorLayersPtr interface_layers, base_interface_layers; + SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); +#if 1 //#ifdef SLIC3R_DEBUG + SupportGeneratorLayersPtr layers_sorted = +#endif // SLIC3R_DEBUG + generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); + // Don't fill in the tree supports, make them hollow with just a single sheath line. + 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); + + #if 0 +//#ifdef SLIC3R_DEBUG + { + static int iRun = 0; + ++ iRun; + size_t layer_id = 0; + for (int i = 0; i < int(layers_sorted.size());) { + // Find the last layer with roughly the same print_z, find the minimum layer height of all. + // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. + int j = i + 1; + coordf_t zmax = layers_sorted[i]->print_z + EPSILON; + bool empty = layers_sorted[i]->polygons.empty(); + for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) + if (!layers_sorted[j]->polygons.empty()) + empty = false; + if (!empty) { + export_print_z_polygons_to_svg( + debug_out_path("support-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), + layers_sorted.data() + i, j - i); + export_print_z_polygons_and_extrusions_to_svg( + debug_out_path("support-w-fills-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), + layers_sorted.data() + i, j - i, + *print_object.support_layers()[layer_id]); + ++layer_id; + } + i = j; + } + } +#endif /* SLIC3R_DEBUG */ + + ++ counter; + } + +// storage.support.generated = true; +} + +} // namespace FFFTreeSupport + +void fff_tree_support_generate(PrintObject &print_object, std::function throw_on_cancel) +{ + size_t idx = 0; + for (PrintObject* po : print_object.print()->objects()) { + if (po == &print_object) + break; + ++idx; + } + FFFTreeSupport::generate_support_areas(*print_object.print(), BuildVolume(Pointfs{ Vec2d{ -300., -300. }, Vec2d{ -300., +300. }, Vec2d{ +300., +300. }, Vec2d{ +300., -300. } }, 0.), { idx }, throw_on_cancel); } } // namespace Slic3r diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 579835686..26ab7fc84 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -12,15 +12,13 @@ #include "TreeModelVolumes.hpp" #include "Point.hpp" -#include // For combining hashes +#include #include "BoundingBox.hpp" #include "Utils.hpp" #define TREE_SUPPORT_SHOW_ERRORS -#define SUPPORT_TREE_CIRCLE_RESOLUTION 25 // The number of vertices in each circle. - #ifdef SLIC3R_TREESUPPORTS_PROGRESS // The various stages of the process can be weighted differently in the progress bar. // These weights are obtained experimentally using a small sample size. Sensible weights can differ drastically based on the assumed default settings and model. @@ -35,842 +33,529 @@ #define TREE_PROGRESS_FINALIZE_BRANCH_AREAS TREE_PROGRESS_DRAW_AREAS / 3 #endif // SLIC3R_TREESUPPORTS_PROGRESS -#define SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL false -#define SUPPORT_TREE_AVOID_SUPPORT_BLOCKER true - namespace Slic3r { +// Forward declarations +class Print; +class PrintObject; +class SupportGeneratorLayer; +using SupportGeneratorLayerStorage = std::deque; +using SupportGeneratorLayersPtr = std::vector; + +namespace FFFTreeSupport +{ + using LayerIndex = int; static constexpr const double SUPPORT_TREE_EXPONENTIAL_FACTOR = 1.5; static constexpr const coord_t SUPPORT_TREE_EXPONENTIAL_THRESHOLD = scaled(1. * SUPPORT_TREE_EXPONENTIAL_FACTOR); static constexpr const coord_t SUPPORT_TREE_COLLISION_RESOLUTION = scaled(0.5); -//FIXME -class Print; -class PrintObject; -class SupportGeneratorLayer; -using SupportGeneratorLayerStorage = std::deque; -using SupportGeneratorLayersPtr = std::vector; -/*! - * \brief Generates a tree structure to support your models. - */ +// The number of vertices in each circle. +static constexpr const size_t SUPPORT_TREE_CIRCLE_RESOLUTION = 25; +static constexpr const bool SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL = false; +static constexpr const bool SUPPORT_TREE_AVOID_SUPPORT_BLOCKER = true; -class TreeSupport +enum class InterfacePreference { -public: - using AvoidanceType = TreeModelVolumes::AvoidanceType; - enum class InterfacePreference - { - INTERFACE_AREA_OVERWRITES_SUPPORT, - SUPPORT_AREA_OVERWRITES_INTERFACE, - INTERFACE_LINES_OVERWRITE_SUPPORT, - SUPPORT_LINES_OVERWRITE_INTERFACE, - NOTHING - }; - - /*! - * \brief Creates an instance of the tree support generator. - */ - TreeSupport() = default; - - /*! - * \brief Create the areas that need support. - * - * These areas are stored inside the given SliceDataStorage object. - * \param storage The data storage where the mesh data is gotten from and - * where the resulting support areas are stored. - */ - void generateSupportAreas(Print &print, const BuildVolume &build_volume, const std::vector& print_object_ids); - void generateSupportAreas(PrintObject &print_object); - - //todo Remove! Only relevant for public BETA! - static bool inline showed_critical=false; - static bool inline showed_performance=false; - static void showError(std::string message,bool critical); - - struct TreeSupportSettings; // forward declaration as we need some config values in the merge case - - struct AreaIncreaseSettings - { - AvoidanceType type { AvoidanceType::Fast }; - coord_t increase_speed { 0 }; - bool increase_radius { false }; - bool no_error { false }; - bool use_min_distance { false }; - bool move { false }; - bool operator==(const AreaIncreaseSettings& other) const - { - return increase_radius == other.increase_radius && increase_speed == other.increase_speed && type == other.type && - no_error == other.no_error && use_min_distance == other.use_min_distance && move == other.move; - } - }; - - struct SupportElement - { - explicit SupportElement( - coord_t distance_to_top, size_t target_height, Point target_position, bool to_buildplate, bool to_model_gracious, bool use_min_xy_dist, size_t dont_move_until, - bool supports_roof, bool can_use_safe_radius, bool force_tips_to_roof, bool skip_ovalisation, Polygons &&newArea) : - target_height(target_height), target_position(target_position), next_position(target_position), next_height(target_height), effective_radius_height(distance_to_top), - to_buildplate(to_buildplate), distance_to_top(distance_to_top), result_on_layer(target_position), increased_to_model_radius(0), to_model_gracious(to_model_gracious), - elephant_foot_increases(0), use_min_xy_dist(use_min_xy_dist), supports_roof(supports_roof), dont_move_until(dont_move_until), can_use_safe_radius(can_use_safe_radius), - last_area_increase(AreaIncreaseSettings{ AvoidanceType::Fast, 0, false, false, false, false }), missing_roof_layers(force_tips_to_roof ? dont_move_until : 0), skip_ovalisation(skip_ovalisation), - area(std::move(newArea)) - { - } - - explicit SupportElement(const SupportElement& elem, Polygons &&newArea) - : // copy constructor with possibility to set a new area - target_height(elem.target_height), - target_position(elem.target_position), - next_position(elem.next_position), - next_height(elem.next_height), - effective_radius_height(elem.effective_radius_height), - to_buildplate(elem.to_buildplate), - distance_to_top(elem.distance_to_top), - area(std::move(newArea)), - result_on_layer(elem.result_on_layer), - increased_to_model_radius(elem.increased_to_model_radius), - to_model_gracious(elem.to_model_gracious), - elephant_foot_increases(elem.elephant_foot_increases), - use_min_xy_dist(elem.use_min_xy_dist), - supports_roof(elem.supports_roof), - dont_move_until(elem.dont_move_until), - can_use_safe_radius(elem.can_use_safe_radius), - last_area_increase(elem.last_area_increase), - missing_roof_layers(elem.missing_roof_layers), - skip_ovalisation(elem.skip_ovalisation) - - { - parents.insert(parents.begin(), elem.parents.begin(), elem.parents.end()); - } - - /*! - * \brief Create a new Element for one layer below the element of the pointer supplied. - */ - - explicit SupportElement(SupportElement* element_above) - : target_height(element_above->target_height), - target_position(element_above->target_position), - next_position(element_above->next_position), - next_height(element_above->next_height), - effective_radius_height(element_above->effective_radius_height), - to_buildplate(element_above->to_buildplate), - distance_to_top(element_above->distance_to_top + 1), - area(element_above->area), - result_on_layer(Point(-1, -1)), // set to invalid as we are a new node on a new layer - increased_to_model_radius(element_above->increased_to_model_radius), - to_model_gracious(element_above->to_model_gracious), - elephant_foot_increases(element_above->elephant_foot_increases), - use_min_xy_dist(element_above->use_min_xy_dist), - supports_roof(element_above->supports_roof), - dont_move_until(element_above->dont_move_until), - can_use_safe_radius(element_above->can_use_safe_radius), - last_area_increase(element_above->last_area_increase), - missing_roof_layers(element_above->missing_roof_layers), - skip_ovalisation(false) - { - parents = { element_above }; - } - - // ONLY to be called in merge as it assumes a few assurances made by it. - explicit SupportElement( - const SupportElement& first, const SupportElement& second, size_t next_height, Point next_position, - coord_t increased_to_model_radius, const TreeSupportSettings& config) : - next_position(next_position), next_height(next_height), increased_to_model_radius(increased_to_model_radius), - use_min_xy_dist(first.use_min_xy_dist || second.use_min_xy_dist), supports_roof(first.supports_roof || second.supports_roof), - dont_move_until(std::max(first.dont_move_until, second.dont_move_until)), can_use_safe_radius(first.can_use_safe_radius || second.can_use_safe_radius), - missing_roof_layers(std::min(first.missing_roof_layers, second.missing_roof_layers)), skip_ovalisation(false) - - { - if (first.target_height > second.target_height) { - target_height = first.target_height; - target_position = first.target_position; - } else { - target_height = second.target_height; - target_position = second.target_position; - } - effective_radius_height = std::max(first.effective_radius_height, second.effective_radius_height); - distance_to_top = std::max(first.distance_to_top, second.distance_to_top); - - to_buildplate = first.to_buildplate && second.to_buildplate; - to_model_gracious = first.to_model_gracious && second.to_model_gracious; // valid as we do not merge non-gracious with gracious - - AddParents(first.parents); - AddParents(second.parents); - - elephant_foot_increases = 0; - if (config.diameter_scale_bp_radius > 0) { - coord_t foot_increase_radius = std::abs(std::max(config.getCollisionRadius(second), config.getCollisionRadius(first)) - config.getCollisionRadius(*this)); - // 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. - elephant_foot_increases = foot_increase_radius / (config.branch_radius * (config.diameter_scale_bp_radius - config.diameter_angle_scale_factor)); - } - - // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. - last_area_increase = { - std::min(first.last_area_increase.type, second.last_area_increase.type), - std::min(first.last_area_increase.increase_speed, second.last_area_increase.increase_speed), - first.last_area_increase.increase_radius || second.last_area_increase.increase_radius, - first.last_area_increase.no_error || second.last_area_increase.no_error, - first.last_area_increase.use_min_distance && second.last_area_increase.use_min_distance, - first.last_area_increase.move || second.last_area_increase.move }; - } - - /*! - * \brief The layer this support elements wants reach - */ - LayerIndex target_height; - - /*! - * \brief The position this support elements wants to support on layer=target_height - */ - Point target_position; - - /*! - * \brief The next position this support elements wants to reach. NOTE: This is mainly a suggestion regarding direction inside the influence area. - */ - Point next_position; - - - /*! - * \brief The next height this support elements wants to reach - */ - LayerIndex next_height; - - /*! - * \brief The Effective distance to top of this element regarding radius increases and collision calculations. - */ - - size_t effective_radius_height; - - /*! - * \brief The element trys to reach the buildplate - */ - - bool to_buildplate; - - /*! - * \brief All elements in the layer above the current one that are supported by this element - */ - std::vector parents; - - /*! - * \brief The amount of layers this element is below the topmost layer of this branch. - */ - size_t distance_to_top; - - /*! - * \brief The resulting influence area. - * Will only be set in the results of createLayerPathing, and will be nullptr inside! - */ - Polygons area; - - /*! - * \brief The resulting center point around which a circle will be drawn later. - * Will be set by setPointsOnAreas - */ - Point result_on_layer = Point(-1, -1); - /*! - * \brief The amount of extra radius we got from merging branches that could have reached the buildplate, but merged with ones that can not. - */ - coord_t increased_to_model_radius; // how much to model we increased only relevant for merging - /*! - * \brief Will the branch be able to rest completely on a flat surface, be it buildplate or model ? - */ - bool to_model_gracious; - - /*! - * \brief Counter about the times the elephant foot was increased. Can be fractions for merge reasons. - */ - double elephant_foot_increases; - - /*! - * \brief Whether the min_xy_distance can be used to get avoidance or similar. Will only be true if support_xy_overrides_z=Z overrides X/Y. - */ - bool use_min_xy_dist; - - /*! - * \brief True if this Element or any parent provides support to a support roof. - */ - bool supports_roof; - - /*! - * \brief The element trys not to move until this dtt is reached, is set to 0 if the element had to move. - */ - size_t dont_move_until; - - /*! - * \brief An influence area is considered safe when it can use the holefree avoidance <=> It will not have to encounter holes on its way downward. - */ - bool can_use_safe_radius; - - /*! - * \brief Settings used to increase the influence area to its current state. - */ - AreaIncreaseSettings last_area_increase; - - /*! - * \brief Amount of roof layers that were not yet added, because the branch needed to move. - */ - size_t missing_roof_layers; - - /*! - * \brief Skip the ovalisation to parent and children when generating the final circles. - */ - bool skip_ovalisation; - - bool operator==(const SupportElement& other) const - { - return target_position == other.target_position && target_height == other.target_height; - } - - bool operator<(const SupportElement& other) const // true if me < other - { - return !(*this == other) && !(*this > other); - } - bool operator>(const SupportElement& other) const - { - // Doesn't really have to make sense, only required for ordering in maps to ensure deterministic behavior. - if (*this == other) - return false; - if (other.target_height != target_height) - return other.target_height < target_height; - return other.target_position.x() == target_position.x() ? other.target_position.y() < target_position.y() : other.target_position.x() < target_position.x(); - } - - void AddParents(const std::vector& adding) - { - for (SupportElement* ptr : adding) - { - parents.emplace_back(ptr); - } - } - }; - - /*! - * \brief This struct contains settings used in the tree support. Thanks to this most functions do not need to know of meshes etc. Also makes the code shorter. - */ - struct TreeSupportSettings - { - TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupport class. - - explicit TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings) - : angle(mesh_group_settings.support_tree_angle), - angle_slow(mesh_group_settings.support_tree_angle_slow), - support_line_width(mesh_group_settings.support_line_width), - layer_height(mesh_group_settings.layer_height), - branch_radius(mesh_group_settings.support_tree_branch_diameter / 2), - min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance - maximum_move_distance((angle < M_PI / 2.) ? (coord_t)(tan(angle) * layer_height) : std::numeric_limits::max()), - maximum_move_distance_slow((angle_slow < M_PI / 2.) ? (coord_t)(tan(angle_slow) * layer_height) : std::numeric_limits::max()), - support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0), - tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // Ensure lines always stack nicely even if layer height is large - diameter_angle_scale_factor(sin(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height / branch_radius), - max_to_model_radius_increase(mesh_group_settings.support_tree_max_diameter_increase_by_merges_when_support_to_model / 2), - min_dtt_to_model(round_up_divide(mesh_group_settings.support_tree_min_height_to_model, layer_height)), - increase_radius_until_radius(mesh_group_settings.support_tree_branch_diameter / 2), - increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / (branch_radius * diameter_angle_scale_factor)), - support_rests_on_model(! mesh_group_settings.support_material_buildplate_only), - xy_distance(mesh_group_settings.support_xy_distance), - xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)), - bp_radius(mesh_group_settings.support_tree_bp_diameter / 2), - diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. - z_distance_top_layers(round_up_divide(mesh_group_settings.support_top_distance, layer_height)), - z_distance_bottom_layers(round_up_divide(mesh_group_settings.support_bottom_distance, layer_height)), - performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)), -// support_infill_angles(mesh_group_settings.support_infill_angles), - support_roof_angles(mesh_group_settings.support_roof_angles), - roof_pattern(mesh_group_settings.support_roof_pattern), - support_pattern(mesh_group_settings.support_pattern), - support_roof_line_width(mesh_group_settings.support_roof_line_width), - support_line_spacing(mesh_group_settings.support_line_spacing), - support_bottom_offset(mesh_group_settings.support_bottom_offset), - support_wall_count(mesh_group_settings.support_wall_count), - resolution(mesh_group_settings.resolution), - support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. - settings(mesh_group_settings), - min_feature_size(mesh_group_settings.min_feature_size) - - - { - layer_start_bp_radius = (bp_radius - branch_radius) / (branch_radius * diameter_scale_bp_radius); - - if (TreeSupport::TreeSupportSettings::soluble) { - // safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely - // When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size - // This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance. - xy_min_distance = std::max(xy_min_distance, scaled(0.1)); - xy_distance = std::max(xy_distance, xy_min_distance); - } - - -// const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE }, { "interface_area_overwrite_support_area", InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT }, { "support_lines_overwrite_interface_area", InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE }, { "interface_lines_overwrite_support_area", InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT }, { "nothing", InterfacePreference::NOTHING } }; -// interface_preference = interface_map.at(mesh_group_settings.get("support_interface_priority")); -//FIXME this was the default -// interface_preference = InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; - interface_preference = InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE; - } - - private: - double angle; - double angle_slow; - std::vector known_z; - - public: - // some static variables dependent on other meshes that are not currently processed. - // Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy. - inline static bool soluble = false; - /*! - * \brief Width of a single line of support. - */ - coord_t support_line_width; - /*! - * \brief Height of a single layer - */ - coord_t layer_height; - /*! - * \brief Radius of a branch when it has left the tip. - */ - coord_t branch_radius; - /*! - * \brief smallest allowed radius, required to ensure that even at DTT 0 every circle will still be printed - */ - coord_t min_radius; - /*! - * \brief How far an influence area may move outward every layer at most. - */ - coord_t maximum_move_distance; - /*! - * \brief How far every influence area will move outward every layer if possible. - */ - coord_t maximum_move_distance_slow; - /*! - * \brief Amount of bottom layers. 0 if disabled. - */ - size_t support_bottom_layers; - /*! - * \brief Amount of effectiveDTT increases are required to reach branch radius. - */ - size_t tip_layers; - /*! - * \brief Factor by which to increase the branch radius. - */ - double diameter_angle_scale_factor; - /*! - * \brief How much a branch resting on the model may grow in radius by merging with branches that can reach the buildplate. - */ - coord_t max_to_model_radius_increase; - /*! - * \brief If smaller (in layers) than that, all branches to model will be deleted - */ - size_t min_dtt_to_model; - /*! - * \brief Increase radius in the resulting drawn branches, even if the avoidance does not allow it. Will be cut later to still fit. - */ - coord_t increase_radius_until_radius; - /*! - * \brief Same as increase_radius_until_radius, but contains the DTT at which the radius will be reached. - */ - size_t increase_radius_until_layer; - /*! - * \brief True if the branches may connect to the model. - */ - bool support_rests_on_model; - /*! - * \brief How far should support be from the model. - */ - coord_t xy_distance; - /*! - * \brief Radius a branch should have when reaching the buildplate. - */ - coord_t bp_radius; - /*! - * \brief The layer index at which an increase in radius may be required to reach the bp_radius. - */ - coord_t layer_start_bp_radius; - /*! - * \brief Factor by which to increase the branch radius to reach the required bp_radius at layer 0. Note that this radius increase will not happen in the tip, to ensure the tip is structurally sound. - */ - double diameter_scale_bp_radius; - /*! - * \brief minimum xy_distance. Only relevant when Z overrides XY, otherwise equal to xy_distance- - */ - coord_t xy_min_distance; - /*! - * \brief Amount of layers distance required the top of the support to the model - */ - size_t z_distance_top_layers; - /*! - * \brief Amount of layers distance required from the top of the model to the bottom of a support structure. - */ - size_t z_distance_bottom_layers; - /*! - * \brief used for performance optimization at the support floor. Should have no impact on the resulting tree. - */ - size_t performance_interface_skip_layers; - /*! - * \brief User specified angles for the support infill. - */ -// std::vector support_infill_angles; - /*! - * \brief User specified angles for the support roof infill. - */ - std::vector support_roof_angles; - /*! - * \brief Pattern used in the support roof. May contain non relevant data if support roof is disabled. - */ - SupportMaterialInterfacePattern roof_pattern; - /*! - * \brief Pattern used in the support infill. - */ - SupportMaterialPattern support_pattern; - /*! - * \brief Line width of the support roof. - */ - coord_t support_roof_line_width; - /*! - * \brief Distance between support infill lines. - */ - coord_t support_line_spacing; - /*! - * \brief Offset applied to the support floor area. - */ - coord_t support_bottom_offset; - /* - * \brief Amount of walls the support area will have. - */ - int support_wall_count; - /* - * \brief Maximum allowed deviation when simplifying. - */ - coord_t resolution; - /* - * \brief Distance between the lines of the roof. - */ - coord_t support_roof_line_distance; - /* - * \brief How overlaps of an interface area with a support area should be handled. - */ - InterfacePreference interface_preference; - - /* - * \brief The infill class wants a settings object. This one will be the correct one for all settings it uses. - */ - TreeSupportMeshGroupSettings settings; - - /* - * \brief Minimum thickness of any model features. - */ - coord_t min_feature_size; - - public: - bool operator==(const TreeSupportSettings& other) const - { - return branch_radius == other.branch_radius && tip_layers == other.tip_layers && diameter_angle_scale_factor == other.diameter_angle_scale_factor && layer_start_bp_radius == other.layer_start_bp_radius && bp_radius == other.bp_radius && diameter_scale_bp_radius == other.diameter_scale_bp_radius && min_radius == other.min_radius && xy_min_distance == other.xy_min_distance && // as a recalculation of the collision areas is required to set a new min_radius. - xy_distance - xy_min_distance == other.xy_distance - other.xy_min_distance && // if the delta of xy_min_distance and xy_distance is different the collision areas have to be recalculated. - support_rests_on_model == other.support_rests_on_model && increase_radius_until_layer == other.increase_radius_until_layer && min_dtt_to_model == other.min_dtt_to_model && max_to_model_radius_increase == other.max_to_model_radius_increase && maximum_move_distance == other.maximum_move_distance && maximum_move_distance_slow == other.maximum_move_distance_slow && z_distance_bottom_layers == other.z_distance_bottom_layers && support_line_width == other.support_line_width && - support_line_spacing == other.support_line_spacing && support_roof_line_width == other.support_roof_line_width && // can not be set on a per-mesh basis currently, so code to enable processing different roof line width in the same iteration seems useless. - support_bottom_offset == other.support_bottom_offset && support_wall_count == other.support_wall_count && support_pattern == other.support_pattern && roof_pattern == other.roof_pattern && // can not be set on a per-mesh basis currently, so code to enable processing different roof patterns in the same iteration seems useless. - support_roof_angles == other.support_roof_angles && - //support_infill_angles == other.support_infill_angles && - increase_radius_until_radius == other.increase_radius_until_radius && support_bottom_layers == other.support_bottom_layers && layer_height == other.layer_height && z_distance_top_layers == other.z_distance_top_layers && resolution == other.resolution && // Infill generation depends on deviation and resolution. - support_roof_line_distance == other.support_roof_line_distance && interface_preference == other.interface_preference - && min_feature_size == other.min_feature_size // interface_preference should be identical to ensure the tree will correctly interact with the roof. - // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry -#if 0 - && (interface_preference == InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT || interface_preference == InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE - // Perimeter generator parameters - || - (settings.get("fill_outline_gaps") == other.settings.get("fill_outline_gaps") && - settings.get("min_bead_width") == other.settings.get("min_bead_width") && - settings.get("wall_transition_angle") == other.settings.get("wall_transition_angle") && - settings.get("wall_transition_length") == other.settings.get("wall_transition_length") && - settings.get("wall_split_middle_threshold") == other.settings.get("wall_split_middle_threshold") && - settings.get("wall_add_middle_threshold") == other.settings.get("wall_add_middle_threshold") && - settings.get("wall_distribution_count") == other.settings.get("wall_distribution_count") && - settings.get("wall_transition_filter_distance") == other.settings.get("wall_transition_filter_distance") && - settings.get("wall_transition_filter_deviation") == other.settings.get("wall_transition_filter_deviation") && - settings.get("wall_line_width_x") == other.settings.get("wall_line_width_x") && - settings.get("meshfix_maximum_extrusion_area_deviation") == other.settings.get("meshfix_maximum_extrusion_area_deviation")) - ) -#endif - ; - } - - - /*! - * \brief Get the Distance to top regarding the real radius this part will have. This is different from distance_to_top, which is can be used to calculate the top most layer of the branch. - * \param elem[in] The SupportElement one wants to know the effectiveDTT - * \return The Effective DTT. - */ - [[nodiscard]] inline size_t getEffectiveDTT(const TreeSupport::SupportElement& elem) const - { - return elem.effective_radius_height < increase_radius_until_layer ? (elem.distance_to_top < increase_radius_until_layer ? elem.distance_to_top : increase_radius_until_layer) : elem.effective_radius_height; - } - - /*! - * \brief Get the Radius part will have based on numeric values. - * \param distance_to_top[in] The effective distance_to_top of the element - * \param elephant_foot_increases[in] The elephant_foot_increases of the element. - * \return The radius an element with these attributes would have. - */ - [[nodiscard]] inline coord_t getRadius(size_t distance_to_top, const double elephant_foot_increases = 0) const - { - return (distance_to_top <= tip_layers ? min_radius + (branch_radius - min_radius) * distance_to_top / tip_layers : // tip - branch_radius + // base - branch_radius * (distance_to_top - tip_layers) * diameter_angle_scale_factor) - + // gradual increase - branch_radius * elephant_foot_increases * (std::max(diameter_scale_bp_radius - diameter_angle_scale_factor, 0.0)); - } - - /*! - * \brief Get the Radius, that this element will have. - * \param elem[in] The Element. - * \return The radius the element has. - */ - [[nodiscard]] inline coord_t getRadius(const TreeSupport::SupportElement& elem) const - { - return getRadius(getEffectiveDTT(elem), elem.elephant_foot_increases); - } - - /*! - * \brief Get the collision Radius of this Element. This can be smaller then the actual radius, as the drawAreas will cut off areas that may collide with the model. - * \param elem[in] The Element. - * \return The collision radius the element has. - */ - [[nodiscard]] inline coord_t getCollisionRadius(const TreeSupport::SupportElement& elem) const - { - return getRadius(elem.effective_radius_height, elem.elephant_foot_increases); - } - - /*! - * \brief Get the Radius an element should at least have at a given layer. - * \param layer_idx[in] The layer. - * \return The radius every element should aim to achieve. - */ - [[nodiscard]] inline coord_t recommendedMinRadius(LayerIndex layer_idx) const - { - double scale = (layer_start_bp_radius - int(layer_idx)) * diameter_scale_bp_radius; - return scale > 0 ? branch_radius + branch_radius * scale : 0; - } - - /*! - * \brief Return on which z in microns the layer will be printed. Used only for support infill line generation. - * \param layer_idx[in] The layer. - * \return The radius every element should aim to achieve. - */ - [[nodiscard]] inline coord_t getActualZ(LayerIndex layer_idx) - { - return layer_idx < coord_t(known_z.size()) ? known_z[layer_idx] : (layer_idx - known_z.size()) * layer_height + known_z.size() ? known_z.back() : 0; - } - - /*! - * \brief Set the z every Layer is printed at. Required for getActualZ to work - * \param z[in] The z every LayerIndex is printed. Vector is used as a map with the index of each element being the corresponding LayerIndex - * \return The radius every element should aim to achieve. - */ - void setActualZ(std::vector& z) - { - known_z = z; - } - }; - -private: - /*! - * \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. - */ - void generateInitialAreas(const PrintObject &print_object, - const std::vector &overhangs, - std::vector> &move_bounds, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &top_interface_layers, - SupportGeneratorLayerStorage &layer_storage); - - /*! - * \brief Checks if an influence area contains a valid subsection and returns the corresponding metadata and the new Influence area. - * - * Calculates an influence areas of the layer below, based on the influence area of one element on the current layer. - * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in we would change our gracious or to_buildplate status the influence areas are instead increased by maximum_move_distance_slow. - * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. - * - * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. - * - * \param settings[in] Which settings have to be used to check validity. - * \param layer_idx[in] Number of the current layer. - * \param parent[in] The metadata of the parents influence area. - * \param relevant_offset[in] The maximal possible influence area. No guarantee regarding validity with current layer collision required, as it is ensured in-function! - * \param to_bp_data[out] The part of the Influence area that can reach the buildplate. - * \param to_model_data[out] The part of the Influence area that do not have to reach the buildplate. This has overlap with new_layer_data. - * \param increased[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. - * \param overspeed[in] How much should the already offset area be offset again. Usually this is 0. - * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. - * \return A valid support element for the next layer regarding the calculated influence areas. Empty if no influence are can be created using the supplied influence area and settings. - */ - std::optional increaseSingleArea(AreaIncreaseSettings settings, LayerIndex layer_idx, SupportElement* parent, const Polygons& relevant_offset, Polygons& to_bp_data, Polygons& to_model_data, Polygons& increased, const coord_t overspeed, const bool mergelayer); - /*! - * \brief Increases influence areas as far as required. - * - * Calculates influence areas of the layer below, based on the influence areas of the current layer. - * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in it would change the gracious or to_buildplate status, the influence areas are instead increased by maximum_move_distance. - * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. - * - * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. - * - * \param to_bp_areas[out] Influence areas that can reach the buildplate - * \param to_model_areas[out] Influence areas that do not have to reach the buildplate. This has overlap with new_layer_data, as areas that can reach the buildplate are also considered valid areas to the model. - * This redundancy is required if a to_buildplate influence area is allowed to merge with a to model influence area. - * \param influence_areas[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. - * \param bypass_merge_areas[out] Influence areas ready to be added to the layer below that do not need merging. - * \param last_layer[in] Influence areas of the current layer. - * \param layer_idx[in] Number of the current layer. - * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. - */ - void increaseAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, std::vector& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, const bool mergelayer); - - /*! - * \brief Propagates influence downwards, and merges overlapping ones. - * - * \param move_bounds[in,out] All currently existing influence areas - */ - void createLayerPathing(std::vector>& move_bounds); - - - /*! - * \brief Sets the result_on_layer for all parents based on the SupportElement supplied. - * - * \param elem[in] The SupportElements, which parent's position should be determined. - */ - void setPointsOnAreas(const SupportElement* elem); - /*! - * \brief Get the best point to connect to the model and set the result_on_layer of the relevant SupportElement accordingly. - * - * \param move_bounds[in,out] All currently existing influence areas - * \param first_elem[in,out] SupportElement that did not have its result_on_layer set meaning that it does not have a child element. - * \param layer_idx[in] The current layer. - * \return Should elem be deleted. - */ - bool setToModelContact(std::vector>& move_bounds, SupportElement* first_elem, const LayerIndex layer_idx); - - /*! - * \brief Set the result_on_layer point for all influence areas - * - * \param move_bounds[in,out] All currently existing influence areas - */ - void createNodesFromArea(std::vector>& move_bounds); - - /*! - * \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. - */ - void generateBranchAreas(std::vector>& linear_data, std::vector>& layer_tree_polygons, const std::map& inverse_tree_order); - - /*! - * \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. - */ - void smoothBranchAreas(std::vector>& layer_tree_polygons); - - /*! - * \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. - */ - void dropNonGraciousAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, const std::map& inverse_tree_order); - - /*! - * \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. - */ - void finalizeInterfaceAndSupportAreas( - const PrintObject &print_object, - 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); - - /*! - * \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. - */ - void drawAreas( - PrintObject &print_object, - const std::vector &overhangs, - std::vector> &move_bounds, - - SupportGeneratorLayersPtr &bottom_contacts, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage); - - /*! - * \brief Settings with the indexes of meshes that use these settings. - * - */ - std::vector>> m_grouped_meshes; - - /*! - * \brief Generator for model collision, avoidance and internal guide volumes. - * - */ - TreeModelVolumes m_volumes; - - /*! - * \brief Contains config settings to avoid loading them in every function. This was done to improve readability of the code. - */ - TreeSupportSettings m_config; - - /*! - * \brief The progress multiplier of all values added progress bar. - * Required for the progress bar the behave as expected when areas have to be calculated multiple times - */ - double m_progress_multiplier = 1; - - /*! - * \brief The progress offset added to all values communicated to the progress bar. - * Required for the progress bar the behave as expected when areas have to be calculated multiple times - */ - double m_progress_offset = 0; + InterfaceAreaOverwritesSupport, + SupportAreaOverwritesInterface, + InterfaceLinesOverwriteSupport, + SupportLinesOverwriteInterface, + Nothing }; +struct AreaIncreaseSettings +{ + AreaIncreaseSettings( + TreeModelVolumes::AvoidanceType type = TreeModelVolumes::AvoidanceType::Fast, coord_t increase_speed = 0, + bool increase_radius = false, bool no_error = false, bool use_min_distance = false, bool move = false) : + increase_speed{ increase_speed }, type{ type }, increase_radius{ increase_radius }, no_error{ no_error }, use_min_distance{ use_min_distance }, move{ move } {} + + coord_t increase_speed; + // Packing for smaller memory footprint of SupportElementState && SupportElementMerging + TreeModelVolumes::AvoidanceType type; + bool increase_radius : 1; + bool no_error : 1; + bool use_min_distance : 1; + bool move : 1; + bool operator==(const AreaIncreaseSettings& other) const + { + return type == other.type && + increase_speed == other.increase_speed && + increase_radius == other.increase_radius && + no_error == other.no_error && + use_min_distance == other.use_min_distance && + move == other.move; + } +}; + +struct TreeSupportSettings; + +struct SupportElementID +{ + /*! + * \brief The layer this support elements wants reach + */ + LayerIndex target_height; + + /*! + * \brief The position this support elements wants to support on layer=target_height + */ + Point target_position; +}; + +struct SupportElementState : public SupportElementID +{ + /*! + * \brief The next position this support elements wants to reach. NOTE: This is mainly a suggestion regarding direction inside the influence area. + */ + Point next_position; + + /*! + * \brief The next height this support elements wants to reach + */ + LayerIndex next_height; + + /*! + * \brief The Effective distance to top of this element regarding radius increases and collision calculations. + */ + uint32_t effective_radius_height; + + /*! + * \brief The amount of layers this element is below the topmost layer of this branch. + */ + uint32_t distance_to_top; + + /*! + * \brief The resulting center point around which a circle will be drawn later. + * Will be set by setPointsOnAreas + */ + Point result_on_layer { std::numeric_limits::max(), std::numeric_limits::max() }; + bool result_on_layer_is_set() const { return this->result_on_layer != Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } + void result_on_layer_reset() { this->result_on_layer = Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } + /*! + * \brief The amount of extra radius we got from merging branches that could have reached the buildplate, but merged with ones that can not. + */ + coord_t increased_to_model_radius; // how much to model we increased only relevant for merging + + /*! + * \brief Counter about the times the elephant foot was increased. Can be fractions for merge reasons. + */ + 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. + */ + uint32_t dont_move_until; + + /*! + * \brief Settings used to increase the influence area to its current state. + */ + AreaIncreaseSettings last_area_increase; + + /*! + * \brief Amount of roof layers that were not yet added, because the branch needed to move. + */ + uint32_t missing_roof_layers; + + /*! + * \brief The element trys to reach the buildplate + */ + bool to_buildplate : 1; + + /*! + * \brief Will the branch be able to rest completely on a flat surface, be it buildplate or model ? + */ + bool to_model_gracious : 1; + + /*! + * \brief Whether the min_xy_distance can be used to get avoidance or similar. Will only be true if support_xy_overrides_z=Z overrides X/Y. + */ + bool use_min_xy_dist : 1; + + /*! + * \brief True if this Element or any parent provides support to a support roof. + */ + bool supports_roof : 1; + + /*! + * \brief An influence area is considered safe when it can use the holefree avoidance <=> It will not have to encounter holes on its way downward. + */ + bool can_use_safe_radius : 1; + + /*! + * \brief Skip the ovalisation to parent and children when generating the final circles. + */ + bool skip_ovalisation : 1; + + // called by increase_single_area() and increaseAreas() + [[nodiscard]] static SupportElementState propagate_down(const SupportElementState &src) + { + SupportElementState dst{ src }; + ++ dst.distance_to_top; + // set to invalid as we are a new node on a new layer + dst.result_on_layer_reset(); + dst.skip_ovalisation = false; + return dst; + } +}; + +struct SupportElement +{ +// SupportElement(const SupportElementState &state) : SupportElementState(state) {} + SupportElement(const SupportElementState &state, Polygons &&influence_area) : state(state), influence_area(std::move(influence_area)) {} + SupportElement(const SupportElementState &state, boost::container::small_vector &&parents, Polygons &&influence_area) : + state(state), parents(std::move(parents)), influence_area(std::move(influence_area)) {} + + SupportElementState state; + /*! + * \brief All elements in the layer above the current one that are supported by this element + */ + boost::container::small_vector parents; + + /*! + * \brief The resulting influence area. + * Will only be set in the results of createLayerPathing, and will be nullptr inside! + */ + Polygons influence_area; +}; + +/*! + * \brief This struct contains settings used in the tree support. Thanks to this most functions do not need to know of meshes etc. Also makes the code shorter. + */ +struct TreeSupportSettings +{ + TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupportGenerator class. + + explicit TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings) + : angle(mesh_group_settings.support_tree_angle), + angle_slow(mesh_group_settings.support_tree_angle_slow), + support_line_width(mesh_group_settings.support_line_width), + layer_height(mesh_group_settings.layer_height), + branch_radius(mesh_group_settings.support_tree_branch_diameter / 2), + min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance + maximum_move_distance((angle < M_PI / 2.) ? (coord_t)(tan(angle) * layer_height) : std::numeric_limits::max()), + maximum_move_distance_slow((angle_slow < M_PI / 2.) ? (coord_t)(tan(angle_slow) * layer_height) : std::numeric_limits::max()), + support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0), + tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // Ensure lines always stack nicely even if layer height is large + diameter_angle_scale_factor(sin(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height / branch_radius), + max_to_model_radius_increase(mesh_group_settings.support_tree_max_diameter_increase_by_merges_when_support_to_model / 2), + min_dtt_to_model(round_up_divide(mesh_group_settings.support_tree_min_height_to_model, layer_height)), + increase_radius_until_radius(mesh_group_settings.support_tree_branch_diameter / 2), + increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / (branch_radius * diameter_angle_scale_factor)), + support_rests_on_model(! mesh_group_settings.support_material_buildplate_only), + xy_distance(mesh_group_settings.support_xy_distance), + xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)), + bp_radius(mesh_group_settings.support_tree_bp_diameter / 2), + diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. + z_distance_top_layers(round_up_divide(mesh_group_settings.support_top_distance, layer_height)), + z_distance_bottom_layers(round_up_divide(mesh_group_settings.support_bottom_distance, layer_height)), + performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)), +// support_infill_angles(mesh_group_settings.support_infill_angles), + support_roof_angles(mesh_group_settings.support_roof_angles), + roof_pattern(mesh_group_settings.support_roof_pattern), + support_pattern(mesh_group_settings.support_pattern), + support_roof_line_width(mesh_group_settings.support_roof_line_width), + support_line_spacing(mesh_group_settings.support_line_spacing), + support_bottom_offset(mesh_group_settings.support_bottom_offset), + support_wall_count(mesh_group_settings.support_wall_count), + resolution(mesh_group_settings.resolution), + support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. + settings(mesh_group_settings), + min_feature_size(mesh_group_settings.min_feature_size) + + + { + layer_start_bp_radius = (bp_radius - branch_radius) / (branch_radius * diameter_scale_bp_radius); + + if (TreeSupportSettings::soluble) { + // safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely + // When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size + // This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance. + xy_min_distance = std::max(xy_min_distance, scaled(0.1)); + xy_distance = std::max(xy_distance, xy_min_distance); + } + + +// const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SupportAreaOverwritesInterface }, { "interface_area_overwrite_support_area", InterfacePreference::InterfaceAreaOverwritesSupport }, { "support_lines_overwrite_interface_area", InterfacePreference::SupportLinesOverwriteInterface }, { "interface_lines_overwrite_support_area", InterfacePreference::InterfaceLinesOverwriteSupport }, { "nothing", InterfacePreference::Nothing } }; +// interface_preference = interface_map.at(mesh_group_settings.get("support_interface_priority")); +//FIXME this was the default +// interface_preference = InterfacePreference::SupportLinesOverwriteInterface; + interface_preference = InterfacePreference::SupportAreaOverwritesInterface; + } + +private: + double angle; + double angle_slow; + std::vector known_z; + +public: + // some static variables dependent on other meshes that are not currently processed. + // Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy. + inline static bool soluble = false; + /*! + * \brief Width of a single line of support. + */ + coord_t support_line_width; + /*! + * \brief Height of a single layer + */ + coord_t layer_height; + /*! + * \brief Radius of a branch when it has left the tip. + */ + coord_t branch_radius; + /*! + * \brief smallest allowed radius, required to ensure that even at DTT 0 every circle will still be printed + */ + coord_t min_radius; + /*! + * \brief How far an influence area may move outward every layer at most. + */ + coord_t maximum_move_distance; + /*! + * \brief How far every influence area will move outward every layer if possible. + */ + coord_t maximum_move_distance_slow; + /*! + * \brief Amount of bottom layers. 0 if disabled. + */ + size_t support_bottom_layers; + /*! + * \brief Amount of effectiveDTT increases are required to reach branch radius. + */ + size_t tip_layers; + /*! + * \brief Factor by which to increase the branch radius. + */ + double diameter_angle_scale_factor; + /*! + * \brief How much a branch resting on the model may grow in radius by merging with branches that can reach the buildplate. + */ + coord_t max_to_model_radius_increase; + /*! + * \brief If smaller (in layers) than that, all branches to model will be deleted + */ + size_t min_dtt_to_model; + /*! + * \brief Increase radius in the resulting drawn branches, even if the avoidance does not allow it. Will be cut later to still fit. + */ + coord_t increase_radius_until_radius; + /*! + * \brief Same as increase_radius_until_radius, but contains the DTT at which the radius will be reached. + */ + size_t increase_radius_until_layer; + /*! + * \brief True if the branches may connect to the model. + */ + bool support_rests_on_model; + /*! + * \brief How far should support be from the model. + */ + coord_t xy_distance; + /*! + * \brief Radius a branch should have when reaching the buildplate. + */ + coord_t bp_radius; + /*! + * \brief The layer index at which an increase in radius may be required to reach the bp_radius. + */ + coord_t layer_start_bp_radius; + /*! + * \brief Factor by which to increase the branch radius to reach the required bp_radius at layer 0. Note that this radius increase will not happen in the tip, to ensure the tip is structurally sound. + */ + double diameter_scale_bp_radius; + /*! + * \brief minimum xy_distance. Only relevant when Z overrides XY, otherwise equal to xy_distance- + */ + coord_t xy_min_distance; + /*! + * \brief Amount of layers distance required the top of the support to the model + */ + size_t z_distance_top_layers; + /*! + * \brief Amount of layers distance required from the top of the model to the bottom of a support structure. + */ + size_t z_distance_bottom_layers; + /*! + * \brief used for performance optimization at the support floor. Should have no impact on the resulting tree. + */ + size_t performance_interface_skip_layers; + /*! + * \brief User specified angles for the support infill. + */ +// std::vector support_infill_angles; + /*! + * \brief User specified angles for the support roof infill. + */ + std::vector support_roof_angles; + /*! + * \brief Pattern used in the support roof. May contain non relevant data if support roof is disabled. + */ + SupportMaterialInterfacePattern roof_pattern; + /*! + * \brief Pattern used in the support infill. + */ + SupportMaterialPattern support_pattern; + /*! + * \brief Line width of the support roof. + */ + coord_t support_roof_line_width; + /*! + * \brief Distance between support infill lines. + */ + coord_t support_line_spacing; + /*! + * \brief Offset applied to the support floor area. + */ + coord_t support_bottom_offset; + /* + * \brief Amount of walls the support area will have. + */ + int support_wall_count; + /* + * \brief Maximum allowed deviation when simplifying. + */ + coord_t resolution; + /* + * \brief Distance between the lines of the roof. + */ + coord_t support_roof_line_distance; + /* + * \brief How overlaps of an interface area with a support area should be handled. + */ + InterfacePreference interface_preference; + + /* + * \brief The infill class wants a settings object. This one will be the correct one for all settings it uses. + */ + TreeSupportMeshGroupSettings settings; + + /* + * \brief Minimum thickness of any model features. + */ + coord_t min_feature_size; + + public: + bool operator==(const TreeSupportSettings& other) const + { + return branch_radius == other.branch_radius && tip_layers == other.tip_layers && diameter_angle_scale_factor == other.diameter_angle_scale_factor && layer_start_bp_radius == other.layer_start_bp_radius && bp_radius == other.bp_radius && diameter_scale_bp_radius == other.diameter_scale_bp_radius && min_radius == other.min_radius && xy_min_distance == other.xy_min_distance && // as a recalculation of the collision areas is required to set a new min_radius. + xy_distance - xy_min_distance == other.xy_distance - other.xy_min_distance && // if the delta of xy_min_distance and xy_distance is different the collision areas have to be recalculated. + support_rests_on_model == other.support_rests_on_model && increase_radius_until_layer == other.increase_radius_until_layer && min_dtt_to_model == other.min_dtt_to_model && max_to_model_radius_increase == other.max_to_model_radius_increase && maximum_move_distance == other.maximum_move_distance && maximum_move_distance_slow == other.maximum_move_distance_slow && z_distance_bottom_layers == other.z_distance_bottom_layers && support_line_width == other.support_line_width && + support_line_spacing == other.support_line_spacing && support_roof_line_width == other.support_roof_line_width && // can not be set on a per-mesh basis currently, so code to enable processing different roof line width in the same iteration seems useless. + support_bottom_offset == other.support_bottom_offset && support_wall_count == other.support_wall_count && support_pattern == other.support_pattern && roof_pattern == other.roof_pattern && // can not be set on a per-mesh basis currently, so code to enable processing different roof patterns in the same iteration seems useless. + support_roof_angles == other.support_roof_angles && + //support_infill_angles == other.support_infill_angles && + increase_radius_until_radius == other.increase_radius_until_radius && support_bottom_layers == other.support_bottom_layers && layer_height == other.layer_height && z_distance_top_layers == other.z_distance_top_layers && resolution == other.resolution && // Infill generation depends on deviation and resolution. + support_roof_line_distance == other.support_roof_line_distance && interface_preference == other.interface_preference + && min_feature_size == other.min_feature_size // interface_preference should be identical to ensure the tree will correctly interact with the roof. + // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry +#if 0 + && (interface_preference == InterfacePreference::InterfaceAreaOverwritesSupport || interface_preference == InterfacePreference::SupportAreaOverwritesInterface + // Perimeter generator parameters + || + (settings.get("fill_outline_gaps") == other.settings.get("fill_outline_gaps") && + settings.get("min_bead_width") == other.settings.get("min_bead_width") && + settings.get("wall_transition_angle") == other.settings.get("wall_transition_angle") && + settings.get("wall_transition_length") == other.settings.get("wall_transition_length") && + settings.get("wall_split_middle_threshold") == other.settings.get("wall_split_middle_threshold") && + settings.get("wall_add_middle_threshold") == other.settings.get("wall_add_middle_threshold") && + settings.get("wall_distribution_count") == other.settings.get("wall_distribution_count") && + settings.get("wall_transition_filter_distance") == other.settings.get("wall_transition_filter_distance") && + settings.get("wall_transition_filter_deviation") == other.settings.get("wall_transition_filter_deviation") && + settings.get("wall_line_width_x") == other.settings.get("wall_line_width_x") && + settings.get("meshfix_maximum_extrusion_area_deviation") == other.settings.get("meshfix_maximum_extrusion_area_deviation")) + ) +#endif + ; + } + + /*! + * \brief Get the Distance to top regarding the real radius this part will have. This is different from distance_to_top, which is can be used to calculate the top most layer of the branch. + * \param elem[in] The SupportElement one wants to know the effectiveDTT + * \return The Effective DTT. + */ + [[nodiscard]] inline size_t getEffectiveDTT(const SupportElementState &elem) const + { + return elem.effective_radius_height < increase_radius_until_layer ? (elem.distance_to_top < increase_radius_until_layer ? elem.distance_to_top : increase_radius_until_layer) : elem.effective_radius_height; + } + + /*! + * \brief Get the Radius part will have based on numeric values. + * \param distance_to_top[in] The effective distance_to_top of the element + * \param elephant_foot_increases[in] The elephant_foot_increases of the element. + * \return The radius an element with these attributes would have. + */ + [[nodiscard]] inline coord_t getRadius(size_t distance_to_top, const double elephant_foot_increases = 0) const + { + return (distance_to_top <= tip_layers ? min_radius + (branch_radius - min_radius) * distance_to_top / tip_layers : // tip + branch_radius + // base + branch_radius * (distance_to_top - tip_layers) * diameter_angle_scale_factor) + + // gradual increase + branch_radius * elephant_foot_increases * (std::max(diameter_scale_bp_radius - diameter_angle_scale_factor, 0.0)); + } + + /*! + * \brief Get the Radius, that this element will have. + * \param elem[in] The Element. + * \return The radius the element has. + */ + [[nodiscard]] inline coord_t getRadius(const SupportElementState &elem) const + { return getRadius(getEffectiveDTT(elem), elem.elephant_foot_increases); } + [[nodiscard]] inline coord_t getRadius(const SupportElement &elem) const + { return this->getRadius(elem.state); } + + /*! + * \brief Get the collision Radius of this Element. This can be smaller then the actual radius, as the drawAreas will cut off areas that may collide with the model. + * \param elem[in] The Element. + * \return The collision radius the element has. + */ + [[nodiscard]] inline coord_t getCollisionRadius(const SupportElementState &elem) const + { + return getRadius(elem.effective_radius_height, elem.elephant_foot_increases); + } + + /*! + * \brief Get the Radius an element should at least have at a given layer. + * \param layer_idx[in] The layer. + * \return The radius every element should aim to achieve. + */ + [[nodiscard]] inline coord_t recommendedMinRadius(LayerIndex layer_idx) const + { + double scale = (layer_start_bp_radius - int(layer_idx)) * diameter_scale_bp_radius; + return scale > 0 ? branch_radius + branch_radius * scale : 0; + } + + /*! + * \brief Return on which z in microns the layer will be printed. Used only for support infill line generation. + * \param layer_idx[in] The layer. + * \return The radius every element should aim to achieve. + */ + [[nodiscard]] inline coord_t getActualZ(LayerIndex layer_idx) + { + return layer_idx < coord_t(known_z.size()) ? known_z[layer_idx] : (layer_idx - known_z.size()) * layer_height + known_z.size() ? known_z.back() : 0; + } + + /*! + * \brief Set the z every Layer is printed at. Required for getActualZ to work + * \param z[in] The z every LayerIndex is printed. Vector is used as a map with the index of each element being the corresponding LayerIndex + * \return The radius every element should aim to achieve. + */ + void setActualZ(std::vector& z) + { + known_z = z; + } +}; + +// todo Remove! ONLY FOR PUBLIC BETA!! +void tree_supports_show_error(std::string message, bool critical); + +} // namespace FFFTreeSupport + +void fff_tree_support_generate(PrintObject &print_object, std::function throw_on_cancel = []{}); } // namespace Slic3r -namespace std -{ -template <> -struct hash -{ - size_t operator()(const Slic3r::TreeSupport::SupportElement& node) const - { - size_t hash_node = Slic3r::PointHash{}(node.target_position); - boost::hash_combine(hash_node, size_t(node.target_height)); - return hash_node; - } -}; -} // namespace std - #endif /* slic3r_TreeSupport_hpp */