From b9e7cd2d7b3f4cdeec20a938b94e012029b8e9ae Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 23 Aug 2022 11:37:06 +0200 Subject: [PATCH] WIP TreeSupports: Improved speed of TreeModelVolumes by better parallelization, cleaned up the code by better structuring the collision caches with their mutexes. --- src/libslic3r/TreeModelVolumes.cpp | 796 ++++++++++++++--------------- src/libslic3r/TreeModelVolumes.hpp | 123 ++--- src/libslic3r/TreeSupport.cpp | 17 +- src/libslic3r/TreeSupport.hpp | 9 +- 4 files changed, 454 insertions(+), 491 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index c07669fe0..ccfd531af 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -26,6 +26,10 @@ namespace Slic3r { +// or warning +// had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL() +#define error_level_not_in_cache error + TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &print_object) { const PrintConfig &print_config = print_object.print()->config(); @@ -37,10 +41,11 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr assert(config.support_material); assert(config.support_material_style == smsTree); + // Calculate maximum external perimeter width over all printing regions, taking into account the default layer height. coordf_t external_perimeter_width = 0.; for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) { const PrintRegion ®ion = print_object.printing_region(region_id); - external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(print_object, frExternalPerimeter, config.layer_height).width())); + external_perimeter_width = std::max(external_perimeter_width, region.flow(print_object, frExternalPerimeter, config.layer_height).width()); } this->layer_height = scaled(config.layer_height.value); @@ -59,6 +64,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr this->support_material_buildplate_only = config.support_material_buildplate_only; // this->support_xy_overrides_z = this->support_xy_distance = scaled(config.support_material_xy_spacing.get_abs_value(external_perimeter_width)); + // Separation of interfaces, it is likely smaller than support_xy_distance. this->support_xy_distance_overhang = std::min(this->support_xy_distance, scaled(0.5 * external_perimeter_width)); this->support_top_distance = scaled(slicing_params.gap_support_object); this->support_bottom_distance = scaled(slicing_params.gap_object_support); @@ -79,6 +85,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr // this->support_offset = } +//FIXME Machine border is currently ignored. static Polygons calculateMachineBorderCollision(Polygon machine_border) { // Put a border of 1m around the print volume so that we don't collide. @@ -99,13 +106,13 @@ TreeModelVolumes::TreeModelVolumes( 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) : - m_max_move{ std::max(max_move - 2, coord_t(0)) }, m_max_move_slow{ std::max(max_move_slow - 2, coord_t(0)) }, m_progress_multiplier{ progress_multiplier }, m_progress_offset{ progress_offset }, // -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) }, + m_progress_multiplier{ progress_multiplier }, m_progress_offset{ progress_offset }, m_machine_border{ calculateMachineBorderCollision(build_volume.polygon()) } { - std::unordered_map mesh_to_layeroutline_idx; - #if 0 + std::unordered_map mesh_to_layeroutline_idx; for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); ++ mesh_idx) { SliceMeshStorage mesh = storage.meshes[mesh_idx]; bool added = false; @@ -131,7 +138,6 @@ TreeModelVolumes::TreeModelVolumes( #else { m_anti_overhang = print_object.slice_support_blockers(); - mesh_to_layeroutline_idx[0] = 0; TreeSupportMeshGroupSettings mesh_settings(print_object); m_layer_outlines.emplace_back(mesh_settings, std::vector{}); m_current_outline_idx = 0; @@ -195,222 +201,188 @@ TreeModelVolumes::TreeModelVolumes( #endif } -void TreeModelVolumes::precalculate(coord_t max_layer) +void TreeModelVolumes::precalculate(const coord_t max_layer) { auto t_start = std::chrono::high_resolution_clock::now(); m_precalculated = true; - // 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. + // 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); - // calculate which radius each layer in the tip may have. - std::unordered_set possible_tip_radiis; - for (size_t dtt = 0; dtt <= config.tip_layers; dtt++) { - possible_tip_radiis.emplace(ceilRadius(config.getRadius(dtt))); - possible_tip_radiis.emplace(ceilRadius(config.getRadius(dtt) + m_current_min_xy_dist_delta)); + { + // calculate which radius each layer in the tip may have. + std::vector possible_tip_radiis; + for (size_t distance_to_top = 0; distance_to_top <= config.tip_layers; ++ distance_to_top) { + possible_tip_radiis.emplace_back(ceilRadius(config.getRadius(distance_to_top))); + possible_tip_radiis.emplace_back(ceilRadius(config.getRadius(distance_to_top) + m_current_min_xy_dist_delta)); + } + sort_remove_duplicates(possible_tip_radiis); + // It theoretically may happen in the tip, that the radius can change so much in-between 2 layers, + // that a ceil step is skipped (as in there is a radius r so that ceilRadius(radius(dtt)) radius_until_layer; // while it is possible to calculate, up to which layer the avoidance should be calculated, this simulation is easier to understand, and does not need to be adjusted if something of the radius calculation is changed. // Overhead with an assumed worst case of 6600 layers was about 2ms - for (LayerIndex simulated_dtt = 0; simulated_dtt <= max_layer; simulated_dtt++) { - const LayerIndex current_layer = max_layer - simulated_dtt; - const coord_t max_regular_radius = ceilRadius(config.getRadius(simulated_dtt, 0) + m_current_min_xy_dist_delta); - const coord_t max_min_radius = ceilRadius(config.getRadius(simulated_dtt, 0)); // the maximal radius that the radius with the min_xy_dist can achieve - const coord_t max_initial_layer_diameter_radius = ceilRadius(config.recommendedMinRadius(current_layer) + m_current_min_xy_dist_delta); - if (!radius_until_layer.count(max_regular_radius)) - radius_until_layer[max_regular_radius] = current_layer; - if (!radius_until_layer.count(max_min_radius)) - radius_until_layer[max_min_radius] = current_layer; - if (!radius_until_layer.count(max_initial_layer_diameter_radius)) - radius_until_layer[max_initial_layer_diameter_radius] = current_layer; + for (LayerIndex distance_to_top = 0; distance_to_top <= max_layer; ++ distance_to_top) { + const LayerIndex current_layer = max_layer - distance_to_top; + auto update_radius_until_layer = [&radius_until_layer, current_layer](coord_t r) { + auto it = radius_until_layer.find(r); + if (it == radius_until_layer.end()) + radius_until_layer.emplace_hint(it, r, current_layer); + }; + // regular radius + update_radius_until_layer(ceilRadius(config.getRadius(distance_to_top, 0) + m_current_min_xy_dist_delta)); + // the maximum radius that the radius with the min_xy_dist can achieve + update_radius_until_layer(ceilRadius(config.getRadius(distance_to_top, 0))); + update_radius_until_layer(ceilRadius(config.recommendedMinRadius(current_layer) + m_current_min_xy_dist_delta)); } // Copy to deque to use in parallel for later. - std::deque relevant_avoidance_radiis; - std::deque relevant_avoidance_radiis_to_model; - relevant_avoidance_radiis.insert(relevant_avoidance_radiis.end(), radius_until_layer.begin(), radius_until_layer.end()); - relevant_avoidance_radiis_to_model.insert(relevant_avoidance_radiis_to_model.end(), radius_until_layer.begin(), radius_until_layer.end()); + std::vector relevant_avoidance_radiis{ radius_until_layer.begin(), radius_until_layer.end() }; // Append additional radiis needed for collision. - - radius_until_layer[ceilRadius(m_increase_until_radius, false)] = max_layer; // To calculate collision holefree for every radius, the collision of radius m_increase_until_radius will be required. + // To calculate collision holefree for every radius, the collision of radius m_increase_until_radius will be required. + radius_until_layer[ceilRadius(m_increase_until_radius + m_current_min_xy_dist_delta)] = max_layer; // Collision for radius 0 needs to be calculated everywhere, as it will be used to ensure valid xy_distance in drawAreas. radius_until_layer[0] = max_layer; if (m_current_min_xy_dist_delta != 0) radius_until_layer[m_current_min_xy_dist_delta] = max_layer; - std::deque relevant_collision_radiis; - relevant_collision_radiis.insert(relevant_collision_radiis.end(), radius_until_layer.begin(), radius_until_layer.end()); // Now that required_avoidance_limit contains the maximum of ild and regular required radius just copy. + // Now that required_avoidance_limit contains the maximum of ild and regular required radius just copy. + std::vector relevant_collision_radiis{ radius_until_layer.begin(), radius_until_layer.end() }; - - // ### Calculate the relevant collisions + // Calculate the relevant collisions calculateCollision(relevant_collision_radiis); // calculate a separate Collisions with all holes removed. These are relevant for some avoidances that try to avoid holes (called safe) - std::deque relevant_hole_collision_radiis; + std::vector relevant_hole_collision_radiis; for (RadiusLayerPair key : relevant_avoidance_radiis) if (key.first < m_increase_until_radius + m_current_min_xy_dist_delta) relevant_hole_collision_radiis.emplace_back(key); - // ### Calculate collisions without holes, build from regular collision + // Calculate collisions without holes, built from regular collision calculateCollisionHolefree(relevant_hole_collision_radiis); + // Let placables be calculated from calculateAvoidance() for better parallelization. + if (m_support_rests_on_model) + calculatePlaceables(relevant_avoidance_radiis); auto t_coll = std::chrono::high_resolution_clock::now(); - // ### Calculate the relevant avoidances in parallel as far as possible + // Calculate the relevant avoidances in parallel as far as possible { tbb::task_group task_group; - task_group.run([this, relevant_avoidance_radiis]{ calculateAvoidance(relevant_avoidance_radiis); }); + task_group.run([this, relevant_avoidance_radiis]{ calculateAvoidance(relevant_avoidance_radiis, true, m_support_rests_on_model); }); task_group.run([this, relevant_avoidance_radiis]{ calculateWallRestrictions(relevant_avoidance_radiis); }); - if (m_support_rests_on_model) - task_group.run([this, relevant_avoidance_radiis_to_model]{ - calculatePlaceables(relevant_avoidance_radiis_to_model); - calculateAvoidanceToModel(relevant_avoidance_radiis_to_model); - }); task_group.wait(); } auto t_end = std::chrono::high_resolution_clock::now(); auto dur_col = 0.001 * std::chrono::duration_cast(t_coll - t_start).count(); auto dur_avo = 0.001 * std::chrono::duration_cast(t_end - t_coll).count(); +// m_precalculated = true; BOOST_LOG_TRIVIAL(info) << "Precalculating collision took" << dur_col << " ms. Precalculating avoidance took " << dur_avo << " ms."; } -const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const +const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const { - coord_t orig_radius = radius; - if (!min_xy_dist) - radius += m_current_min_xy_dist_delta; - - // special case as if a radius 0 is requested it could be to ensure correct xy distance. As such it is beneficial if the collision is as close to the configured values as possible. - if (orig_radius != 0) - radius = ceilRadius(radius); - RadiusLayerPair key{ radius, layer_idx }; - std::optional> result = m_collision_cache.getArea(key); - if (result) + const coord_t radius = this->ceilRadius(orig_radius, min_xy_dist); + if (std::optional> result = m_collision_cache.getArea({ radius, layer_idx }); result) return result.value().get(); if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate collision at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; + 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); } - const_cast(this)->calculateCollision(key); + const_cast(this)->calculateCollision(radius, layer_idx); return getCollision(orig_radius, layer_idx, min_xy_dist); } -const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const +// Private. Only called internally by calculateAvoidance() and calculateAvoidanceToModel(), radius is already snapped to grid. +const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx) const { - coord_t orig_radius = radius; - 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::optional> result = m_collision_cache_holefree.getArea(key); - if (result) + assert(radius == this->ceilRadius(radius)); + assert(radius < m_increase_until_radius + m_current_min_xy_dist_delta); + if (std::optional> result = m_collision_cache_holefree.getArea({ radius, layer_idx }); result) return result.value().get(); if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate collision holefree at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; + 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); } - const_cast(this)->calculateCollisionHolefree(key); - return getCollisionHolefree(orig_radius, layer_idx, min_xy_dist); + const_cast(this)->calculateCollisionHolefree({ radius, layer_idx }); + return getCollisionHolefree(radius, layer_idx); } -const Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) const +const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) const { if (layer_idx == 0) // What on the layer directly above buildplate do i have to avoid to reach the buildplate ... - return getCollision(radius, layer_idx, min_xy_dist); + return getCollision(orig_radius, layer_idx, min_xy_dist); - coord_t orig_radius = radius; - - 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::FastSafe) // no holes anymore by definition at this request + const coord_t radius = this->ceilRadius(orig_radius, min_xy_dist); + if (type == AvoidanceType::FastSafe && radius >= m_increase_until_radius + m_current_min_xy_dist_delta) + // no holes anymore by definition at this request type = AvoidanceType::Fast; - const RadiusLayerPair key{ radius, layer_idx }; - - const RadiusLayerPolygonCache *cache_ptr = nullptr; - if (!to_model && type == AvoidanceType::Fast) { - cache_ptr = &m_avoidance_cache; - } else if (!to_model && type == AvoidanceType::Slow) { - cache_ptr = &m_avoidance_cache_slow; - } 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; - } else if (to_model && type == AvoidanceType::Slow) { - cache_ptr = &m_avoidance_cache_to_model_slow; - } 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) + if (std::optional> result = + this->avoidance_cache(type, to_model).getArea({ radius, layer_idx }); + result) return result.value().get(); - if (to_model) { - 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!"; + 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); - } - const_cast(this)->calculateAvoidanceToModel(key); - } else { - 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!"; + } 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); } - const_cast(this)->calculateAvoidance(key); } - 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_cast(this)->calculateAvoidance({ radius, layer_idx }, ! to_model, to_model); + // Retrive failed and correct result was calculated. Now it has to be retrived. + return getAvoidance(orig_radius, layer_idx, type, to_model, min_xy_dist); } -const Polygons& TreeModelVolumes::getPlaceableAreas(coord_t orig_radius, LayerIndex layer_idx) const +const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, LayerIndex layer_idx) const { + if (orig_radius == 0) + return this->getCollision(0, layer_idx, true); + const coord_t radius = ceilRadius(orig_radius); - std::optional> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); - if (result) + if (std::optional> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); 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!"; + 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); } - if (orig_radius > 0) - const_cast(this)->calculatePlaceables({ radius, layer_idx }); - else - getCollision(0, layer_idx, true); + const_cast(this)->calculatePlaceables(radius, layer_idx); return getPlaceableAreas(orig_radius, layer_idx); } -const Polygons& TreeModelVolumes::getWallRestriction(coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const +const Polygons& TreeModelVolumes::getWallRestriction(const 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. + assert(layer_idx > 0); + 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(orig_radius, layer_idx, min_xy_dist); - min_xy_dist = min_xy_dist && m_current_min_xy_dist_delta > 0; + min_xy_dist &= m_current_min_xy_dist_delta > 0; - coord_t radius = ceilRadius(orig_radius); - std::optional> result = + const coord_t radius = ceilRadius(orig_radius); + if (std::optional> result = (min_xy_dist ? m_wall_restrictions_cache_min : m_wall_restrictions_cache).getArea({ radius, layer_idx }); - if (result) + 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!"; + 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( min_xy_dist ? "Not precalculated Wall restriction of minimum xy distance requested )." : @@ -421,289 +393,276 @@ const Polygons& TreeModelVolumes::getWallRestriction(coord_t orig_radius, LayerI return getWallRestriction(orig_radius, layer_idx, min_xy_dist); // Retrieve failed and correct result was calculated. Now it has to be retrieved. } -void TreeModelVolumes::calculateCollision(std::deque keys) +void TreeModelVolumes::calculateCollision(const std::vector &keys) { tbb::parallel_for(tbb::blocked_range(0, keys.size()), [&](const tbb::blocked_range &range) { - for (size_t i = range.begin(); i != range.end(); ++ i) { - const coord_t radius = keys[i].first; - const size_t layer_idx = keys[i].second; - RadiusLayerPair key(radius, 0); - RadiusLayerPolygonCacheData data_outer; - RadiusLayerPolygonCacheData data_placeable_outer; - for (size_t outline_idx = 0; outline_idx < m_layer_outlines.size(); ++outline_idx) { - 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; - const coord_t z_distance_bottom = m_layer_outlines[outline_idx].first.support_bottom_distance; - const size_t z_distance_bottom_layers = round_up_divide(z_distance_bottom, layer_height); - const coord_t z_distance_top_layers = round_up_divide(m_layer_outlines[outline_idx].first.support_top_distance, layer_height); - const LayerIndex max_required_layer = layer_idx + std::max(coord_t(1), z_distance_top_layers); - const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : m_layer_outlines[outline_idx].first.support_xy_distance; - // technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all not currently processing meshes as this delta will be added at request time. - // 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 = m_collision_cache.getMaxCalculatedLayer(radius) - int(z_distance_bottom_layers); - if (min_layer_bottom < 0) - min_layer_bottom = 0; - //FIXME parallel_for - for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; layer_idx++) { - key.second = layer_idx; - Polygons collision_areas = m_machine_border; - if (size_t(layer_idx) < m_layer_outlines[outline_idx].second.size()) - append(collision_areas, m_layer_outlines[outline_idx].second[layer_idx]); - // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. - // if a key does not exist when it is accessed it is added! - append(data[key], offset(union_ex(collision_areas), radius + xy_distance, ClipperLib::jtMiter, 1.2)); - } - - // Add layers below, to ensure correct support_bottom_distance. Also save placeable areas of radius 0, if required for this mesh. - for (int layer_idx = int(max_required_layer); layer_idx >= min_layer_bottom; -- layer_idx) { - key.second = layer_idx; - for (size_t layer_offset = 1; layer_offset <= z_distance_bottom_layers && layer_idx - coord_t(layer_offset) > min_layer_bottom; ++ layer_offset) - append(data[key], data[RadiusLayerPair(radius, layer_idx - layer_offset)]); - if (support_rests_on_this_model && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) { - RadiusLayerPair key_next_layer(radius, layer_idx + 1); - //data[key] = union_(data[key]); - Polygons above = data[key_next_layer]; - // just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly. - //FIXME Vojtech: Why m_anti_overhang.size() > layer_idx + 1? Why +1? - above = m_anti_overhang.size() > layer_idx + 1 ? union_(above, m_anti_overhang[layer_idx]) : union_(above); - data_placeable[key_next_layer] = union_(data_placeable[key_next_layer], diff(data[key], above)); - } - } - - // Add collision layers above to ensure correct support_top_distance. - for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; ++ layer_idx) { - key.second = layer_idx; - Polygons collisions = std::move(data[key]); - for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + layer_idx <= max_required_layer; ++ layer_offset) - append(collisions, data[RadiusLayerPair(radius, layer_idx + layer_offset)]); - data[key] = m_anti_overhang.size() > layer_idx ? union_(collisions, offset(union_ex(m_anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(collisions); - } - - for (int layer_idx = int(max_required_layer); layer_idx > keys[i].second; -- layer_idx) { - // all these dont have the correct z_distance_top_layers as they can still have areas above them - auto it = data.find(RadiusLayerPair(radius, layer_idx)); - if (it != data.end()) - data.erase(it); - } - - for (auto pair : data) - data_outer[pair.first] = union_(data_outer[pair.first], polygons_simplify(pair.second, m_min_resolution)); - if (radius == 0) { - for (auto pair : data_placeable) - data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], polygons_simplify(pair.second, m_min_resolution)); - } - } - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - { - std::lock_guard critical_section(*m_critical_progress); - if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL) { - m_precalculation_progress += TREE_PROGRESS_PRECALC_COLL / keys.size(); - Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - } - } -#endif - - m_collision_cache.insert(std::move(data_outer)); - if (radius == 0) - m_placeable_areas_cache.insert(std::move(data_placeable_outer)); + for (size_t ikey = range.begin(); ikey != range.end(); ++ ikey) { + const LayerIndex radius = keys[ikey].first; + const size_t max_layer_idx = keys[ikey].second; + // recursive call to parallel_for. + calculateCollision(radius, max_layer_idx); } }); } -void TreeModelVolumes::calculateCollisionHolefree(std::deque keys) +void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex max_layer_idx) +{ + assert(radius == this->ceilRadius(radius)); + + // Process the outlines from least layers to most layers so that the final union will run over the longest vector. + std::vector layer_outline_indices(m_layer_outlines.size(), 0); + std::iota(layer_outline_indices.begin(), layer_outline_indices.end(), 0); + std::sort(layer_outline_indices.begin(), layer_outline_indices.end(), + [this](size_t i, size_t j) { return m_layer_outlines[i].second.size() < m_layer_outlines[j].second.size(); }); + + const LayerIndex min_layer_last = m_collision_cache.getMaxCalculatedLayer(radius); + std::vector data(max_layer_idx + 1 - min_layer_last, Polygons{}); + const bool calculate_placable = m_support_rests_on_model && radius == 0; + std::vector data_placeable; + if (calculate_placable) + data_placeable = std::vector(max_layer_idx + 1 - min_layer_last, Polygons{}); + + for (size_t outline_idx : layer_outline_indices) + if (const std::vector &outlines = m_layer_outlines[outline_idx].second; ! outlines.empty()) { + const TreeSupportMeshGroupSettings &settings = m_layer_outlines[outline_idx].first; + const coord_t layer_height = settings.layer_height; + const bool support_rests_on_model = ! settings.support_material_buildplate_only; + const coord_t z_distance_bottom = settings.support_bottom_distance; + const int z_distance_bottom_layers = round_up_divide(z_distance_bottom, layer_height); + const int z_distance_top_layers = round_up_divide(settings.support_top_distance, layer_height); + const LayerIndex max_required_layer = std::min(outlines.size(), max_layer_idx + std::max(coord_t(1), z_distance_top_layers)); + const LayerIndex min_layer_bottom = std::max(0, min_layer_last - int(z_distance_bottom_layers)); + // technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all + // not currently processing meshes as this delta will be added at request time. + // 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. + const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : settings.support_xy_distance; + + // 1) Calculate offsets of collision areas in parallel. + std::vector collision_areas_offsetted(max_required_layer + 1 - min_layer_bottom); + tbb::parallel_for(tbb::blocked_range(min_layer_bottom, max_required_layer + 1), + [&outlines, &machine_border = m_machine_border, offset_value = radius + xy_distance, min_layer_bottom, &collision_areas_offsetted] + (const tbb::blocked_range &range) { + for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) { + Polygons collision_areas = machine_border; + append(collision_areas, outlines[layer_idx]); + // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. + // if a key does not exist when it is accessed it is added! + collision_areas_offsetted[layer_idx - min_layer_bottom] = offset_value == 0 ? union_(collision_areas) : offset(union_ex(collision_areas), offset_value, ClipperLib::jtMiter, 1.2); + } + }); + + // 2) Sum over top / bottom ranges. + const bool last = outline_idx == layer_outline_indices.size(); + tbb::parallel_for(tbb::blocked_range(min_layer_last + 1, max_layer_idx + 1), + [&collision_areas_offsetted, &anti_overhang = m_anti_overhang, min_layer_bottom, radius, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, min_layer_last, last] + (const tbb::blocked_range& range) { + for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++layer_idx) { + Polygons collisions; + for (int i = -z_distance_bottom_layers; i <= z_distance_top_layers; ++ i) { + int j = layer_idx + i - min_layer_bottom; + if (j >= 0 && j < collision_areas_offsetted.size()) + append(collisions, collision_areas_offsetted[j]); + } + collisions = last && layer_idx < anti_overhang.size() ? union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(collisions); + auto &dst = data[layer_idx - (min_layer_last + 1)]; + if (last) { + if (! dst.empty()) + collisions = union_(collisions, dst); + dst = polygons_simplify(collisions, min_resolution); + } else + append(dst, collisions); + } + }); + + // 3) Optionally calculate placables. + if (calculate_placable) { + // Calculating both the collision areas and placable areas. + tbb::parallel_for(tbb::blocked_range(std::max(min_layer_last + 1, z_distance_bottom_layers + 1), max_layer_idx + 1), + [&collision_areas_offsetted, &anti_overhang = m_anti_overhang, min_layer_bottom, radius, z_distance_bottom_layers, z_distance_top_layers, last, min_resolution = m_min_resolution, &data_placeable, min_layer_last] + (const tbb::blocked_range& range) { + for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) { + LayerIndex layer_idx_below = layer_idx - (z_distance_bottom_layers + 1) - min_layer_bottom; + assert(layer_idx_below >= 0); + auto ¤t = collision_areas_offsetted[layer_idx - min_layer_bottom]; + auto &below = collision_areas_offsetted[layer_idx_below]; + auto placable = diff(below, layer_idx < anti_overhang.size() ? union_(current, anti_overhang[layer_idx - (z_distance_bottom_layers + 1)]) : current); + auto &dst = data_placeable[layer_idx - (min_layer_last + 1)]; + if (last) { + if (! dst.empty()) + placable = union_(placable, dst); + dst = polygons_simplify(placable, min_resolution); + } else + append(dst, placable); + } + }); + } else { + // Calculating just the collision areas. + } + } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + { + std::lock_guard critical_section(*m_critical_progress); + if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL) { + m_precalculation_progress += TREE_PROGRESS_PRECALC_COLL / keys.size(); + Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); + } + } +#endif + m_collision_cache.insert(std::move(data), min_layer_last + 1, radius); + if (calculate_placable) + m_placeable_areas_cache.insert(std::move(data_placeable), min_layer_last + 1, radius); +} + +void TreeModelVolumes::calculateCollisionHolefree(const std::vector &keys) { LayerIndex max_layer = 0; for (long long unsigned int i = 0; i < keys.size(); i++) max_layer = std::max(max_layer, keys[i].second); - tbb::parallel_for(tbb::blocked_range(0, max_layer + 1), + tbb::parallel_for(tbb::blocked_range(0, max_layer + 1, keys.size()), [&](const tbb::blocked_range &range) { + RadiusLayerPolygonCacheData data; for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - RadiusLayerPolygonCacheData data; - for (RadiusLayerPair key : keys) { - // Logically increase the collision by m_increase_until_radius - coord_t radius = key.first; - coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - ceilRadius(radius, true); - // this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area. - Polygons col = polygons_simplify(offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution), m_min_resolution); - data[RadiusLayerPair(radius, layer_idx)] = col; - } - - m_collision_cache_holefree.insert(std::move(data)); + for (RadiusLayerPair key : keys) + if (layer_idx <= key.second) { + // Logically increase the collision by m_increase_until_radius + coord_t radius = key.first; + assert(radius == this->ceilRadius(radius)); + assert(radius < m_increase_until_radius + m_current_min_xy_dist_delta); + coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - radius; + assert(increase_radius_ceil > 0); + // this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area. + data[RadiusLayerPair(radius, layer_idx)] = polygons_simplify( + offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), + 5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution), + m_min_resolution); + } } + m_collision_cache_holefree.insert(std::move(data)); }); } -void TreeModelVolumes::calculateAvoidance(std::deque keys) +void TreeModelVolumes::calculateAvoidance(const std::vector &keys, bool to_build_plate, bool to_model) { - // 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::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]; - const bool slow = type == AvoidanceType::Slow; - const bool holefree = type == AvoidanceType::FastSafe; + // For every RadiusLayer pair there are 3 avoidances that have to be calculated. + // Prepare tasks for parallelization. + struct AvoidanceTask { + AvoidanceType type; + coord_t radius; + LayerIndex max_required_layer; + bool to_model; + LayerIndex start_layer; - coord_t radius = keys[key_idx].first; - LayerIndex max_required_layer = keys[key_idx].second; + bool slow() const { return this->type == AvoidanceType::Slow; } + bool holefree() const { return this->type == AvoidanceType::FastSafe; } + }; - // do not calculate not needed safe avoidances - if (holefree && radius >= m_increase_until_radius + m_current_min_xy_dist_delta) - continue; + std::vector avoidance_tasks; + avoidance_tasks.reserve((int(to_build_plate) + int(to_model)) * keys.size() * size_t(AvoidanceType::Count)); - const coord_t offset_speed = slow ? m_max_move_slow : m_max_move; - RadiusLayerPair key(radius, 0); - 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; - } - std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); + for (int iter_idx = 0; iter_idx < 2 * keys.size() * size_t(AvoidanceType::Count); ++ iter_idx) { + AvoidanceTask task{ + AvoidanceType(iter_idx % int(AvoidanceType::Count)), + keys[iter_idx / 6].first, // radius + keys[iter_idx / 6].second, // max_layer + ((iter_idx / 3) & 1) != 0 // to_model + }; + // Ensure start_layer is at least 1 as if no avoidance was calculated yet getMaxCalculatedLayer() returns -1. + task.start_layer = std::max(1, 1 + avoidance_cache(task.type, task.to_model).getMaxCalculatedLayer(task.radius)); + if (task.start_layer > task.max_required_layer) { + BOOST_LOG_TRIVIAL(debug) << "Calculation requested for value already calculated?"; + continue; + } + if (! task.holefree() || task.radius < m_increase_until_radius + m_current_min_xy_dist_delta) + avoidance_tasks.emplace_back(task); + } - // Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1 - start_layer = std::max(start_layer, LayerIndex(1)); - // minDist as the delta was already added, also avoidance for layer 0 will return the collision. - Polygons latest_avoidance = getAvoidance(radius, start_layer - 1, type, false, true); - // ### main loop doing the calculation - for (LayerIndex layer = start_layer; layer <= max_required_layer; ++ layer) { - key.second = layer; - const Polygons &col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ? - getCollisionHolefree(radius, layer, true) : - getCollision(radius, layer, true); - latest_avoidance = polygons_simplify(union_(offset(union_ex(latest_avoidance), -offset_speed, ClipperLib::jtRound, m_min_resolution), col), m_min_resolution); - data[layer] = std::pair(key, latest_avoidance); - } - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - { - std::lock_guard critical_section(*m_critical_progress); - if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { - m_precalculation_progress += m_support_rests_on_model ? 0.4 : 1 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3); - Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - } - } -#endif - - (slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_holefree : m_avoidance_cache).insert(std::move(data)); + tbb::parallel_for(tbb::blocked_range(0, avoidance_tasks.size(), 1), + [this, &avoidance_tasks](const tbb::blocked_range &range) { + for (size_t task_idx = range.begin(); task_idx < range.end(); ++ task_idx) { + const AvoidanceTask &task = avoidance_tasks[task_idx]; + assert(! task.holefree() || task.radius < m_increase_until_radius + m_current_min_xy_dist_delta); + if (task.to_model) + // ensuring Placeableareas are calculated + getPlaceableAreas(task.radius, task.max_required_layer); + // The following loop propagating avoidance regions bottom up is inherently serial. + const bool collision_holefree = (task.slow() || task.holefree()) && task.radius < m_increase_until_radius + m_current_min_xy_dist_delta; + const float max_move = task.slow() ? m_max_move_slow : m_max_move; + // minDist as the delta was already added, also avoidance for layer 0 will return the collision. + Polygons latest_avoidance = getAvoidance(task.radius, task.start_layer - 1, task.type, task.to_model, true); + std::vector> data; + data.reserve(task.max_required_layer + 1 - task.start_layer); + for (LayerIndex layer_idx = task.start_layer; layer_idx <= task.max_required_layer; ++ layer_idx) { + latest_avoidance = union_( + // Propagate avoidance region from the layers below, adjust for allowed tilt of the tree branch. + offset(union_ex(latest_avoidance), -max_move, ClipperLib::jtRound, m_min_resolution), + // Add current layer collisions. + collision_holefree ? getCollisionHolefree(task.radius, layer_idx) : getCollision(task.radius, layer_idx, true)); + if (task.to_model) + latest_avoidance = diff(latest_avoidance, getPlaceableAreas(task.radius, layer_idx)); + latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution); + data.emplace_back(RadiusLayerPair{task.radius, layer_idx}, latest_avoidance); } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + { + std::lock_guard critical_section(*m_critical_progress); + if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { + m_precalculation_progress += to_model ? + 0.4 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3) : + m_support_rests_on_model ? 0.4 : 1 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3); + Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); + } + } +#endif + avoidance_cache(task.type, task.to_model).insert(std::move(data)); } }); } -void TreeModelVolumes::calculatePlaceables(std::deque keys) + +void TreeModelVolumes::calculatePlaceables(const std::vector &keys) { tbb::parallel_for(tbb::blocked_range(0, keys.size()), - [&, keys](const tbb::blocked_range &range) { - for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { - const coord_t radius = keys[key_idx].first; - const LayerIndex max_required_layer = keys[key_idx].second; - std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); - RadiusLayerPair key(radius, 0); - - 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; - } - - if (start_layer == 0) { - data[0] = std::pair(key, diff(m_machine_border, getCollision(radius, 0, true))); - start_layer = 1; - } - - for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) { - key.second = layer; - Polygons placeable = getPlaceableAreas(0, layer); - placeable = polygons_simplify(placeable, m_min_resolution); // it is faster to do this here in each thread than once in calculateCollision. - placeable = offset(union_ex(placeable), - radius, jtMiter, 1.2); - data[layer] = std::pair(key, placeable); - } - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - { - std::lock_guard critical_section(*m_critical_progress); - if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { - m_precalculation_progress += 0.2 * TREE_PROGRESS_PRECALC_AVO / (keys.size()); - Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - } - } -#endif - - m_placeable_areas_cache.insert(std::move(data)); - } - }); + [&, keys](const tbb::blocked_range& range) { + for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) + this->calculatePlaceables(keys[key_idx].first, keys[key_idx].second); + }); } -void TreeModelVolumes::calculateAvoidanceToModel(std::deque keys) +void TreeModelVolumes::calculatePlaceables(const coord_t radius, const LayerIndex max_required_layer) { - // 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::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::FastSafe; - coord_t radius = keys[key_idx].first; - LayerIndex max_required_layer = keys[key_idx].second; + 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 ?"; + return; + } - // do not calculate not needed safe avoidances - if (holefree && radius >= m_increase_until_radius + m_current_min_xy_dist_delta) - continue; + std::vector data(max_required_layer + 1 - start_layer, Polygons{}); - getPlaceableAreas(radius, max_required_layer); // ensuring Placeableareas are calculated - const coord_t offset_speed = slow ? m_max_move_slow : m_max_move; - std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); - RadiusLayerPair key(radius, 0); - - 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; - } - // Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1 - start_layer = std::max(start_layer, LayerIndex(1)); - // minDist as the delta was already added, also avoidance for layer 0 will return the collision. - Polygons latest_avoidance = getAvoidance(radius, start_layer - 1, type, true, true); - // ### main loop doing the calculation - for (LayerIndex layer = start_layer; layer <= max_required_layer; ++ layer) { - key.second = layer; - const Polygons &col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ? - getCollisionHolefree(radius, layer, true) : - getCollision(radius, layer, true); - latest_avoidance = polygons_simplify(diff(union_(offset(union_ex(latest_avoidance), -offset_speed, ClipperLib::jtRound, m_min_resolution), col), getPlaceableAreas(radius, layer)), m_min_resolution); - data[layer] = std::pair(key, latest_avoidance); - } + if (start_layer == 0) + data[0] = diff(m_machine_border, getCollision(radius, 0, true)); + tbb::parallel_for(tbb::blocked_range(std::max(1, start_layer), max_required_layer + 1), + [this, &data, radius, start_layer](const tbb::blocked_range& range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) + data[layer_idx - start_layer] = offset(union_ex(getPlaceableAreas(0, layer_idx)), - radius, jtMiter, 1.2); + }); #ifdef SLIC3R_TREESUPPORTS_PROGRESS - { - std::lock_guard critical_section(*m_critical_progress); - if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { - m_precalculation_progress += 0.4 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3); - Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - } - } -#endif - - (slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_holefree_to_model : m_avoidance_cache_to_model).insert(std::move(data)); + { + std::lock_guard critical_section(*m_critical_progress); + if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { + m_precalculation_progress += 0.2 * TREE_PROGRESS_PRECALC_AVO / (keys.size()); + Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); } - }); + } +#endif + m_placeable_areas_cache.insert(std::move(data), start_layer, radius); } -void TreeModelVolumes::calculateWallRestrictions(std::deque keys) +void TreeModelVolumes::calculateWallRestrictions(const std::vector &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. // As they exist to prevent accidentially moving though a wall at high speed between layers like thie (x = wall,i = influence area,o= empty space,d = blocked area because of z distance) Assume maximum movement distance is two characters and maximum safe movement distance of one character @@ -742,65 +701,62 @@ void TreeModelVolumes::calculateWallRestrictions(std::deque key tbb::parallel_for(tbb::blocked_range(0, keys.size()), [&, keys](const tbb::blocked_range &range) { for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { - coord_t radius = keys[key_idx].first; - RadiusLayerPair key(radius, 0); - 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; - - for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= keys[key_idx].second; layer_idx++) { - key.second = layer_idx; - LayerIndex layer_idx_below = layer_idx - 1; - 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) { - 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); + const coord_t radius = keys[key_idx].first; + const LayerIndex max_required_layer = keys[key_idx].second; + const coord_t min_layer_bottom = std::max(1, m_wall_restrictions_cache.getMaxCalculatedLayer(radius)); + const size_t buffer_size = max_required_layer + 1 - min_layer_bottom; + std::vector data(buffer_size, Polygons{}); + std::vector data_min; + if (m_current_min_xy_dist_delta > 0) + data_min.assign(buffer_size, Polygons{}); + tbb::parallel_for(tbb::blocked_range(min_layer_bottom, max_required_layer + 1), + [this, &data, &data_min, radius, min_layer_bottom](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + data[layer_idx - min_layer_bottom] = polygons_simplify( + // radius contains m_current_min_xy_dist_delta already if required + intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx - 1, true)), + m_min_resolution); + if (! data_min.empty()) + data_min[layer_idx - min_layer_bottom] = + polygons_simplify( + intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx - 1, true)), + m_min_resolution); } - } - m_wall_restrictions_cache.insert(std::move(data)); - m_wall_restrictions_cache_min.insert(std::move(data_min)); + }); + m_wall_restrictions_cache.insert(std::move(data), min_layer_bottom, radius); + if (! data_min.empty()) + m_wall_restrictions_cache_min.insert(std::move(data_min), min_layer_bottom, radius); } }); } -coord_t TreeModelVolumes::ceilRadius(coord_t radius) const +coord_t TreeModelVolumes::ceilRadius(const coord_t radius) const { if (radius == 0) return 0; - if (radius <= m_radius_0) - return m_radius_0; - - if (SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION) - { + coord_t out = m_radius_0; + if (radius > m_radius_0) { // generate SUPPORT_TREE_PRE_EXPONENTIAL_STEPS of radiis before starting to exponentially increase them. - - coord_t exponential_result = SUPPORT_TREE_EXPONENTIAL_THRESHOLD * SUPPORT_TREE_EXPONENTIAL_FACTOR; - const coord_t stepsize = (exponential_result - m_radius_0) / (SUPPORT_TREE_PRE_EXPONENTIAL_STEPS + 1); - coord_t result = m_radius_0; - for (size_t step = 0; step < SUPPORT_TREE_PRE_EXPONENTIAL_STEPS; step++) { - result += stepsize; - if (result >= radius && !m_ignorable_radii.count(result)) - return result; + coord_t initial_radius_delta = SUPPORT_TREE_EXPONENTIAL_THRESHOLD - m_radius_0; + auto ignore = [this](coord_t r) { return std::binary_search(m_ignorable_radii.begin(), m_ignorable_radii.end(), r); }; + if (initial_radius_delta > SUPPORT_TREE_COLLISION_RESOLUTION) { + const int num_steps = round_up_divide(initial_radius_delta, SUPPORT_TREE_EXPONENTIAL_THRESHOLD); + const int stepsize = initial_radius_delta / num_steps; + out += stepsize; + for (auto step = 0; step < num_steps; ++ step) { + if (out >= radius && ! ignore(out)) + return out; + out += stepsize; + } + } else + out += SUPPORT_TREE_COLLISION_RESOLUTION; + while (out < radius || ignore(out)) { + assert(out * SUPPORT_TREE_EXPONENTIAL_FACTOR > out + SUPPORT_TREE_COLLISION_RESOLUTION); + out = out * SUPPORT_TREE_EXPONENTIAL_FACTOR; } - - while (exponential_result < radius || m_ignorable_radii.count(exponential_result)) - exponential_result = std::max(coord_t(exponential_result * SUPPORT_TREE_EXPONENTIAL_FACTOR), exponential_result + SUPPORT_TREE_COLLISION_RESOLUTION); - return exponential_result; - } - else - { // generates equidistant steps of size SUPPORT_TREE_COLLISION_RESOLUTION starting from m_radius_0. If SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION then this code is dead, and can safely be removed. - coord_t ceil_step_n = (radius - m_radius_0) / SUPPORT_TREE_COLLISION_RESOLUTION; - coord_t resulting_ceil = m_radius_0 + (ceil_step_n + ((radius - m_radius_0) % SUPPORT_TREE_COLLISION_RESOLUTION != 0)) * SUPPORT_TREE_COLLISION_RESOLUTION; - return - radius <= m_radius_0 && radius != 0 ? m_radius_0 : - m_ignorable_radii.count(resulting_ceil) ? ceilRadius(resulting_ceil + 1) : resulting_ceil; } + return out; } } diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 5f4b66772..18765be58 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -211,7 +211,8 @@ public: { Slow, FastSafe, - Fast + Fast, + Count }; /*! @@ -220,7 +221,7 @@ public: * 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); + void precalculate(const coord_t max_layer); /*! * \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. @@ -233,7 +234,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) const; + const Polygons& getCollision(const 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 @@ -277,8 +278,12 @@ 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 { - return this->ceilRadius(min_xy_dist ? radius : radius + m_current_min_xy_dist_delta); + coord_t ceilRadius(const coord_t radius, const bool min_xy_dist) const { + assert(radius >= 0); + return min_xy_dist ? + this->ceilRadius(radius) : + // special case as if a radius 0 is requested it could be to ensure correct xy distance. As such it is beneficial if the collision is as close to the configured values as possible. + radius > 0 ? this->ceilRadius(radius + m_current_min_xy_dist_delta) : 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. @@ -288,6 +293,7 @@ public: * \return The maximum radius, resulting in the same rounding. */ coord_t getRadiusNextCeil(coord_t radius, bool min_xy_dist) const { + assert(radius > 0); return min_xy_dist ? this->ceilRadius(radius) : this->ceilRadius(radius + m_current_min_xy_dist_delta) - m_current_min_xy_dist_delta; @@ -313,11 +319,22 @@ private: for (auto& d : in) this->data.emplace(d.first, std::move(d.second)); } - void insert(std::vector> && in) { + void insert(std::vector> &&in) { std::lock_guard guard(this->mutex); for (auto& d : in) this->data.emplace(d.first, std::move(d.second)); } + // by layer + void insert(std::vector> &&in, coord_t radius) { + std::lock_guard guard(this->mutex); + for (auto &d : in) + this->data.emplace(RadiusLayerPair{ radius, d.first }, std::move(d.second)); + } + void insert(std::vector &&in, coord_t first_layer_idx, coord_t radius) { + std::lock_guard guard(this->mutex); + for (auto &d : in) + this->data.emplace(RadiusLayerPair{ radius, first_layer_idx ++ }, std::move(d)); + } /*! * \brief Checks a cache for a given RadiusLayerPair and returns it if it is found * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. @@ -359,19 +376,20 @@ private: * 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. + * minimum xy distance is always used. * \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; + const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx) const; /*! * \brief Round \p radius upwards to either a multiple of m_radius_sample_resolution or a exponentially increasing value * * \param radius The radius of the node of interest */ - coord_t ceilRadius(coord_t radius) const; + coord_t ceilRadius(const coord_t radius) const; /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. @@ -380,18 +398,8 @@ private: * collide with the model. Result is saved in the cache. * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. */ - void calculateCollision(std::deque keys); - /*! - * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. - * - * The result is a 2D area that would cause nodes of given radius to - * collide with the model. Result is saved in the cache. - * \param key RadiusLayerPairs the requested areas. The radius will be calculated up to the provided layer. - */ - void calculateCollision(RadiusLayerPair key) - { - calculateCollision(std::deque{ RadiusLayerPair(key) }); - } + void calculateCollision(const std::vector &keys); + void calculateCollision(const coord_t radius, const LayerIndex max_layer_idx); /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed. * @@ -400,7 +408,7 @@ private: * A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall. * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. */ - void calculateCollisionHolefree(std::deque keys); + void calculateCollisionHolefree(const std::vector &keys); /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed. @@ -412,7 +420,7 @@ private: */ void calculateCollisionHolefree(RadiusLayerPair key) { - calculateCollisionHolefree(std::deque{ RadiusLayerPair(key) }); + calculateCollisionHolefree(std::vector{ RadiusLayerPair(key) }); } /*! @@ -422,7 +430,7 @@ private: * collide with the model. Result is saved in the cache. * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. */ - void calculateAvoidance(std::deque keys); + void calculateAvoidance(const std::vector &keys, bool to_build_plate, bool to_model); /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model. @@ -431,9 +439,9 @@ private: * collide with the model. Result is saved in the cache. * \param key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer. */ - void calculateAvoidance(RadiusLayerPair key) + void calculateAvoidance(RadiusLayerPair key, bool to_build_plate, bool to_model) { - calculateAvoidance(std::deque{ RadiusLayerPair(key) }); + calculateAvoidance(std::vector{ RadiusLayerPair(key) }, to_build_plate, to_model); } /*! @@ -441,38 +449,16 @@ private: * Result is saved in the cache. * \param key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer. */ - void calculatePlaceables(RadiusLayerPair key) - { - calculatePlaceables(std::deque{ key }); - } + void calculatePlaceables(const coord_t radius, const LayerIndex max_required_layer); + /*! * \brief Creates the areas where a branch of a given radius can be placed on the model. * Result is saved in the cache. * \param keys RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. */ - void calculatePlaceables(std::deque keys); + void calculatePlaceables(const std::vector &keys); - /*! - * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model without being able to place a branch with given radius on a single layer. - * - * The result is a 2D area that would cause nodes of radius \p radius to - * collide with the model in a not wanted way. Result is saved in the cache. - * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. - */ - void calculateAvoidanceToModel(std::deque keys); - - /*! - * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model without being able to place a branch with given radius on a single layer. - * - * The result is a 2D area that would cause nodes of radius \p radius to - * collide with the model in a not wanted way. Result is saved in the cache. - * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. - */ - void calculateAvoidanceToModel(RadiusLayerPair key) - { - calculateAvoidanceToModel(std::deque{ RadiusLayerPair(key) }); - } /*! * \brief Creates the areas that can not be passed when expanding an area downwards. As such these areas are an somewhat abstract representation of a wall (as in a printed object). * @@ -480,7 +466,7 @@ private: * * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. */ - void calculateWallRestrictions(std::deque keys); + void calculateWallRestrictions(const std::vector &keys); /*! * \brief Creates the areas that can not be passed when expanding an area downwards. As such these areas are an somewhat abstract representation of a wall (as in a printed object). @@ -489,7 +475,7 @@ private: */ void calculateWallRestrictions(RadiusLayerPair key) { - calculateWallRestrictions(std::deque{ RadiusLayerPair(key) }); + calculateWallRestrictions(std::vector{ RadiusLayerPair(key) }); } /*! @@ -556,9 +542,9 @@ private: */ std::vector m_anti_overhang; /*! - * \brief Radii that can be ignored by ceilRadius as they will never be requested. + * \brief Radii that can be ignored by ceilRadius as they will never be requested, sorted. */ - std::unordered_set m_ignorable_radii; + std::vector m_ignorable_radii; /*! * \brief Smallest radius a branch can have. This is the radius of a SupportElement with DTT=0. @@ -581,18 +567,39 @@ private: * \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_holefree; - RadiusLayerPolygonCache m_avoidance_cache_holefree_to_model; + RadiusLayerPolygonCache m_avoidance_cache_holefree; + RadiusLayerPolygonCache m_avoidance_cache_holefree_to_model; + + RadiusLayerPolygonCache& avoidance_cache(const AvoidanceType type, const bool to_model) { + if (to_model) { + switch (type) { + case AvoidanceType::Fast: return m_avoidance_cache_to_model; + case AvoidanceType::Slow: return m_avoidance_cache_to_model_slow; + case AvoidanceType::FastSafe: return m_avoidance_cache_holefree_to_model; + } + } else { + switch (type) { + case AvoidanceType::Fast: return m_avoidance_cache; + case AvoidanceType::Slow: return m_avoidance_cache_slow; + case AvoidanceType::FastSafe: return m_avoidance_cache_holefree; + } + } + assert(false); + return m_avoidance_cache; + } + const RadiusLayerPolygonCache& avoidance_cache(const AvoidanceType type, const bool to_model) const { + return const_cast(this)->avoidance_cache(type, to_model); + } /*! * \brief Caches to represent walls not allowed to be passed over. */ - RadiusLayerPolygonCache m_wall_restrictions_cache; + RadiusLayerPolygonCache m_wall_restrictions_cache; // 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; + RadiusLayerPolygonCache m_wall_restrictions_cache_min; #ifdef SLIC3R_TREESUPPORTS_PROGRESS std::unique_ptr m_critical_progress { std::make_unique() }; diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 2d7debea9..0bcab9318 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -874,9 +874,9 @@ static std::optional> polyline_sample_next_point_at_dis if (area(expoly) <= 0.) ::MessageBoxA(nullptr, "TreeSupport infill negative area", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); #endif // _WIN32 - assert(intersecting_edges(expoly).empty()); + assert(intersecting_edges(to_polygons(expoly)).empty()); #ifdef _WIN32 - if (! intersecting_edges(expoly).empty()) + if (! intersecting_edges(to_polygons(expoly)).empty()) ::MessageBoxA(nullptr, "TreeSupport infill self intersections", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); #endif // _WIN32 Surface surface(stInternal, std::move(expoly)); @@ -1926,13 +1926,13 @@ void TreeSupport::increaseAreas(std::unordered_map& to constexpr bool increase_radius = true, no_error = true, use_min_radius = true, move = true; // aliases for better readability // Determine in which order configurations are checked if they result in a valid influence area. Check will stop if a valid area is found - std::deque order; + std::vector order; auto insertSetting = [&](AreaIncreaseSettings settings, bool back) { if (std::find(order.begin(), order.end(), settings) == order.end()) { if (back) order.emplace_back(settings); else - order.emplace_front(settings); + order.insert(order.begin(), settings); } }; @@ -1973,7 +1973,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to if (elem.use_min_xy_dist) { - std::deque new_order; + 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) { @@ -2444,15 +2444,16 @@ void TreeSupport::generateBranchAreas( // 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 (const ExPolygon &part : nozzle_path) { + for (ExPolygon &part : nozzle_path) { if (part.contains(elem->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; - moveInside(part, from, 0); + Polygons &to = to_polygons(std::move(part)); + moveInside(to, from, 0); if ((elem->result_on_layer - from).cast().norm() < scaled(0.025)) - polygons_with_correct_center = union_(polygons_with_correct_center, part); + 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. diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 041344375..d4e04ef64 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -37,17 +37,16 @@ #define SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL false #define SUPPORT_TREE_AVOID_SUPPORT_BLOCKER true -#define SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION true -#define SUPPORT_TREE_EXPONENTIAL_THRESHOLD 1000 -#define SUPPORT_TREE_EXPONENTIAL_FACTOR 1.5 -#define SUPPORT_TREE_PRE_EXPONENTIAL_STEPS 1 -#define SUPPORT_TREE_COLLISION_RESOLUTION 500 // Only has an effect if SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION is false namespace Slic3r { 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;