diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index c94edca72..c07669fe0 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -16,6 +16,7 @@ #include "Point.hpp" #include "Print.hpp" #include "PrintConfig.hpp" +#include "Utils.hpp" #include @@ -282,21 +283,9 @@ void TreeModelVolumes::precalculate(coord_t max_layer) BOOST_LOG_TRIVIAL(info) << "Precalculating collision took" << dur_col << " ms. Precalculating avoidance took " << dur_avo << " ms."; } -/*! - * \brief Checks a cache for a given RadiusLayerPair and returns it if it is found - * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. - * \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found) - */ -std::optional> getArea(const TreeModelVolumes::RadiusLayerPolygonCache& cache, const TreeModelVolumes::RadiusLayerPair& key) -{ - const auto it = cache.find(key); - return it == cache.end() ? std::optional>{} : std::optional>{ it->second }; -} - const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const { coord_t orig_radius = radius; - std::optional> result; if (!min_xy_dist) radius += m_current_min_xy_dist_delta; @@ -304,11 +293,7 @@ const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_ if (orig_radius != 0) radius = ceilRadius(radius); RadiusLayerPair key{ radius, layer_idx }; - - { - std::lock_guard critical_section_support_max_layer_nr(*m_critical_avoidance_cache); - result = getArea(m_collision_cache, key); - } + std::optional> result = m_collision_cache.getArea(key); if (result) return result.value().get(); if (m_precalculated) { @@ -322,18 +307,15 @@ const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_ const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const { coord_t orig_radius = radius; - std::optional> result; if (!min_xy_dist) radius += m_current_min_xy_dist_delta; if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta) return getCollision(orig_radius, layer_idx, min_xy_dist); + // not needed as the radius should already be adjusted. + // radius = ceilRadius(radius); RadiusLayerPair key{ radius, layer_idx }; - - { - std::lock_guard critical_section_support_max_layer_nr(*m_critical_collision_cache_holefree); - result = getArea(m_collision_cache_holefree, key); - } + std::optional> result = m_collision_cache_holefree.getArea(key); if (result) return result.value().get(); if (m_precalculated) { @@ -351,61 +333,44 @@ const Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_ coord_t orig_radius = radius; - std::optional> result; - if (!min_xy_dist) radius += m_current_min_xy_dist_delta; radius = ceilRadius(radius); - if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta && type == AvoidanceType::FAST_SAFE) // no holes anymore by definition at this request - type = AvoidanceType::FAST; + if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta && type == AvoidanceType::FastSafe) // no holes anymore by definition at this request + type = AvoidanceType::Fast; const RadiusLayerPair key{ radius, layer_idx }; - const RadiusLayerPolygonCache* cache_ptr = nullptr; - std::mutex* mutex_ptr; - if (!to_model && type == AvoidanceType::FAST) { + const RadiusLayerPolygonCache *cache_ptr = nullptr; + if (!to_model && type == AvoidanceType::Fast) { cache_ptr = &m_avoidance_cache; - mutex_ptr = m_critical_avoidance_cache.get(); - } else if (!to_model && type == AvoidanceType::SLOW) { + } else if (!to_model && type == AvoidanceType::Slow) { cache_ptr = &m_avoidance_cache_slow; - mutex_ptr = m_critical_avoidance_cache_slow.get(); - } else if (!to_model && type == AvoidanceType::FAST_SAFE) { - cache_ptr = &m_avoidance_cache_hole; - mutex_ptr = m_critical_avoidance_cache_holefree.get(); - } else if (to_model && type == AvoidanceType::FAST) { + } else if (!to_model && type == AvoidanceType::FastSafe) { + cache_ptr = &m_avoidance_cache_holefree; + } else if (to_model && type == AvoidanceType::Fast) { cache_ptr = &m_avoidance_cache_to_model; - mutex_ptr = m_critical_avoidance_cache_to_model.get(); - } else if (to_model && type == AvoidanceType::SLOW) { + } else if (to_model && type == AvoidanceType::Slow) { cache_ptr = &m_avoidance_cache_to_model_slow; - mutex_ptr = m_critical_avoidance_cache_to_model_slow.get(); - } else if (to_model && type == AvoidanceType::FAST_SAFE) { - cache_ptr = &m_avoidance_cache_hole_to_model; - mutex_ptr = m_critical_avoidance_cache_holefree_to_model.get(); + } else if (to_model && type == AvoidanceType::FastSafe) { + cache_ptr = &m_avoidance_cache_holefree_to_model; } else { BOOST_LOG_TRIVIAL(error) << "Invalid Avoidance Request"; TreeSupport::showError("Invalid Avoidance Request.\n", true); } + std::optional> result = cache_ptr->getArea(key); + if (result) + return result.value().get(); + if (to_model) { - { - std::lock_guard critical_section(*mutex_ptr); - result = getArea(*cache_ptr, key); - } - if (result) - return result.value().get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(warning) << "Had to calculate Avoidance to model at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Avoidance(to model) requested.", false); } const_cast(this)->calculateAvoidanceToModel(key); } else { - { - std::lock_guard critical_section(*mutex_ptr); - result = getArea(*cache_ptr, key); - } - if (result) - return result.value().get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(warning) << "Had to calculate Avoidance at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Avoidance(to buildplate) requested.", false); @@ -415,120 +380,47 @@ const Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_ return getAvoidance(orig_radius, layer_idx, type, to_model, min_xy_dist); // retrive failed and correct result was calculated. Now it has to be retrived. } -const Polygons& TreeModelVolumes::getPlaceableAreas(coord_t radius, LayerIndex layer_idx) const +const Polygons& TreeModelVolumes::getPlaceableAreas(coord_t orig_radius, LayerIndex layer_idx) const { - std::optional> result; - const coord_t orig_radius = radius; - radius = ceilRadius(radius); - RadiusLayerPair key{ radius, layer_idx }; - - { - std::lock_guard critical_section(*m_critical_placeable_areas_cache); - result = getArea(m_placeable_areas_cache, key); - } + const coord_t radius = ceilRadius(orig_radius); + std::optional> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); if (result) return result.value().get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(warning) << "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); } - if (radius != 0) - const_cast(this)->calculatePlaceables(key); + if (orig_radius > 0) + const_cast(this)->calculatePlaceables({ radius, layer_idx }); else getCollision(0, layer_idx, true); return getPlaceableAreas(orig_radius, layer_idx); } -const Polygons& TreeModelVolumes::getWallRestriction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const +const Polygons& TreeModelVolumes::getWallRestriction(coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const { if (layer_idx == 0) // Should never be requested as there will be no going below layer 0 ..., but just to be sure some semi-sane catch. Alternative would be empty Polygon. - return getCollision(radius, layer_idx, min_xy_dist); + return getCollision(orig_radius, layer_idx, min_xy_dist); - coord_t orig_radius = radius; min_xy_dist = min_xy_dist && m_current_min_xy_dist_delta > 0; - std::optional> result; - - radius = ceilRadius(radius); - const RadiusLayerPair key{ radius, layer_idx }; - - const RadiusLayerPolygonCache* cache_ptr = min_xy_dist ? &m_wall_restrictions_cache_min : &m_wall_restrictions_cache; - - if (min_xy_dist) { - { - std::lock_guard critical_section(*m_critical_wall_restrictions_cache_min); - result = getArea(*cache_ptr, key); - } - if (result) - return result.value().get(); - if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate Wall restricions at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Wall restriction of minimum xy distance requested ).", false); - } - } else { - { - std::lock_guard critical_section(*m_critical_wall_restrictions_cache); - result = getArea(*cache_ptr, key); - } - if (result) - return result.value().get(); - if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate Wall restricions at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Wall restriction requested ).", false); - } + coord_t radius = ceilRadius(orig_radius); + std::optional> result = + (min_xy_dist ? m_wall_restrictions_cache_min : m_wall_restrictions_cache).getArea({ radius, layer_idx }); + if (result) + return result.value().get(); + if (m_precalculated) { + BOOST_LOG_TRIVIAL(warning) << "Had to calculate Wall restricions at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; + TreeSupport::showError( + min_xy_dist ? + "Not precalculated Wall restriction of minimum xy distance requested )." : + "Not precalculated Wall restriction requested )." + , false); } - const_cast(this)->calculateWallRestrictions(key); + const_cast(this)->calculateWallRestrictions({ radius, layer_idx }); return getWallRestriction(orig_radius, layer_idx, min_xy_dist); // Retrieve failed and correct result was calculated. Now it has to be retrieved. } -coord_t TreeModelVolumes::ceilRadius(coord_t radius, bool min_xy_dist) const -{ - if (! min_xy_dist) - radius += m_current_min_xy_dist_delta; - return ceilRadius(radius); -} - -coord_t TreeModelVolumes::getRadiusNextCeil(coord_t radius, bool min_xy_dist) const -{ - coord_t ceiled_radius = ceilRadius(radius, min_xy_dist); - if (! min_xy_dist) - ceiled_radius -= m_current_min_xy_dist_delta; - return ceiled_radius; -} - -#if 0 -Polygons TreeModelVolumes::extractOutlineFromMesh(const PrintObject &print_object, LayerIndex layer_idx) const -{ - constexpr bool external_polys_only = false; - Polygons total; - - // similar to SliceDataStorage.getLayerOutlines but only for one mesh instead of for everyone - - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) - return Polygons(); - - const SliceLayer& layer = mesh.layers[layer_idx]; - - layer.getOutlines(total, external_polys_only); - if (mesh.settings.get("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) - total = union_(total, layer.openPolyLines.offsetPolyLine(100)); - coord_t resolution = mesh.settings.get("resolution"); - return simplify(total, resolution); -} -#endif - -LayerIndex TreeModelVolumes::getMaxCalculatedLayer(coord_t radius, const RadiusLayerPolygonCache& map) const -{ - int max_layer = -1; - // the placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist. - const RadiusLayerPair key_layer_1(radius, 1); - if (getArea(map, key_layer_1)) - max_layer = 1; - while (map.count(RadiusLayerPair(radius, max_layer + 1))) - ++ max_layer; - return max_layer; -} - void TreeModelVolumes::calculateCollision(std::deque keys) { tbb::parallel_for(tbb::blocked_range(0, keys.size()), @@ -537,11 +429,11 @@ void TreeModelVolumes::calculateCollision(std::deque keys) const coord_t radius = keys[i].first; const size_t layer_idx = keys[i].second; RadiusLayerPair key(radius, 0); - RadiusLayerPolygonCache data_outer; - RadiusLayerPolygonCache data_placeable_outer; + RadiusLayerPolygonCacheData data_outer; + RadiusLayerPolygonCacheData data_placeable_outer; for (size_t outline_idx = 0; outline_idx < m_layer_outlines.size(); ++outline_idx) { - RadiusLayerPolygonCache data; - RadiusLayerPolygonCache data_placeable; + RadiusLayerPolygonCacheData data; + RadiusLayerPolygonCacheData data_placeable; const coord_t layer_height = m_layer_outlines[outline_idx].first.layer_height; const bool support_rests_on_this_model = ! m_layer_outlines[outline_idx].first.support_material_buildplate_only; @@ -554,12 +446,7 @@ void TreeModelVolumes::calculateCollision(std::deque keys) // avoiding this would require saving each collision for each outline_idx separately. // and later for each avoidance... But avoidance calculation has to be for the whole scene and can NOT be done for each outline_idx separately and combined later. // so avoiding this inaccuracy seems infeasible as it would require 2x the avoidance calculations => 0.5x the performance. - coord_t min_layer_bottom; - { - std::lock_guard critical_section(*m_critical_collision_cache); - min_layer_bottom = getMaxCalculatedLayer(radius, m_collision_cache) - int(z_distance_bottom_layers); - } - + coord_t min_layer_bottom = m_collision_cache.getMaxCalculatedLayer(radius) - int(z_distance_bottom_layers); if (min_layer_bottom < 0) min_layer_bottom = 0; //FIXME parallel_for @@ -623,14 +510,9 @@ void TreeModelVolumes::calculateCollision(std::deque keys) } #endif - { - std::lock_guard critical_section(*m_critical_collision_cache); - m_collision_cache.insert(data_outer.begin(), data_outer.end()); - } - if (radius == 0) { - std::lock_guard critical_section(*m_critical_placeable_areas_cache); - m_placeable_areas_cache.insert(data_placeable_outer.begin(), data_placeable_outer.end()); - } + m_collision_cache.insert(std::move(data_outer)); + if (radius == 0) + m_placeable_areas_cache.insert(std::move(data_placeable_outer)); } }); } @@ -644,7 +526,7 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke tbb::parallel_for(tbb::blocked_range(0, max_layer + 1), [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - RadiusLayerPolygonCache data; + RadiusLayerPolygonCacheData data; for (RadiusLayerPair key : keys) { // Logically increase the collision by m_increase_until_radius coord_t radius = key.first; @@ -654,8 +536,7 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke data[RadiusLayerPair(radius, layer_idx)] = col; } - std::lock_guard critical_section(*m_critical_collision_cache_holefree); - m_collision_cache_holefree.insert(data.begin(), data.end()); + m_collision_cache_holefree.insert(std::move(data)); } }); } @@ -663,7 +544,7 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke void TreeModelVolumes::calculateAvoidance(std::deque keys) { // For every RadiusLayer pair there are 3 avoidances that have to be calculate, calculated in the same paralell_for loop for better paralellisation. - const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; + const std::vector all_types = { AvoidanceType::Slow, AvoidanceType::FastSafe, AvoidanceType::Fast }; tbb::parallel_for(tbb::blocked_range(0, keys.size() * 3), [&, keys, all_types](const tbb::blocked_range &range) { for (size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { @@ -671,8 +552,8 @@ void TreeModelVolumes::calculateAvoidance(std::deque keys) { size_t type_idx = iter_idx % all_types.size(); AvoidanceType type = all_types[type_idx]; - const bool slow = type == AvoidanceType::SLOW; - const bool holefree = type == AvoidanceType::FAST_SAFE; + const bool slow = type == AvoidanceType::Slow; + const bool holefree = type == AvoidanceType::FastSafe; coord_t radius = keys[key_idx].first; LayerIndex max_required_layer = keys[key_idx].second; @@ -683,11 +564,7 @@ void TreeModelVolumes::calculateAvoidance(std::deque keys) const coord_t offset_speed = slow ? m_max_move_slow : m_max_move; RadiusLayerPair key(radius, 0); - LayerIndex start_layer; - { - std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_slow : holefree ? m_critical_avoidance_cache_holefree : m_critical_avoidance_cache)); - start_layer = 1 + getMaxCalculatedLayer(radius, slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_hole : m_avoidance_cache); - } + LayerIndex start_layer = 1 + (slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_holefree : m_avoidance_cache).getMaxCalculatedLayer(radius); if (start_layer > max_required_layer) { BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; continue; @@ -718,10 +595,7 @@ void TreeModelVolumes::calculateAvoidance(std::deque keys) } #endif - { - std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_slow : holefree ? m_critical_avoidance_cache_holefree : m_critical_avoidance_cache)); - (slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_hole : m_avoidance_cache).insert(data.begin(), data.end()); - } + (slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_holefree : m_avoidance_cache).insert(std::move(data)); } } }); @@ -737,11 +611,7 @@ void TreeModelVolumes::calculatePlaceables(std::deque keys) std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); RadiusLayerPair key(radius, 0); - LayerIndex start_layer; - { - std::lock_guard critical_section(*m_critical_placeable_areas_cache); - start_layer = 1 + getMaxCalculatedLayer(radius, m_placeable_areas_cache); - } + LayerIndex start_layer = 1 + m_placeable_areas_cache.getMaxCalculatedLayer(radius); if (start_layer > max_required_layer) { BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; continue; @@ -770,10 +640,7 @@ void TreeModelVolumes::calculatePlaceables(std::deque keys) } #endif - { - std::lock_guard critical_section(*m_critical_placeable_areas_cache); - m_placeable_areas_cache.insert(data.begin(), data.end()); - } + m_placeable_areas_cache.insert(std::move(data)); } }); } @@ -781,15 +648,15 @@ void TreeModelVolumes::calculatePlaceables(std::deque keys) void TreeModelVolumes::calculateAvoidanceToModel(std::deque keys) { // For every RadiusLayer pair there are 3 avoidances that have to be calculated, calculated in the same parallel_for loop for better parallelization. - const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; + const std::vector all_types = { AvoidanceType::Slow, AvoidanceType::FastSafe, AvoidanceType::Fast }; tbb::parallel_for(tbb::blocked_range(0, keys.size() * 3), [&, keys, all_types](const tbb::blocked_range &range) { for (size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { size_t key_idx = iter_idx / 3; size_t type_idx = iter_idx % all_types.size(); AvoidanceType type = all_types[type_idx]; - bool slow = type == AvoidanceType::SLOW; - bool holefree = type == AvoidanceType::FAST_SAFE; + bool slow = type == AvoidanceType::Slow; + bool holefree = type == AvoidanceType::FastSafe; coord_t radius = keys[key_idx].first; LayerIndex max_required_layer = keys[key_idx].second; @@ -802,12 +669,7 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque key std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); RadiusLayerPair key(radius, 0); - LayerIndex start_layer; - - { - std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_to_model_slow : holefree ? m_critical_avoidance_cache_holefree_to_model : m_critical_avoidance_cache_to_model)); - start_layer = 1 + getMaxCalculatedLayer(radius, slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_hole_to_model : m_avoidance_cache_to_model); - } + LayerIndex start_layer = 1 + (slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_holefree_to_model : m_avoidance_cache_to_model).getMaxCalculatedLayer(radius); if (start_layer > max_required_layer) { BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; continue; @@ -836,15 +698,11 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque key } #endif - { - std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_to_model_slow : holefree ? m_critical_avoidance_cache_holefree_to_model : m_critical_avoidance_cache_to_model)); - (slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_hole_to_model : m_avoidance_cache_to_model).insert(data.begin(), data.end()); - } + (slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_holefree_to_model : m_avoidance_cache_to_model).insert(std::move(data)); } }); } - void TreeModelVolumes::calculateWallRestrictions(std::deque keys) { // Wall restrictions are mainly important when they represent actual walls that are printed, and not "just" the configured z_distance, because technically valid placement is no excuse for moving through a wall. @@ -886,15 +744,9 @@ void TreeModelVolumes::calculateWallRestrictions(std::deque key for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { coord_t radius = keys[key_idx].first; RadiusLayerPair key(radius, 0); - coord_t min_layer_bottom; - RadiusLayerPolygonCache data; - RadiusLayerPolygonCache data_min; - - { - std::lock_guard critical_section(*m_critical_wall_restrictions_cache); - min_layer_bottom = getMaxCalculatedLayer(radius, m_wall_restrictions_cache); - } - + RadiusLayerPolygonCacheData data; + RadiusLayerPolygonCacheData data_min; + coord_t min_layer_bottom = m_wall_restrictions_cache.getMaxCalculatedLayer(radius); if (min_layer_bottom < 1) min_layer_bottom = 1; @@ -904,21 +756,14 @@ void TreeModelVolumes::calculateWallRestrictions(std::deque key Polygons wall_restriction = intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx_below, true)); // radius contains m_current_min_xy_dist_delta already if required wall_restriction = polygons_simplify(wall_restriction, m_min_resolution); data.emplace(key, wall_restriction); - if (m_current_min_xy_dist_delta > 0) - { + if (m_current_min_xy_dist_delta > 0) { Polygons wall_restriction_min = intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx_below, true)); wall_restriction = polygons_simplify(wall_restriction_min, m_min_resolution); data_min.emplace(key, wall_restriction_min); } } - { - std::lock_guard critical_section(*m_critical_wall_restrictions_cache); - m_wall_restrictions_cache.insert(data.begin(), data.end()); - } - { - std::lock_guard critical_section(*m_critical_wall_restrictions_cache_min); - m_wall_restrictions_cache_min.insert(data_min.begin(), data_min.end()); - } + m_wall_restrictions_cache.insert(std::move(data)); + m_wall_restrictions_cache_min.insert(std::move(data_min)); } }); } diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index c8b986a00..5f4b66772 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -23,7 +23,6 @@ namespace Slic3r { using LayerIndex = int; -using AngleRadians = double; class BuildVolume; class PrintObject; @@ -52,7 +51,7 @@ struct TreeSupportMeshGroupSettings { // Support Overhang Angle // The minimum angle of overhangs for which support is added. At a value of 0° all overhangs are supported, 90° will not provide any support. - AngleRadians support_angle { 50. * M_PI / 180. }; + double support_angle { 50. * M_PI / 180. }; // Support Line Width // Width of a single support structure line. coord_t support_line_width { scaled(0.4) }; @@ -92,7 +91,7 @@ struct TreeSupportMeshGroupSettings { // A list of integer line directions to use. Elements from the list are used sequentially as the layers progress and when the end // of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained // in square brackets. Default is an empty list which means use the default angle 0 degrees. - std::vector support_infill_angles {}; + std::vector support_infill_angles {}; // Enable Support Roof // Generate a dense slab of material between the top of support and the model. This will create a skin between the model and support. bool support_roof_enable { false }; @@ -106,7 +105,7 @@ struct TreeSupportMeshGroupSettings { // and when the end of the list is reached, it starts at the beginning again. The list items are separated // by commas and the whole list is contained in square brackets. Default is an empty list which means // use the default angles (alternates between 45 and 135 degrees if interfaces are quite thick or 90 degrees). - std::vector support_roof_angles {}; + std::vector support_roof_angles {}; // Support Roof Pattern (aka top interface) // The pattern with which the roofs of the support are printed. SupportMaterialInterfacePattern support_roof_pattern { smipAuto }; @@ -144,12 +143,12 @@ struct TreeSupportMeshGroupSettings { // Tree Support Maximum Branch Angle // The maximum angle of the branches, when the branches have to avoid the model. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach. // minimum: 0, minimum warning: 20, maximum: 89, maximum warning": 85 - AngleRadians support_tree_angle { 60. * M_PI / 180. }; + double support_tree_angle { 60. * M_PI / 180. }; // Tree Support Branch Diameter Angle // The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. // A bit of an angle can increase stability of the tree support. // minimum: 0, maximum: 89.9999, maximum warning: 15 - AngleRadians support_tree_branch_diameter_angle { 5. * M_PI / 180. }; + double support_tree_branch_diameter_angle { 5. * M_PI / 180. }; // Tree Support Branch Distance // How far apart the branches need to be when they touch the model. Making this distance small will cause // the tree support to touch the model at more points, causing better overhang but making support harder to remove. @@ -166,7 +165,7 @@ struct TreeSupportMeshGroupSettings { // Tree Support Preferred Branch Angle // The preferred angle of the branches, when they do not have to avoid the model. Use a lower angle to make them more vertical and more stable. Use a higher angle for branches to merge faster. // minimum: 0, minimum warning: 10, maximum: support_tree_angle, maximum warning: support_tree_angle-1 - AngleRadians support_tree_angle_slow { 50. * M_PI / 180. }; + double support_tree_angle_slow { 50. * M_PI / 180. }; // Tree Support Diameter Increase To Model // The most the diameter of a branch that has to connect to the model may increase by merging with branches that could reach the buildplate. // Increasing this reduces print time, but increases the area of support that rests on model @@ -195,17 +194,13 @@ struct TreeSupportMeshGroupSettings { //enum support_interface_priority { support_lines_overwrite_interface_area }; }; -inline coord_t round_up_divide(const coord_t dividend, const coord_t divisor) //!< Return dividend divided by divisor rounded to the nearest integer -{ - return (dividend + divisor - 1) / divisor; -} - 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, double progress_multiplier, + double progress_offset, const std::vector &additional_excluded_areas = {}); TreeModelVolumes(TreeModelVolumes&&) = default; TreeModelVolumes& operator=(TreeModelVolumes&&) = default; @@ -214,17 +209,16 @@ public: enum class AvoidanceType { - SLOW, - FAST_SAFE, - FAST + Slow, + FastSafe, + Fast }; /*! - * \brief Precalculate avoidances and collisions up to this layer. - * - * This uses knowledge about branch angle to only calculate avoidances and collisions that could actually be needed. - * Not calling this will cause the class to lazily calculate avoidances and collisions as needed, which will be a lot slower on systems with more then one or two cores! + * \brief Precalculate avoidances and collisions up to max_layer. * + * Knowledge about branch angle is used to only calculate avoidances and collisions that may actually be needed. + * Not calling precalculate() will cause the class to lazily calculate avoidances and collisions as needed, which will be a lot slower on systems with more then one or two cores! */ void precalculate(coord_t max_layer); @@ -239,20 +233,7 @@ public: * \param min_xy_dist Is the minimum xy distance used. * \return Polygons object */ - const Polygons& getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist = false) const; - - /*! - * \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed. - * - * The result is a 2D area that would cause nodes of given radius to - * collide with the model or be inside a hole. - * A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall. - * \param radius The radius of the node of interest - * \param layer_idx The layer of interest - * \param min_xy_dist Is the minimum xy distance used. - * \return Polygons object - */ - const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist = false) const; + const Polygons& getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const; /*! * \brief Provides the areas that have to be avoided by the tree's branches @@ -266,12 +247,12 @@ public: * * \param radius The radius of the node of interest * \param layer_idx The layer of interest - * \param slow Is the propagation with the maximum move distance slow required. + * \param type Is the propagation with the maximum move distance slow required. * \param to_model Does the avoidance allow good connections with the model. * \param min_xy_dist is the minimum xy distance used. * \return Polygons object */ - const Polygons& getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model = false, bool min_xy_dist = false) const; + const Polygons& getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) const; /*! * \brief Provides the area represents all areas on the model where the branch does completely fit on the given layer. * \param radius The radius of the node of interest @@ -296,7 +277,9 @@ public: * \param min_xy_dist is the minimum xy distance used. * \return The rounded radius */ - coord_t ceilRadius(coord_t radius, bool min_xy_dist) const; + coord_t ceilRadius(coord_t radius, bool min_xy_dist) const { + return this->ceilRadius(min_xy_dist ? radius : radius + m_current_min_xy_dist_delta); + } /*! * \brief Round \p radius upwards to the maximum that would still round up to the same value as the provided one. * @@ -304,16 +287,84 @@ public: * \param min_xy_dist is the minimum xy distance used. * \return The maximum radius, resulting in the same rounding. */ - coord_t getRadiusNextCeil(coord_t radius, bool min_xy_dist) const; + coord_t getRadiusNextCeil(coord_t radius, bool min_xy_dist) const { + return min_xy_dist ? + this->ceilRadius(radius) : + this->ceilRadius(radius + m_current_min_xy_dist_delta) - m_current_min_xy_dist_delta; + } private: /*! * \brief Convenience typedef for the keys to the caches */ - using RadiusLayerPair = std::pair; - using RadiusLayerPolygonCache = std::unordered_map>; + using RadiusLayerPair = std::pair; + using RadiusLayerPolygonCacheData = std::unordered_map>; + class RadiusLayerPolygonCache { + public: + RadiusLayerPolygonCache() = default; + RadiusLayerPolygonCache(RadiusLayerPolygonCache &&rhs) : data(std::move(rhs.data)) {} + RadiusLayerPolygonCache& operator=(RadiusLayerPolygonCache &&rhs) { data = std::move(rhs.data); return *this; } - friend std::optional> getArea(const TreeModelVolumes::RadiusLayerPolygonCache &cache, const TreeModelVolumes::RadiusLayerPair &key); + RadiusLayerPolygonCache(const RadiusLayerPolygonCache&) = delete; + RadiusLayerPolygonCache& operator=(const RadiusLayerPolygonCache&) = delete; + + void insert(RadiusLayerPolygonCacheData &&in) { + std::lock_guard guard(this->mutex); + for (auto& d : in) + this->data.emplace(d.first, std::move(d.second)); + } + void insert(std::vector> && in) { + std::lock_guard guard(this->mutex); + for (auto& d : in) + this->data.emplace(d.first, std::move(d.second)); + } + /*! + * \brief Checks a cache for a given RadiusLayerPair and returns it if it is found + * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. + * \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found) + */ + std::optional> getArea(const TreeModelVolumes::RadiusLayerPair &key) const { + std::lock_guard guard(this->mutex); + const auto it = this->data.find(key); + return it == this->data.end() ? + std::optional>{} : std::optional>{ it->second }; + } + /*! + * \brief Get the highest already calculated layer in the cache. + * \param radius The radius for which the highest already calculated layer has to be found. + * \param map The cache in which the lookup is performed. + * + * \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found) + */ + LayerIndex getMaxCalculatedLayer(coord_t radius) const { + std::lock_guard guard(this->mutex); + int max_layer = -1; + // the placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist. + if (this->data.find({ radius, 1 }) != this->data.end()) + max_layer = 1; + while (this->data.count(TreeModelVolumes::RadiusLayerPair(radius, max_layer + 1)) > 0) + ++ max_layer; + return max_layer; + } + + private: + RadiusLayerPolygonCacheData data; + mutable std::mutex mutex; + }; + + + /*! + * \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed. + * + * The result is a 2D area that would cause nodes of given radius to + * collide with the model or be inside a hole. + * A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall. + * \param radius The radius of the node of interest + * \param layer_idx The layer of interest + * \param min_xy_dist Is the minimum xy distance used. + * \return Polygons object + */ + const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const; /*! * \brief Round \p radius upwards to either a multiple of m_radius_sample_resolution or a exponentially increasing value @@ -322,14 +373,6 @@ private: */ coord_t ceilRadius(coord_t radius) const; - /*! - * \brief Extracts the relevant outline from a mesh - * \param[in] mesh The mesh which outline will be extracted - * \param layer_idx The layer which should be extracted from the mesh - * \return Polygons object representing the outline - */ -// Polygons extractOutlineFromMesh(const PrintObject &print_object, LayerIndex layer_idx) const; - /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. * @@ -449,15 +492,6 @@ private: calculateWallRestrictions(std::deque{ RadiusLayerPair(key) }); } - /*! - * \brief Get the highest already calculated layer in the cache. - * \param radius The radius for which the highest already calculated layer has to be found. - * \param map The cache in which the lookup is performed. - * - * \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found) - */ - LayerIndex getMaxCalculatedLayer(coord_t radius, const RadiusLayerPolygonCache& map) const; - /*! * \brief The maximum distance that the center point of a tree branch may move in consecutive layers if it has to avoid the model. */ @@ -531,59 +565,40 @@ private: */ coord_t m_radius_0; - /*! * \brief Caches for the collision, avoidance and areas on the model where support can be placed safely * at given radius and layer indices. */ - RadiusLayerPolygonCache m_collision_cache; - std::unique_ptr m_critical_collision_cache { std::make_unique() }; - - RadiusLayerPolygonCache m_collision_cache_holefree; - std::unique_ptr m_critical_collision_cache_holefree { std::make_unique() }; - - RadiusLayerPolygonCache m_avoidance_cache; - std::unique_ptr m_critical_avoidance_cache { std::make_unique() }; - - RadiusLayerPolygonCache m_avoidance_cache_slow; - std::unique_ptr m_critical_avoidance_cache_slow { std::make_unique() }; - - RadiusLayerPolygonCache m_avoidance_cache_to_model; - std::unique_ptr m_critical_avoidance_cache_to_model { std::make_unique() }; - - RadiusLayerPolygonCache m_avoidance_cache_to_model_slow; - std::unique_ptr m_critical_avoidance_cache_to_model_slow { std::make_unique() }; - - RadiusLayerPolygonCache m_placeable_areas_cache; - std::unique_ptr m_critical_placeable_areas_cache { std::make_unique() }; + RadiusLayerPolygonCache m_collision_cache; + RadiusLayerPolygonCache m_collision_cache_holefree; + RadiusLayerPolygonCache m_avoidance_cache; + RadiusLayerPolygonCache m_avoidance_cache_slow; + RadiusLayerPolygonCache m_avoidance_cache_to_model; + RadiusLayerPolygonCache m_avoidance_cache_to_model_slow; + RadiusLayerPolygonCache m_placeable_areas_cache; /*! * \brief Caches to avoid holes smaller than the radius until which the radius is always increased, as they are free of holes. * Also called safe avoidances, as they are safe regarding not running into holes. */ - RadiusLayerPolygonCache m_avoidance_cache_hole; - std::unique_ptr m_critical_avoidance_cache_holefree { std::make_unique() }; - - RadiusLayerPolygonCache m_avoidance_cache_hole_to_model; - std::unique_ptr m_critical_avoidance_cache_holefree_to_model { std::make_unique() }; + RadiusLayerPolygonCache m_avoidance_cache_holefree; + RadiusLayerPolygonCache m_avoidance_cache_holefree_to_model; /*! * \brief Caches to represent walls not allowed to be passed over. */ RadiusLayerPolygonCache m_wall_restrictions_cache; - std::unique_ptr m_critical_wall_restrictions_cache { std::make_unique() }; // A different cache for min_xy_dist as the maximal safe distance an influence area can be increased(guaranteed overlap of two walls in consecutive layer) // is much smaller when min_xy_dist is used. This causes the area of the wall restriction to be thinner and as such just using the min_xy_dist wall // restriction would be slower. RadiusLayerPolygonCache m_wall_restrictions_cache_min; - std::unique_ptr m_critical_wall_restrictions_cache_min = std::make_unique(); +#ifdef SLIC3R_TREESUPPORTS_PROGRESS std::unique_ptr m_critical_progress { std::make_unique() }; +#endif // SLIC3R_TREESUPPORTS_PROGRESS }; -Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision); - } #endif //slic3r_TreeModelVolumes_hpp diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 1a725ec65..2d7debea9 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -525,13 +525,13 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo for (const Polyline &line : polylines) { LineInformation res_line; for (Point p : line) { - if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST_SAFE, false, !xy_overrides_z), p)) + if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FastSafe, false, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_BP_SAFE); - else if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST, false, !xy_overrides_z), p)) + else if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::Fast, false, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_BP); - else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST_SAFE, true, !xy_overrides_z), p)) + else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FastSafe, true, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS_SAFE); - else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST, true, !xy_overrides_z), p)) + else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::Fast, true, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS); else if (config.support_rests_on_model && ! contains(volumes.getCollision(config.getRadius(0), layer_idx, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_MODEL); @@ -580,12 +580,12 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo size_t current_layer, std::pair &p) { using AvoidanceType = TreeSupport::AvoidanceType; - if (! contains(volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, false, !config.support_xy_overrides_z), p.first)) + if (! contains(volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FastSafe : AvoidanceType::Fast, false, !config.support_xy_overrides_z), p.first)) return true; if (config.support_rests_on_model && (p.second != LineStatus::TO_BP && p.second != LineStatus::TO_BP_SAFE)) return ! contains( p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? - volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, true, !config.support_xy_overrides_z) : + volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FastSafe : AvoidanceType::Fast, true, !config.support_xy_overrides_z) : volumes.getCollision(config.getRadius(0), current_layer - 1, !config.support_xy_overrides_z), p.first); return false; @@ -936,19 +936,6 @@ static std::optional> polyline_sample_next_point_at_dis return result; } -// ensures offsets are only done in sizes with a max step size per offset while adding the collision offset after each step, this ensures that areas cannot glitch through walls defined by the collision when offsetting to fast -[[nodiscard]] Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) -{ - const size_t steps = std::abs(distance / max_safe_step_distance); - assert(int64_t(distance) * int64_t(max_safe_step_distance) >= 0); - ExPolygons ret = union_ex(me); - Polygons collision_trimmed = clip_for_diff(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); - - for (size_t i = 0; i < steps; ++ i) - ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision_trimmed)); - return union_(offset(ret, distance % max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision_trimmed); -} - /*! * \brief Offsets (increases the area of) a polygons object in multiple steps to ensure that it does not lag through over a given obstacle. * \param me[in] Polygons object that has to be offset. @@ -1065,7 +1052,7 @@ void TreeSupport::generateInitialAreas( Polygon base_circle; const auto base_radius = scaled(0.01); for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; ++ i) { - const AngleRadians angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); + const double angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); base_circle.points.emplace_back(coord_t(cos(angle) * base_radius), coord_t(sin(angle) * base_radius)); } TreeSupportMeshGroupSettings mesh_group_settings(print_object); @@ -1106,9 +1093,9 @@ 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, !xy_overrides_z) : + (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides_z)) : - m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides_z)); + m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, false, !xy_overrides_z)); // 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); } @@ -1257,7 +1244,7 @@ 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, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, false, !xy_overrides_z)); + 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, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, false, !xy_overrides_z)); // 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); }; @@ -1304,7 +1291,11 @@ void TreeSupport::generateInitialAreas( // here the roof is handled. If roof can not be added the branches will try to not move instead Polygons forbidden_next; { - const Polygons &forbidden_next_raw = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides_z));\ + 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, !xy_overrides_z) : + m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides_z)) : + m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::Fast, false, !xy_overrides_z); // prevent rounding errors down the line forbidden_next = offset(union_ex(forbidden_next_raw), scaled(0.005), jtMiter, 1.2); } @@ -1314,11 +1305,11 @@ void TreeSupport::generateInitialAreas( size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; if (dtt_roof != 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 = 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; } - break; } added_roofs[dtt_roof] = overhang_outset; @@ -1641,7 +1632,7 @@ static void mergeHelper( 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)); // 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. + 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)); @@ -1692,7 +1683,7 @@ static void mergeInfluenceAreas( // 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)))); + 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 @@ -1946,7 +1937,7 @@ 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 avoidance_speed_mismatch = parent_moved_slow && elem.last_area_increase.type != AvoidanceType::SLOW; + 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)) { // assume that the avoidance type that was best for the parent is best for me. Makes this function about 7% faster. @@ -1957,27 +1948,27 @@ void TreeSupport::increaseAreas(std::unordered_map& to 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 + // 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, 2)) - insertSetting({ AvoidanceType::FAST, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); - insertSetting({ AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we manage to avoid the hole - insertSetting({ AvoidanceType::FAST_SAFE, 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); + if (elem.distance_to_top < round_up_divide(m_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 { - insertSetting({ AvoidanceType::SLOW, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); + 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. // 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 + insertSetting({ AvoidanceType::Slow, slow_speed, !increase_radius, no_error, !use_min_radius, move }, true); // a if (elem.distance_to_top < m_config.tip_layers) { - insertSetting({ AvoidanceType::FAST_SAFE, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); + insertSetting({ AvoidanceType::FastSafe, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); } - insertSetting({ AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, move }, true); // b - insertSetting({ AvoidanceType::FAST_SAFE, fast_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) @@ -1994,11 +1985,11 @@ void TreeSupport::increaseAreas(std::unordered_map& to 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); + insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move }, true); } if (elem.distance_to_top < elem.dont_move_until && elem.can_use_safe_radius) // only do not move when holes would be avoided in every case. // Only do not move when already in a no hole avoidance with the regular xy distance. - insertSetting({ AvoidanceType::SLOW, 0, increase_radius, no_error, !use_min_radius, !move }, false); + insertSetting({ AvoidanceType::Slow, 0, increase_radius, no_error, !use_min_radius, !move }, false); 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. @@ -2057,7 +2048,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to else elem.result_on_layer = parent->result_on_layer; - elem.can_use_safe_radius = settings.type != AvoidanceType::FAST; + elem.can_use_safe_radius = settings.type != AvoidanceType::Fast; if (!settings.use_min_distance) elem.use_min_xy_dist = false; @@ -2368,7 +2359,7 @@ void TreeSupport::generateBranchAreas( Polygon branch_circle; // Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time. for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; ++ i) { - const AngleRadians angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); + const double angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); branch_circle.points.emplace_back(coord_t(cos(angle) * m_config.branch_radius), coord_t(sin(angle) * m_config.branch_radius)); } @@ -2594,7 +2585,7 @@ void TreeSupport::dropNonGraciousAreas( 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)); + rest_support = diff_clipped(rest_support, m_volumes.getCollision(0, linear_data[idx].first - counter, false)); dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support); counter++; } diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 1514c2e58..041344375 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -15,6 +15,7 @@ #include // For combining hashes #include "BoundingBox.hpp" +#include "Utils.hpp" #define TREE_SUPPORT_SHOW_ERRORS @@ -95,7 +96,7 @@ public: struct AreaIncreaseSettings { - AvoidanceType type { AvoidanceType::FAST }; + AvoidanceType type { AvoidanceType::Fast }; coord_t increase_speed { 0 }; bool increase_radius { false }; bool no_error { false }; @@ -116,7 +117,7 @@ public: 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), 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) { } @@ -176,16 +177,19 @@ public: } // 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), area(nullptr), 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) + 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), + 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) - { + if (first.target_height > second.target_height) { target_height = first.target_height; target_position = first.target_position; - } - else - { + } else { target_height = second.target_height; target_position = second.target_position; } @@ -199,8 +203,7 @@ public: AddParents(second.parents); elephant_foot_increases = 0; - if (config.diameter_scale_bp_radius > 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. @@ -406,7 +409,7 @@ public: xy_distance = std::max(xy_distance, xy_min_distance); // (logic) from getInterfaceAngles in FFFGcodeWriter. - auto getInterfaceAngles = [&](std::vector& angles, SupportMaterialInterfacePattern pattern) { + auto getInterfaceAngles = [&](std::vector& angles, SupportMaterialInterfacePattern pattern) { if (angles.empty()) { if (pattern == SupportMaterialInterfacePattern::smipConcentric) @@ -543,11 +546,11 @@ public: /*! * \brief User specified angles for the support infill. */ - std::vector support_infill_angles; + std::vector support_infill_angles; /*! * \brief User specified angles for the support roof infill. */ - std::vector support_roof_angles; + std::vector support_roof_angles; /*! * \brief Pattern used in the support roof. May contain non relevant data if support roof is disabled. */ @@ -613,7 +616,7 @@ public: || (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_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") && diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 6a09e7fbf..73bb418af 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -192,6 +192,14 @@ inline INDEX_TYPE next_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count) return idx; } + +// Return dividend divided by divisor rounded to the nearest integer +template +inline INDEX_TYPE round_up_divide(const INDEX_TYPE dividend, const INDEX_TYPE divisor) +{ + return (dividend + divisor - 1) / divisor; +} + template inline typename CONTAINER_TYPE::size_type prev_idx_modulo(typename CONTAINER_TYPE::size_type idx, const CONTAINER_TYPE &container) {