From 07e92a3239e2793135e39f291926bc9bc553e228 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Wed, 25 May 2022 20:20:40 +0200 Subject: [PATCH] Updated to 5.0. Fixed some potential bugs. Split ModelVolumes into its own files. Improved spelling and documentation. --- src/TreeModelVolumes.cpp | 1115 +++++++++++++++++++++++++--- src/TreeModelVolumes.h | 398 +++++++--- src/TreeSupport.cpp | 1485 ++++++-------------------------------- src/TreeSupport.h | 588 ++++----------- src/support.cpp | 16 +- src/utils/algorithm.h | 78 +- 6 files changed, 1758 insertions(+), 1922 deletions(-) diff --git a/src/TreeModelVolumes.cpp b/src/TreeModelVolumes.cpp index 7479d5a828..cc9c4d2225 100644 --- a/src/TreeModelVolumes.cpp +++ b/src/TreeModelVolumes.cpp @@ -1,161 +1,1098 @@ //Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. -#include "sliceDataStorage.h" #include "TreeModelVolumes.h" - +#include "TreeSupport.h" +#include "progress/Progress.h" +#include "sliceDataStorage.h" +#include "utils/algorithm.h" +#include "utils/logoutput.h" namespace cura { -TreeModelVolumes::TreeModelVolumes(const SliceDataStorage& storage, const Settings& settings) - : machine_border_(calculateMachineBorderCollision(storage.getMachineBorder())) - , xy_distance_(settings.get("support_xy_distance")) - , xy_distance_overhang(settings.get("support_xy_distance_overhang")) - , distance_priority(settings.get("support_xy_overrides_z")) - , radius_sample_resolution_(settings.get("support_tree_collision_resolution")) +TreeModelVolumes::TreeModelVolumes(const SliceDataStorage& storage, 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) : max_move_{ std::max(max_move - 2, coord_t(0)) }, max_move_slow_{ std::max(max_move_slow - 2, coord_t(0)) }, progress_multiplier{ progress_multiplier }, progress_offset{ progress_offset }, machine_border_{ calculateMachineBorderCollision(storage.getMachineBorder()) } // -2 to avoid rounding errors +{ + anti_overhang_ = std::vector(storage.support.supportLayers.size(), Polygons()); + std::unordered_map mesh_to_layeroutline_idx; + min_maximum_deviation_ = std::numeric_limits::max(); + min_maximum_resolution_ = std::numeric_limits::max(); + support_rests_on_model = false; + for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) + { + SliceMeshStorage mesh = storage.meshes[mesh_idx]; + bool added = false; + for (size_t idx = 0; idx < layer_outlines_.size(); idx++) + { + if (checkSettingsEquality(layer_outlines_[idx].first, mesh.settings)) + { + added = true; + mesh_to_layeroutline_idx[mesh_idx] = idx; + } + } + if (!added) + { + mesh_to_layeroutline_idx[mesh_idx] = layer_outlines_.size(); + layer_outlines_.emplace_back(mesh.settings, std::vector(storage.support.supportLayers.size(), Polygons())); + } + } + + for (auto data_pair : layer_outlines_) + { + support_rests_on_model |= data_pair.first.get("support_type") == ESupportType::EVERYWHERE; + min_maximum_deviation_ = std::min(min_maximum_deviation_, data_pair.first.get("meshfix_maximum_deviation")); + min_maximum_resolution_ = std::min(min_maximum_resolution_, data_pair.first.get("meshfix_maximum_resolution")); + } + + min_maximum_deviation_ = std::min(coord_t(SUPPORT_TREE_MAX_DEVIATION), min_maximum_deviation_); + current_outline_idx = mesh_to_layeroutline_idx[current_mesh_idx]; + TreeSupport::TreeSupportSettings config(layer_outlines_[current_outline_idx].first); + + if (config.support_overrides == SupportDistPriority::Z_OVERRIDES_XY) + { + current_min_xy_dist = config.xy_min_distance; + + if (TreeSupport::TreeSupportSettings::has_to_rely_on_min_xy_dist_only) + { + current_min_xy_dist = std::max(current_min_xy_dist, coord_t(100)); + } + + current_min_xy_dist_delta = std::max(config.xy_distance - current_min_xy_dist, coord_t(0)); + } + else + { + current_min_xy_dist = config.xy_distance; + current_min_xy_dist_delta = 0; + } + increase_until_radius = config.increase_radius_until_radius; + + for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) + { + SliceMeshStorage mesh = storage.meshes[mesh_idx]; + + cura::parallel_for(0, LayerIndex(layer_outlines_[mesh_to_layeroutline_idx[mesh_idx]].second.size()), 1, + [&](const LayerIndex layer_idx) + { + if (mesh.layer_nr_max_filled_layer < layer_idx) + { + return; // cant break as parallel_for wont allow it, this is equivalent to a continue + } + Polygons outline = extractOutlineFromMesh(mesh, layer_idx); + layer_outlines_[mesh_to_layeroutline_idx[mesh_idx]].second[layer_idx].add(outline); + }); + } + cura::parallel_for(0, LayerIndex(anti_overhang_.size()), 1, + [&](const LayerIndex layer_idx) + { + if (layer_idx < coord_t(additional_excluded_areas.size())) + { + anti_overhang_[layer_idx].add(additional_excluded_areas[layer_idx]); + } + + if (SUPPORT_TREE_AVOID_SUPPORT_BLOCKER) + { + anti_overhang_[layer_idx].add(storage.support.supportLayers[layer_idx].anti_overhang); + } + + if (storage.primeTower.enabled) + { + anti_overhang_[layer_idx].add(layer_idx == 0 ? storage.primeTower.outer_poly_first_layer : storage.primeTower.outer_poly); + } + anti_overhang_[layer_idx] = anti_overhang_[layer_idx].unionPolygons(); + }); + + for (size_t idx = 0; idx < layer_outlines_.size(); idx++) + { + cura::parallel_for(0, LayerIndex(anti_overhang_.size()), 1, [&](const LayerIndex layer_idx) { layer_outlines_[idx].second[layer_idx] = layer_outlines_[idx].second[layer_idx].unionPolygons(); }); + } + radius_0 = config.getRadius(0); +} + + +void TreeModelVolumes::precalculate(coord_t max_layer) +{ + auto t_start = std::chrono::high_resolution_clock::now(); + 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. + TreeSupport::TreeSupportSettings config(layer_outlines_[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) + current_min_xy_dist_delta)); + } + // 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) + 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) + 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; + } + } + + // 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()); + + // Append additional radiis needed for collision. + + radius_until_layer[ceilRadius(increase_until_radius, false)] = max_layer; // To calculate collision holefree for every radius, the collision of radius increase_until_radius will be required. + // 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 (current_min_xy_dist_delta != 0) + { + radius_until_layer[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. + + + // ### 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; + for (RadiusLayerPair key : relevant_avoidance_radiis) + { + if (key.first < increase_until_radius + current_min_xy_dist_delta) + { + relevant_hole_collision_radiis.emplace_back(key); + } + } + + // ### Calculate collisions without holes, build from regular collision + calculateCollisionHolefree(relevant_hole_collision_radiis); + + auto t_coll = std::chrono::high_resolution_clock::now(); + + // ### Calculate the relevant avoidances in parallel as far as possible + { + std::future placeable_waiter; + if (support_rests_on_model) + { + placeable_waiter = calculatePlaceables(relevant_avoidance_radiis_to_model); + } + std::future avoidance_waiter = calculateAvoidance(relevant_avoidance_radiis); + std::future wall_restriction_waiter = calculateWallRestrictions(relevant_avoidance_radiis); + if (support_rests_on_model) + { + placeable_waiter.wait(); + std::future avoidance_model_waiter = calculateAvoidanceToModel(relevant_avoidance_radiis_to_model); + avoidance_model_waiter.wait(); + } + avoidance_waiter.wait(); + wall_restriction_waiter.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(); + + log("Precalculating collision took %.3lf ms. Precalculating avoidance took %.3lf ms.\n", dur_col, dur_avo); +} + +const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) +{ + coord_t orig_radius = radius; + std::optional> result; + if (!min_xy_dist) + { + radius += 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::lock_guard critical_section_support_max_layer_nr(*critical_avoidance_cache_); + result = getArea(collision_cache_, key); + } + if (result) + { + return result.value().get(); + } + if (precalculated) + { + logWarning("Had to calculate collision at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); + } + calculateCollision(key); + return getCollision(orig_radius, layer_idx, min_xy_dist); +} + +const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) { - const coord_t layer_height = settings.get("layer_height"); - const AngleRadians angle = settings.get("support_tree_angle"); - max_move_ = (angle < TAU / 4) ? (coord_t)(tan(angle) * layer_height) : std::numeric_limits::max(); - z_distance_layers = round_up_divide(settings.get("support_top_distance"), layer_height) + 1; - for (std::size_t layer_idx = 0; layer_idx < storage.support.supportLayers.size(); ++layer_idx) + coord_t orig_radius = radius; + std::optional> result; + if (!min_xy_dist) + { + radius += current_min_xy_dist_delta; + } + if (radius >= increase_until_radius + current_min_xy_dist_delta) + { + return getCollision(orig_radius, layer_idx, min_xy_dist); + } + RadiusLayerPair key{ radius, layer_idx }; + + { + std::lock_guard critical_section_support_max_layer_nr(*critical_collision_cache_holefree_); + result = getArea(collision_cache_holefree_, key); + } + if (result) + { + return result.value().get(); + } + if (precalculated) { - constexpr bool include_support = false; - constexpr bool include_prime_tower = true; - layer_outlines_.push_back(storage.getLayerOutlines(layer_idx, include_support, include_prime_tower)); + logWarning("Had to calculate collision holefree at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); } + calculateCollisionHolefree(key); + return getCollisionHolefree(orig_radius, layer_idx, min_xy_dist); } -const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx) const +const Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) { + 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); + } + + coord_t orig_radius = radius; + + std::optional> result; + + if (!min_xy_dist) + { + radius += current_min_xy_dist_delta; + } radius = ceilRadius(radius); - RadiusLayerPair key{radius, layer_idx}; - const auto it = collision_cache_.find(key); - if (it != collision_cache_.end()) + + if (radius >= increase_until_radius + current_min_xy_dist_delta && type == AvoidanceType::FAST_SAFE) // no holes anymore by definition at this request + { + type = AvoidanceType::FAST; + } + + const RadiusLayerPair key{ radius, layer_idx }; + + std::unordered_map* cache_ptr = nullptr; + std::mutex* mutex_ptr; + if (!to_model && type == AvoidanceType::FAST) { - return it->second; + cache_ptr = &avoidance_cache_; + mutex_ptr = critical_avoidance_cache_.get(); + } + else if (!to_model && type == AvoidanceType::SLOW) + { + cache_ptr = &avoidance_cache_slow_; + mutex_ptr = critical_avoidance_cache_slow_.get(); + } + else if (!to_model && type == AvoidanceType::FAST_SAFE) + { + cache_ptr = &avoidance_cache_hole_; + mutex_ptr = critical_avoidance_cache_holefree_.get(); + } + else if (to_model && type == AvoidanceType::FAST) + { + cache_ptr = &avoidance_cache_to_model_; + mutex_ptr = critical_avoidance_cache_to_model_.get(); + } + else if (to_model && type == AvoidanceType::SLOW) + { + cache_ptr = &avoidance_cache_to_model_slow_; + mutex_ptr = critical_avoidance_cache_to_model_slow_.get(); + } + else if (to_model && type == AvoidanceType::FAST_SAFE) + { + cache_ptr = &avoidance_cache_hole_to_model_; + mutex_ptr = critical_avoidance_cache_holefree_to_model_.get(); } else { - return calculateCollision(key); + logError("Invalid Avoidance Request\n"); + } + + + if (to_model) + { + { + std::lock_guard critical_section(*mutex_ptr); + result = getArea(*cache_ptr, key); + } + if (result) + { + return result.value().get(); + } + if (precalculated) + { + logWarning("Had to calculate Avoidance to model at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); + } + calculateAvoidanceToModel(key); + } + else + { + { + std::lock_guard critical_section(*mutex_ptr); + result = getArea(*cache_ptr, key); + } + if (result) + { + return result.value().get(); + } + if (precalculated) + { + logWarning("Had to calculate Avoidance at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); + } + 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 Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_idx) const +const Polygons& TreeModelVolumes::getPlaceableAreas(coord_t radius, LayerIndex layer_idx) { + std::optional> result; + const coord_t orig_radius = radius; radius = ceilRadius(radius); - RadiusLayerPair key{radius, layer_idx}; - const auto it = avoidance_cache_.find(key); - if (it != avoidance_cache_.end()) + RadiusLayerPair key{ radius, layer_idx }; + { - return it->second; + std::lock_guard critical_section(*critical_placeable_areas_cache_); + result = getArea(placeable_areas_cache_, key); + } + if (result) + { + return result.value().get(); + } + if (precalculated) + { + logWarning("Had to calculate Placeable Areas at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", radius, layer_idx); + } + if (radius != 0) + { + calculatePlaceables(key); } else { - return calculateAvoidance(key); + getCollision(0, layer_idx, true); } + return getPlaceableAreas(orig_radius, layer_idx); } -const Polygons& TreeModelVolumes::getInternalModel(coord_t radius, LayerIndex layer_idx) const + +const Polygons& TreeModelVolumes::getWallRestriction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) { + 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); + } + + coord_t orig_radius = radius; + min_xy_dist = min_xy_dist && current_min_xy_dist_delta > 0; + + std::optional> result; + radius = ceilRadius(radius); - RadiusLayerPair key{radius, layer_idx}; - const auto it = internal_model_cache_.find(key); - if (it != internal_model_cache_.end()) + const RadiusLayerPair key{ radius, layer_idx }; + + std::unordered_map* cache_ptr; + if (min_xy_dist) { - return it->second; + cache_ptr = &wall_restrictions_cache_min_; } else { - return calculateInternalModel(key); + cache_ptr = &wall_restrictions_cache_; } + + + if (min_xy_dist) + { + { + std::lock_guard critical_section(*critical_wall_restrictions_cache_min_); + result = getArea(*cache_ptr, key); + } + if (result) + { + return result.value().get(); + } + if (precalculated) + { + logWarning("Had to calculate Wall restricions at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); + } + } + else + { + { + std::lock_guard critical_section(*critical_wall_restrictions_cache_); + result = getArea(*cache_ptr, key); + } + if (result) + { + return result.value().get(); + } + if (precalculated) + { + logWarning("Had to calculate Wall restricions at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); + } + } + calculateWallRestrictions(key); + 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) const +coord_t TreeModelVolumes::ceilRadius(coord_t radius, bool min_xy_dist) const { - const auto remainder = radius % radius_sample_resolution_; - const auto delta = remainder != 0 ? radius_sample_resolution_- remainder : 0; - return radius + delta; + if (!min_xy_dist) + { + radius += 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 -= current_min_xy_dist_delta; + return ceiled_radius; +} + +bool TreeModelVolumes::checkSettingsEquality(const Settings& me, const Settings& other) const +{ + return TreeSupport::TreeSupportSettings(me) == TreeSupport::TreeSupportSettings(other); +} + + +Polygons TreeModelVolumes::extractOutlineFromMesh(const SliceMeshStorage& mesh, 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 = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100)); + } + coord_t maximum_resolution = mesh.settings.get("meshfix_maximum_resolution"); + coord_t maximum_deviation = mesh.settings.get("meshfix_maximum_deviation"); + total.simplify(maximum_resolution, maximum_deviation); + return total; } -const Polygons& TreeModelVolumes::calculateCollision(const RadiusLayerPair& key) const +LayerIndex TreeModelVolumes::getMaxCalculatedLayer(coord_t radius, const std::unordered_map& map) const { - const coord_t radius = key.first; - const LayerIndex layer_idx = key.second; + LayerIndex max_layer = -1; - Polygons collision_areas = machine_border_; - if(layer_idx < static_cast(layer_outlines_.size())) + // 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)) { - if(distance_priority == SupportDistPriority::XY_OVERRIDES_Z) + max_layer = 1; + } + + while (map.count(RadiusLayerPair(radius, max_layer + 1))) + { + max_layer++; + } + + return max_layer; +} + + +void TreeModelVolumes::calculateCollision(std::deque keys) +{ + cura::parallel_for(0, keys.size(), 1, + [&](const size_t i) + { + coord_t radius = keys[i].first; + RadiusLayerPair key(radius, 0); + std::unordered_map data_outer; + std::unordered_map data_placeable_outer; + for (size_t outline_idx = 0; outline_idx < layer_outlines_.size(); outline_idx++) { - //If X/Y overrides Z, simply use the X/Y distance as distance to keep away from the model. - collision_areas = collision_areas.unionPolygons(layer_outlines_[layer_idx].offset(xy_distance_ + radius)); + std::unordered_map data; + std::unordered_map data_placeable; + + const coord_t layer_height = layer_outlines_[outline_idx].first.get("layer_height"); + const bool support_rests_on_this_model = layer_outlines_[outline_idx].first.get("support_type") == ESupportType::EVERYWHERE; + const coord_t z_distance_bottom = layer_outlines_[outline_idx].first.get("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(layer_outlines_[outline_idx].first.get("support_top_distance"), layer_height); + const LayerIndex max_anti_overhang_layer = anti_overhang_.size() - 1; + const LayerIndex max_required_layer = keys[i].second + std::max(coord_t(1), z_distance_top_layers); + const coord_t xy_distance = outline_idx == current_outline_idx ? current_min_xy_dist : layer_outlines_[outline_idx].first.get("support_xy_distance"); + // technically this causes collision for the normal xy_distance to be larger by 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; + { + std::lock_guard critical_section(*critical_collision_cache_); + min_layer_bottom = getMaxCalculatedLayer(radius, collision_cache_) - z_distance_bottom_layers; + } + + if (min_layer_bottom < 0) + { + min_layer_bottom = 0; + } + for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; layer_idx++) + { + key.second = layer_idx; + Polygons collision_areas = machine_border_; + if (size_t(layer_idx) < layer_outlines_[outline_idx].second.size()) + { + collision_areas.add(layer_outlines_[outline_idx].second[layer_idx]); + } + collision_areas = collision_areas.offset(radius + xy_distance); // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. + data[key].add(collision_areas); // if a key does not exist when it is accessed it is added! + } + + + // Add layers below, to ensure correct support_bottom_distance. Also save placeable areas of radius 0, if required for this mesh. + for (LayerIndex layer_idx = 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++) + { + data[key].add(data[RadiusLayerPair(radius, layer_idx - layer_offset)]); + } + if (support_rests_on_this_model && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) + { + data[key] = data[key].unionPolygons(); + Polygons above = data[RadiusLayerPair(radius, layer_idx + 1)]; + if (max_anti_overhang_layer >= layer_idx + 1) + { + above = above.unionPolygons(anti_overhang_[layer_idx]); + } + else + { + above = above.unionPolygons(); // just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly. + } + Polygons placeable = data[key].difference(above); + data_placeable[RadiusLayerPair(radius, layer_idx + 1)] = data_placeable[RadiusLayerPair(radius, layer_idx + 1)].unionPolygons(placeable); + } + } + + // 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; + for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + layer_idx <= max_required_layer; layer_offset++) + { + data[key].add(data[RadiusLayerPair(radius, layer_idx + layer_offset)]); + } + if (max_anti_overhang_layer >= layer_idx) + { + data[key] = data[key].unionPolygons(anti_overhang_[layer_idx].offset(radius)); + } + else + { + data[key] = data[key].unionPolygons(); + } + } + + for (LayerIndex layer_idx = max_required_layer; layer_idx > keys[i].second; layer_idx--) + { + data.erase(RadiusLayerPair(radius, layer_idx)); // all these dont have the correct z_distance_top_layers as they can still have areas above them + } + + for (auto pair : data) + { + pair.second.simplify(min_maximum_resolution_, min_maximum_deviation_); + data_outer[pair.first] = data_outer[pair.first].unionPolygons(pair.second); + } + if (radius == 0) + { + for (auto pair : data_placeable) + { + pair.second.simplify(min_maximum_resolution_, min_maximum_deviation_); + data_placeable_outer[pair.first] = data_placeable_outer[pair.first].unionPolygons(pair.second); + } + } } - else if(layer_idx + z_distance_layers < static_cast(layer_outlines_.size())) + + { + std::lock_guard critical_section(*critical_progress); + + if (precalculated && precalculation_progress < TREE_PROGRESS_PRECALC_COLL) + { + precalculation_progress += TREE_PROGRESS_PRECALC_COLL / keys.size(); + Progress::messageProgress(Progress::Stage::SUPPORT, precalculation_progress * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); + } + } + + { + std::lock_guard critical_section(*critical_collision_cache_); + collision_cache_.insert(data_outer.begin(), data_outer.end()); + } + if (radius == 0) { - //If Z overrides X/Y, use X/Y distance if the surface is vertical, or Min X/Y distance if not. - Polygons z_influenced = layer_outlines_[layer_idx + z_distance_layers].difference(layer_outlines_[layer_idx]).offset(xy_distance_); //In-between layers are ignored for performance. - Polygons collision_not_overhang = layer_outlines_[layer_idx].offset(xy_distance_); //In places where there is no overhang nearby, use the normal X/Y distance. - if(!z_influenced.empty()) { - collision_not_overhang = collision_not_overhang.difference(z_influenced); + std::lock_guard critical_section(*critical_placeable_areas_cache_); + placeable_areas_cache_.insert(data_placeable_outer.begin(), data_placeable_outer.end()); } - Polygons collision_model = collision_not_overhang.unionPolygons(layer_outlines_[layer_idx].offset(xy_distance_overhang)); //Apply the minimum distance everywhere else. - collision_areas = collision_areas.unionPolygons(collision_model.offset(radius)); } + }); +} +void TreeModelVolumes::calculateCollisionHolefree(std::deque 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); + } + + cura::parallel_for(0, max_layer + 1, 1, + [&](const LayerIndex layer_idx) + { + std::unordered_map data; + for (RadiusLayerPair key : keys) + { + // Logically increase the collision by increase_until_radius + coord_t radius = key.first; + coord_t increase_radius_ceil = ceilRadius(increase_until_radius, false) - ceilRadius(radius, true); + Polygons col = getCollision(increase_until_radius, layer_idx, false).offset(5 - increase_radius_ceil, ClipperLib::jtRound).unionPolygons(); // 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. + col.simplify(min_maximum_resolution_, min_maximum_deviation_); + data[RadiusLayerPair(radius, layer_idx)] = col; + } + + { + std::lock_guard critical_section(*critical_collision_cache_holefree_); + collision_cache_holefree_.insert(data.begin(), data.end()); + } + }); +} + + +// 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 +Polygons TreeModelVolumes::safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) const +{ + const size_t steps = std::abs(distance / max_safe_step_distance); + assert(distance * max_safe_step_distance >= 0); + Polygons ret = me; + + for (size_t i = 0; i < steps; i++) + { + ret = ret.offset(max_safe_step_distance, jt).unionPolygons(collision); } - const auto ret = collision_cache_.insert({key, std::move(collision_areas)}); - assert(ret.second); - return ret.first->second; + ret = ret.offset(distance % max_safe_step_distance, jt); + + return ret.unionPolygons(collision); +} + +std::future 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 }; + std::future ret = cura::parallel_for_nowait(0, keys.size() * 3, 1, + [&, keys, all_types](const size_t 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::FAST_SAFE; + + coord_t radius = keys[key_idx].first; + LayerIndex max_required_layer = keys[key_idx].second; + + // do not calculate not needed safe avoidances + if (holefree && radius >= increase_until_radius + current_min_xy_dist_delta) + { + return; + } + + const coord_t offset_speed = slow ? max_move_slow_ : max_move_; + const coord_t max_step_move = std::max(1.9 * radius, current_min_xy_dist * 1.9); + RadiusLayerPair key(radius, 0); + Polygons latest_avoidance; + LayerIndex start_layer; + { + std::lock_guard critical_section(*(slow ? critical_avoidance_cache_slow_ : holefree ? critical_avoidance_cache_holefree_ : critical_avoidance_cache_)); + start_layer = 1 + getMaxCalculatedLayer(radius, slow ? avoidance_cache_slow_ : holefree ? avoidance_cache_hole_ : avoidance_cache_); + } + if (start_layer > max_required_layer) + { + logDebug("Requested calculation for value already calculated ?\n"); + return; + } + start_layer = std::max(start_layer, LayerIndex(1)); // Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1 + std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); + + + latest_avoidance = getAvoidance(radius, start_layer - 1, type, false, true); // minDist as the delta was already added, also avoidance for layer 0 will return the collision. + + // ### main loop doing the calculation + for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) + { + key.second = layer; + Polygons col; + if ((slow && radius < increase_until_radius + current_min_xy_dist_delta) || holefree) + { + col = getCollisionHolefree(radius, layer, true); + } + else + { + col = getCollision(radius, layer, true); + } + latest_avoidance = safeOffset(latest_avoidance, -offset_speed, ClipperLib::jtRound, -max_step_move, col); + latest_avoidance.simplify(min_maximum_resolution_, min_maximum_deviation_); + data[layer] = std::pair(key, latest_avoidance); + } + + { + std::lock_guard critical_section(*critical_progress); + + if (precalculated && precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) + { + precalculation_progress += support_rests_on_model ? 0.4 : 1 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3); + Progress::messageProgress(Progress::Stage::SUPPORT, precalculation_progress * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); + } + } + + { + std::lock_guard critical_section(*(slow ? critical_avoidance_cache_slow_ : holefree ? critical_avoidance_cache_holefree_ : critical_avoidance_cache_)); + (slow ? avoidance_cache_slow_ : holefree ? avoidance_cache_hole_ : avoidance_cache_).insert(data.begin(), data.end()); + } + } + }); + return ret; +} + +std::future TreeModelVolumes::calculatePlaceables(std::deque keys) +{ + std::future ret = cura::parallel_for_nowait(0, keys.size(), 1, + [&, keys](const size_t 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; + { + std::lock_guard critical_section(*critical_placeable_areas_cache_); + start_layer = 1 + getMaxCalculatedLayer(radius, placeable_areas_cache_); + } + if (start_layer > max_required_layer) + { + logDebug("Requested calculation for value already calculated ?\n"); + return; + } + + if (start_layer == 0) + { + data[0] = std::pair(key, machine_border_.difference(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.simplify(min_maximum_resolution_, min_maximum_deviation_); // it is faster to do this here in each thread than once in calculateCollision. + placeable = placeable.offset(-radius); + + data[layer] = std::pair(key, placeable); + } + + { + std::lock_guard critical_section(*critical_progress); + + if (precalculated && precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) + { + precalculation_progress += 0.2 * TREE_PROGRESS_PRECALC_AVO / (keys.size()); + Progress::messageProgress(Progress::Stage::SUPPORT, precalculation_progress * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); + } + } + + { + std::lock_guard critical_section(*critical_placeable_areas_cache_); + placeable_areas_cache_.insert(data.begin(), data.end()); + } + }); + return ret; +} + + +std::future 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 }; + std::future ret = cura::parallel_for_nowait(0, keys.size() * 3, 1, + [&, keys, all_types](const size_t 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; + coord_t radius = keys[key_idx].first; + LayerIndex max_required_layer = keys[key_idx].second; + + // do not calculate not needed safe avoidances + if (holefree && radius >= increase_until_radius + current_min_xy_dist_delta) + { + return; + } + getPlaceableAreas(radius, max_required_layer); // ensuring Placeableareas are calculated + const coord_t offset_speed = slow ? max_move_slow_ : max_move_; + const coord_t max_step_move = std::max(1.9 * radius, current_min_xy_dist * 1.9); + Polygons latest_avoidance; + 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 ? critical_avoidance_cache_to_model_slow_ : holefree ? critical_avoidance_cache_holefree_to_model_ : critical_avoidance_cache_to_model_)); + start_layer = 1 + getMaxCalculatedLayer(radius, slow ? avoidance_cache_to_model_slow_ : holefree ? avoidance_cache_hole_to_model_ : avoidance_cache_to_model_); + } + if (start_layer > max_required_layer) + { + logDebug("Requested calculation for value already calculated ?\n"); + return; + } + start_layer = std::max(start_layer, LayerIndex(1)); + latest_avoidance = getAvoidance(radius, start_layer - 1, type, true, true); // minDist as the delta was already added, also avoidance for layer 0 will return the collision. + + // ### main loop doing the calculation + for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) + { + key.second = layer; + Polygons col = getCollision(radius, layer, true); + + if ((slow && radius < increase_until_radius + current_min_xy_dist_delta) || holefree) + { + col = getCollisionHolefree(radius, layer, true); + } + else + { + col = getCollision(radius, layer, true); + } + + latest_avoidance = safeOffset(latest_avoidance, -offset_speed, ClipperLib::jtRound, -max_step_move, col).difference(getPlaceableAreas(radius, layer)); + + latest_avoidance.simplify(min_maximum_resolution_, min_maximum_deviation_); + data[layer] = std::pair(key, latest_avoidance); + } + + { + std::lock_guard critical_section(*critical_progress); + + if (precalculated && precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) + { + precalculation_progress += 0.4 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3); + Progress::messageProgress(Progress::Stage::SUPPORT, precalculation_progress * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); + } + } + + { + std::lock_guard critical_section(*(slow ? critical_avoidance_cache_to_model_slow_ : holefree ? critical_avoidance_cache_holefree_to_model_ : critical_avoidance_cache_to_model_)); + (slow ? avoidance_cache_to_model_slow_ : holefree ? avoidance_cache_hole_to_model_ : avoidance_cache_to_model_).insert(data.begin(), data.end()); + } + }); + + return ret; +} + + +std::future 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. + // 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 + + + /* Potential issue addressed by the wall restrictions: Influence area may lag through a wall + * layer z+1:iiiiiiiiiiioooo + * layer z+0:xxxxxiiiiiiiooo + * layer z-1:ooooixxxxxxxxxx + */ + + // The radius for the upper collission has to be 0 as otherwise one may not enter areas that may be forbidden on layer_idx but not one below (c = not an influence area even though it should ): + /* + * layer z+1:xxxxxiiiiiioo + * layer z+0:dddddiiiiiiio + * layer z-1:dddocdddddddd + */ + // Also there can not just the collision of the lower layer be used because if it were: + /* + * layer z+1:dddddiiiiiiiiiio + * layer z+0:xxxxxddddddddddc + * layer z-1:dddddxxxxxxxxxxc + */ + // Or of the upper layer be used because if it were: + /* + * layer z+1:dddddiiiiiiiiiio + * layer z+0:xxxxcddddddddddc + * layer z-1:ddddcxxxxxxxxxxc + */ + + // And just offseting with maximum movement distance (and not in multiple steps) could cause: + /* + * layer z: oxiiiiiiiiioo + * layer z-1: ixiiiiiiiiiii + */ + + std::future ret = cura::parallel_for_nowait(0, keys.size(), 1, + [&, keys](const size_t key_idx) + { + coord_t radius = keys[key_idx].first; + RadiusLayerPair key(radius, 0); + coord_t min_layer_bottom; + std::unordered_map data; + std::unordered_map data_min; + + { + std::lock_guard critical_section(*critical_wall_restrictions_cache_); + min_layer_bottom = getMaxCalculatedLayer(radius, wall_restrictions_cache_); + } + + 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 = getCollision(0, layer_idx, false).intersection(getCollision(radius, layer_idx_below, true)); // radius contains current_min_xy_dist_delta already if required + wall_restriction.simplify(min_maximum_resolution_, min_maximum_deviation_); + data.emplace(key, wall_restriction); + if (current_min_xy_dist_delta > 0) + { + Polygons wall_restriction_min = getCollision(0, layer_idx, true).intersection(getCollision(radius, layer_idx_below, true)); + wall_restriction_min.simplify(min_maximum_resolution_, min_maximum_deviation_); + data_min.emplace(key, wall_restriction_min); + } + } + + { + std::lock_guard critical_section(*critical_wall_restrictions_cache_); + wall_restrictions_cache_.insert(data.begin(), data.end()); + } + + { + std::lock_guard critical_section(*critical_wall_restrictions_cache_min_); + wall_restrictions_cache_min_.insert(data_min.begin(), data_min.end()); + } + }); + return ret; } -const Polygons& TreeModelVolumes::calculateAvoidance(const RadiusLayerPair& key) const +coord_t TreeModelVolumes::ceilRadius(coord_t radius) const { - const auto& radius = key.first; - const auto& layer_idx = key.second; + if (radius == 0) + { + return 0; + } - if (layer_idx == 0) + if (radius <= radius_0) { - avoidance_cache_[key] = getCollision(radius, 0); - return avoidance_cache_[key]; + return radius_0; } - // Avoidance for a given layer depends on all layers beneath it so could have very deep recursion depths if - // called at high layer heights. We can limit the reqursion depth to N by checking if the if the layer N - // below the current one exists and if not, forcing the calculation of that layer. This may cause another recursion - // if the layer at 2N below the current one but we won't exceed our limit unless there are N*N uncalculated layers - // below our current one. - constexpr auto max_recursion_depth = 100; - // Check if we would exceed the recursion limit by trying to process this layer - if (layer_idx >= max_recursion_depth - && avoidance_cache_.find({radius, layer_idx - max_recursion_depth}) == avoidance_cache_.end()) + if (SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION) { - // Force the calculation of the layer `max_recursion_depth` below our current one, ignoring the result. - getAvoidance(radius, layer_idx - max_recursion_depth); + // 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 - radius_0) / (SUPPORT_TREE_PRE_EXPONENTIAL_STEPS + 1); + coord_t result = radius_0; + for (size_t step = 0; step < SUPPORT_TREE_PRE_EXPONENTIAL_STEPS; step++) + { + result += stepsize; + if (result >= radius && !ignorable_radii_.count(result)) + { + return result; + } + } + + while (exponential_result < radius || 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 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 - radius_0) / SUPPORT_TREE_COLLISION_RESOLUTION; + coord_t resulting_ceil = radius_0 + (ceil_step_n + ((radius - radius_0) % SUPPORT_TREE_COLLISION_RESOLUTION != 0)) * SUPPORT_TREE_COLLISION_RESOLUTION; + + if (radius <= radius_0 && radius != 0) + { + return radius_0; + } + else if (ignorable_radii_.count(resulting_ceil)) + { + return ceilRadius(resulting_ceil + 1); + } + else + { + return resulting_ceil; + } } - auto avoidance_areas = getAvoidance(radius, layer_idx - 1).offset(-max_move_).smooth(5); - avoidance_areas = avoidance_areas.unionPolygons(getCollision(radius, layer_idx)); - const auto ret = avoidance_cache_.insert({key, std::move(avoidance_areas)}); - assert(ret.second); - return ret.first->second; } -const Polygons& TreeModelVolumes::calculateInternalModel(const RadiusLayerPair& key) const +template +const std::optional> TreeModelVolumes::getArea(const std::unordered_map& cache, const KEY key) const { - const auto& radius = key.first; - const auto& layer_idx = key.second; - - const auto& internal_areas = getAvoidance(radius, layer_idx).difference(getCollision(radius, layer_idx)); - const auto ret = internal_model_cache_.insert({key, internal_areas}); - assert(ret.second); - return ret.first->second; + const auto it = cache.find(key); + if (it != cache.end()) + { + return std::optional>{ it->second }; + } + else + { + return std::optional>(); + } } + Polygons TreeModelVolumes::calculateMachineBorderCollision(Polygon machine_border) { Polygons machine_volume_border; - machine_volume_border.add(machine_border.offset(MM2INT(1000))); //Put a border of 1m around the print volume so that we don't collide. - machine_border.reverse(); //Makes the polygon negative so that we subtract the actual volume from the collision area. + machine_volume_border.add(machine_border.offset(1000000)); // Put a border of 1m around the print volume so that we don't collide. + machine_border.reverse(); // Makes the polygon negative so that we subtract the actual volume from the collision area. machine_volume_border.add(machine_border); return machine_volume_border; } diff --git a/src/TreeModelVolumes.h b/src/TreeModelVolumes.h index 74f2b4cc35..4c8b5fbddc 100644 --- a/src/TreeModelVolumes.h +++ b/src/TreeModelVolumes.h @@ -4,62 +4,76 @@ #ifndef TREEMODELVOLUMES_H #define TREEMODELVOLUMES_H +#include +#include #include +#include #include "settings/EnumSettings.h" //To store whether X/Y or Z distance gets priority. #include "settings/types/LayerIndex.h" //Part of the RadiusLayerPair. +#include "sliceDataStorage.h" #include "utils/polygon.h" //For polygon parameters. namespace cura { -class SliceDataStorage; -class LayerIndex; -class Settings; - -/*! - * \brief Lazily generates tree guidance volumes. - * - * \warning This class is not currently thread-safe and should not be accessed in OpenMP blocks - */ class TreeModelVolumes { -public: + public: TreeModelVolumes() = default; + TreeModelVolumes(const SliceDataStorage& storage, 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 = std::vector()); + TreeModelVolumes(TreeModelVolumes&&) = default; + TreeModelVolumes& operator=(TreeModelVolumes&&) = default; + + TreeModelVolumes(const TreeModelVolumes&) = delete; + TreeModelVolumes& operator=(const TreeModelVolumes&) = delete; + + enum class AvoidanceType + { + SLOW, + FAST_SAFE, + FAST + }; + /*! - * \brief Construct the TreeModelVolumes object + * \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! * - * \param storage The slice data storage object to extract the model - * contours from. - * \param settings The settings object to get relevant settings from. - * \param xy_distance The required clearance between the model and the - * tree branches. - * \param max_move The maximum allowable movement between nodes on - * adjacent layers - * \param radius_sample_resolution Sample size used to round requested node radii. */ - TreeModelVolumes(const SliceDataStorage& storage, const Settings& settings); - - TreeModelVolumes(TreeModelVolumes&& original) = default; - TreeModelVolumes& operator=(TreeModelVolumes&& original) = default; - - TreeModelVolumes(const TreeModelVolumes& original) = delete; - TreeModelVolumes& operator=(const TreeModelVolumes& original) = delete; + void precalculate(coord_t max_layer); /*! - * \brief Creates the areas that have to be avoided by the tree's branches. + * \brief Provides 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 radius \p radius to * collide with the model. * * \param radius The radius of the node of interest - * \param layer The layer of interest + * \param layer_idx The layer of interest + * \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); + + /*! + * \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 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& getCollision(coord_t radius, LayerIndex layer_idx) const; + const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist = false); + /*! - * \brief Creates the areas that have to be avoided by the tree's branches + * \brief Provides the areas that have to be avoided by the tree's branches * in order to reach the build plate. * * The result is a 2D area that would cause nodes of radius \p radius to @@ -69,132 +83,346 @@ class TreeModelVolumes * propagated upwards. * * \param radius The radius of the node of interest - * \param layer The layer of interest + * \param layer_idx The layer of interest + * \param slow 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) const; - + const Polygons& getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model = false, bool min_xy_dist = false); + /*! + * \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 + * \param layer_idx The layer of interest + * \return Polygons object + */ + const Polygons& getPlaceableAreas(coord_t radius, LayerIndex layer_idx); + /*! + * \brief Provides the area that represents the walls, as in the printed area, of the model. This is an abstract representation not equal with the outline. See calculateWallRestrictions for better description. + * \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& getWallRestriction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist); /*! - * \brief Generates the area of a given layer that must be avoided if the - * branches wish to go towards the model + * \brief Round \p radius upwards to either a multiple of radius_sample_resolution_ or a exponentially increasing value * - * The area represents the areas that do not collide with the model but - * are unable to reach the build platform + * It also adds the difference between the minimum xy distance and the regular one. * * \param radius The radius of the node of interest - * \param layer The layer of interest - * \return Polygons object + * \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; + /*! + * \brief Round \p radius upwards to the maximum that would still round up to the same value as the provided one. + * + * \param radius The radius of the node of interest + * \param min_xy_dist is the minimum xy distance used. + * \return The maximum radius, resulting in the same rounding. */ - const Polygons& getInternalModel(coord_t radius, LayerIndex layer_idx) const; + coord_t getRadiusNextCeil(coord_t radius, bool min_xy_dist) const; -private: + + private: /*! * \brief Convenience typedef for the keys to the caches */ using RadiusLayerPair = std::pair; + /*! - * \brief Round \p radius upwards to a multiple of radius_sample_resolution_ + * \brief Round \p radius upwards to either a multiple of radius_sample_resolution_ or a exponentially increasing value * * \param radius The radius of the node of interest */ coord_t ceilRadius(coord_t radius) const; /*! - * \brief Calculate the collision areas at the radius and layer indicated - * by \p key. + * \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 SliceMeshStorage& mesh, 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. + * + * 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 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) }); + } + /*! + * \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. * - * \param key The radius and layer of the node of interest + * The result is a 2D area that would cause nodes of given radius to + * collide with the model or be inside a hole. Result is saved in the cache. + * A Hole is defined as an area, in which a branch with 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. */ - const Polygons& calculateCollision(const RadiusLayerPair& key) const; + void calculateCollisionHolefree(std::deque keys); /*! - * \brief Calculate the avoidance areas at the radius and layer indicated - * by \p key. + * \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. * - * \param key The radius and layer of the node of interest + * The result is a 2D area that would cause nodes of given radius to + * collide with the model or be inside a hole. Result is saved in the cache. + * A Hole is defined as an area, in which a branch with increase_until_radius radius would collide with the wall. + * \param key RadiusLayerPairs the requested areas. The radius will be calculated up to the provided layer. */ - const Polygons& calculateAvoidance(const RadiusLayerPair& key) const; + void calculateCollisionHolefree(RadiusLayerPair key) + { + calculateCollisionHolefree(std::deque{ RadiusLayerPair(key) }); + } + + Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) const; /*! - * \brief Calculate the internal model areas at the radius and layer - * indicated by \p key. + * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model. * - * \param key The radius and layer of the node of interest + * The result is a 2D area that would cause nodes of radius \p radius to + * 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. + * \return A future that has to be waited on */ - const Polygons& calculateInternalModel(const RadiusLayerPair& key) const; + [[nodiscard]] std::future calculateAvoidance(std::deque keys); /*! - * \brief Calculate the collision area around the printable area of the machine. + * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model. * - * \param a Polygons object representing the non-printable areas on and around the build platform + * The result is a 2D area that would cause nodes of radius \p radius to + * 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. */ - static Polygons calculateMachineBorderCollision(Polygon machine_border); + void calculateAvoidance(RadiusLayerPair key) + { + calculateAvoidance(std::deque{ RadiusLayerPair(key) }).wait(); + } /*! - * \brief Polygons representing the limits of the printable area of the - * machine + * \brief Creates the areas where a branch of a given radius can be place on the model. + * Result is saved in the cache. + * \param key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer. */ - Polygons machine_border_; + void calculatePlaceables(RadiusLayerPair key) + { + calculatePlaceables(std::deque{ key }).wait(); + } /*! - * \brief The required clearance between the model and the tree branches + * \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. + * + * \return A future that has to be waited on */ - coord_t xy_distance_; + [[nodiscard]] std::future calculatePlaceables(std::deque keys); /*! - * The minimum X/Y distance between the model and the tree branches. + * \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. * - * Used only if the Z distance overrides the X/Y distance and in places that - * are near the surface where the Z distance applies. + * 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. + * + * \return A future that has to be waited on */ - coord_t xy_distance_overhang; + [[nodiscard]] std::future calculateAvoidanceToModel(std::deque keys); /*! - * The number of layers of spacing to hold as Z distance. + * \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) }).wait(); + } + /*! + * \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). + * + * These areas are at least xy_min_dist wide. When calculating it is always assumed that every wall is printed on top of another (as in has an overlap with the wall a layer below). Result is saved in the corresponding cache. * - * This determines where the overhang X/Y distance is used, if the Z - * distance overrides the X/Y distance. + * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. + * + * \return A future that has to be waited on */ - int z_distance_layers; + [[nodiscard]] std::future calculateWallRestrictions(std::deque keys); /*! - * The priority of X/Y distance over Z distance. + * \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). + * These areas are at least xy_min_dist wide. When calculating it is always assumed that every wall is printed on top of another (as in has an overlap with the wall a layer below). Result is saved in the corresponding cache. + * \param key RadiusLayerPair of the requested area. It well be will be calculated up to the provided layer. */ - SupportDistPriority distance_priority; + void calculateWallRestrictions(RadiusLayerPair key) + { + calculateWallRestrictions(std::deque{ RadiusLayerPair(key) }).wait(); + } /*! - * \brief The maximum distance that the centrepoint of a tree branch may - * move in consequtive layers + * \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) + */ + template + const std::optional> getArea(const std::unordered_map& cache, const KEY key) const; + bool checkSettingsEquality(const Settings& me, const Settings& other) const; + /*! + * \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::unordered_map& map) const; + + Polygons calculateMachineBorderCollision(Polygon machine_border); + /*! + * \brief The maximum distance that the center point of a tree branch may move in consecutive layers if it has to avoid the model. */ coord_t max_move_; + /*! + * \brief The maximum distance that the centre-point of a tree branch may + * move in consecutive layers if it does not have to avoid the model + */ + coord_t max_move_slow_; + /*! + * \brief The smallest maximum resolution for simplify + */ + coord_t min_maximum_resolution_; + /*! + * \brief The smallest maximum deviation for simplify + */ + coord_t min_maximum_deviation_; + /*! + * \brief Whether the precalculate was called, meaning every required value should be cached. + */ + bool precalculated = false; + /*! + * \brief The index to access the outline corresponding with the currently processing mesh + */ + size_t current_outline_idx; + /*! + * \brief The minimum required clearance between the model and the tree branches + */ + coord_t current_min_xy_dist; + /*! + * \brief The difference between the minimum required clearance between the model and the tree branches and the regular one. + */ + coord_t current_min_xy_dist_delta; + /*! + * \brief Does at least one mesh allow support to rest on a model. + */ + bool support_rests_on_model; + /*! + * \brief The progress of the precalculate function for communicating it to the progress bar. + */ + coord_t precalculation_progress = 0; + /*! + * \brief The progress multiplier of all values added progress bar. + * Required for the progress bar the behave as expected when areas have to be calculated multiple times + */ + double progress_multiplier; + /*! + * \brief The progress offset added to all values communicated to the progress bar. + * Required for the progress bar the behave as expected when areas have to be calculated multiple times + */ + double progress_offset; + /*! + * \brief Increase radius in the resulting drawn branches, even if the avoidance does not allow it. Will be cut later to still fit. + */ + coord_t increase_until_radius; /*! - * \brief Sample resolution for radius values. - * - * The radius will be rounded (upwards) to multiples of this value before - * calculations are done when collision, avoidance and internal model - * Polygons are requested. + * \brief Polygons representing the limits of the printable area of the + * machine + */ + Polygons machine_border_; + /*! + * \brief Storage for layer outlines and the corresponding settings of the meshes grouped by meshes with identical setting. + */ + std::vector>> layer_outlines_; + /*! + * \brief Storage for areas that should be avoided, like support blocker or previous generated trees. + */ + std::vector anti_overhang_; + /*! + * \brief Radii that can be ignored by ceilRadius as they will never be requested. */ - coord_t radius_sample_resolution_; + std::unordered_set ignorable_radii_; /*! - * \brief Storage for layer outlines of the meshes. + * \brief Smallest radius a branch can have. This is the radius of a SupportElement with DTT=0. */ - std::vector layer_outlines_; + coord_t radius_0; + /*! - * \brief Caches for the collision, avoidance and internal model polygons + * \brief Caches for the collision, avoidance and areas on the model where support can be placed safely * at given radius and layer indices. * * These are mutable to allow modification from const function. This is * generally considered OK as the functions are still logically const - * (ie there is no difference in behaviour for the user betweeen + * (ie there is no difference in behaviour for the user between * calculating the values each time vs caching the results). */ mutable std::unordered_map collision_cache_; + std::unique_ptr critical_collision_cache_ = std::make_unique(); + + mutable std::unordered_map collision_cache_holefree_; + std::unique_ptr critical_collision_cache_holefree_ = std::make_unique(); + mutable std::unordered_map avoidance_cache_; - mutable std::unordered_map internal_model_cache_; + std::unique_ptr critical_avoidance_cache_ = std::make_unique(); + + mutable std::unordered_map avoidance_cache_slow_; + std::unique_ptr critical_avoidance_cache_slow_ = std::make_unique(); + + mutable std::unordered_map avoidance_cache_to_model_; + std::unique_ptr critical_avoidance_cache_to_model_ = std::make_unique(); + + mutable std::unordered_map avoidance_cache_to_model_slow_; + std::unique_ptr critical_avoidance_cache_to_model_slow_ = std::make_unique(); + + mutable std::unordered_map placeable_areas_cache_; + std::unique_ptr critical_placeable_areas_cache_ = std::make_unique(); + + + /*! + * \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. + */ + mutable std::unordered_map avoidance_cache_hole_; + std::unique_ptr critical_avoidance_cache_holefree_ = std::make_unique(); + + mutable std::unordered_map avoidance_cache_hole_to_model_; + std::unique_ptr critical_avoidance_cache_holefree_to_model_ = std::make_unique(); + + /*! + * \brief Caches to represent walls not allowed to be passed over. + */ + mutable std::unordered_map wall_restrictions_cache_; + std::unique_ptr critical_wall_restrictions_cache_ = std::make_unique(); + + mutable std::unordered_map wall_restrictions_cache_min_; // 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. + std::unique_ptr critical_wall_restrictions_cache_min_ = std::make_unique(); + + std::unique_ptr critical_progress = std::make_unique(); }; } diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index f8d9af1817..839b8e5dc0 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -13,52 +13,31 @@ #include "utils/polygonUtils.h" //For moveInside. #include #include -#include #include #include #include +#include +#include -#define SUPPORT_TREE_CIRCLE_RESOLUTION 25 // The number of vertices in each circle. - -// The various stages of the process can be weighted differently in the progress bar. -// These weights are obtained experimentally using a small sample size. Sensible weights can differ drastically based on the assumed default settings and model. -#define PROGRESS_TOTAL 10000 -#define PROGRESS_PRECALC_COLL PROGRESS_TOTAL * 0.1 -#define PROGRESS_PRECALC_AVO PROGRESS_TOTAL * 0.4 -#define PROGRESS_GENERATE_NODES PROGRESS_TOTAL * 0.1 -#define PROGRESS_AREA_CALC PROGRESS_TOTAL * 0.3 -#define PROGRESS_DRAW_AREAS PROGRESS_TOTAL * 0.1 - -#define PROGRESS_GENERATE_BRANCH_AREAS PROGRESS_DRAW_AREAS / 3 -#define PROGRESS_SMOOTH_BRANCH_AREAS PROGRESS_DRAW_AREAS / 3 -#define PROGRESS_FINALIZE_BRANCH_AREAS PROGRESS_DRAW_AREAS / 3 - - -#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 - -#define SUPPORT_TREE_MAX_DEVIATION 0 namespace cura { -bool TreeSupport::TreeSupportSettings::some_model_contains_thick_roof = false; // Explicit zero-initialization, because while a static variable should be always zero-initialized, the linker complains about "undefined reference" otherwise... TreeSupport::TreeSupport(const SliceDataStorage& storage) { size_t largest_printed_mesh_idx = 0; + for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { SliceMeshStorage mesh = storage.meshes[mesh_idx]; - if (mesh.settings.get("support_roof_height") >= 2 * mesh.settings.get("layer_height")) { TreeSupportSettings::some_model_contains_thick_roof = true; } + if (mesh.settings.get("support_top_distance") == 0 || mesh.settings.get("support_bottom_distance") == 0 || mesh.settings.get("min_feature_size") < 100) + { + TreeSupportSettings::has_to_rely_on_min_xy_dist_only = true; + } } // Group all meshes that can be processed together. NOTE this is different from mesh-groups! Only one setting object is needed per group, as different settings in the same group may only occur in the tip, which uses the original settings objects from the meshes. @@ -150,9 +129,9 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) std::vector exclude(storage.support.supportLayers.size()); auto t_start = std::chrono::high_resolution_clock::now(); // get all already existing support areas and exclude them -#pragma omp parallel for - for (LayerIndex layer_idx = 0; layer_idx < coord_t(storage.support.supportLayers.size()); layer_idx++) - { + cura::parallel_for(LayerIndex(0), LayerIndex(storage.support.supportLayers.size()), LayerIndex(1), + [&](const LayerIndex layer_idx) + { Polygons exlude_at_layer; exlude_at_layer.add(storage.support.supportLayers[layer_idx].support_bottom); exlude_at_layer.add(storage.support.supportLayers[layer_idx].support_roof); @@ -161,11 +140,11 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) exlude_at_layer.add(part.outline); } exclude[layer_idx] = exlude_at_layer.unionPolygons(); - } - config = processing.first; // this struct is used to easy retrieve setting. No other function except those in ModelVolumes and generateInitalAreas have knowledge of the existence of multiple meshes being processed. + }); + config = processing.first; // this struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generateInitialAreas have knowledge of the existence of multiple meshes being processed. progress_multiplier = 1.0 / double(grouped_meshes.size()); - progress_offset = counter == 0 ? 0 : PROGRESS_TOTAL * (double(counter) * progress_multiplier); - volumes_ = ModelVolumes(storage, config.maximum_move_distance, config.maximum_move_distance_slow, processing.second.front(), progress_multiplier, progress_offset, exclude); + progress_offset = counter == 0 ? 0 : TREE_PROGRESS_TOTAL * (double(counter) * progress_multiplier); + volumes_ = TreeModelVolumes(storage, config.maximum_move_distance, config.maximum_move_distance_slow, processing.second.front(), progress_multiplier, progress_offset, exclude); // ### Precalculate avoidances, collision etc. precalculate(storage, processing.second); @@ -174,7 +153,7 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) // ### Place tips of the support tree for (size_t mesh_idx : processing.second) { - generateInitalAreas(storage.meshes[mesh_idx], move_bounds, storage); + generateInitialAreas(storage.meshes[mesh_idx], move_bounds, storage); } auto t_gen = std::chrono::high_resolution_clock::now(); @@ -224,7 +203,7 @@ void TreeSupport::precalculate(const SliceDataStorage& storage, std::vector("layer_height"); const coord_t z_distance_top = mesh.settings.get("support_top_distance"); const size_t z_distance_top_layers = round_up_divide(z_distance_top, - layer_height) + 1; // Support must always be 1 layer below overhang. + layer_height) + 1; // Support must always be 1 layer below overhang. if (mesh.overhang_areas.size() <= z_distance_top_layers) { continue; @@ -244,7 +223,7 @@ void TreeSupport::precalculate(const SliceDataStorage& storage, std::vector angles = roof ? config.support_roof_angles : config.support_infill_angles; + std::vector toolpaths; const coord_t z = config.getActualZ(layer_idx); int divisor = static_cast(angles.size()); int index = ((layer_idx % divisor) + divisor) % divisor; const AngleDegrees fill_angle = angles[index]; - Infill roof_computation(pattern, zig_zaggify_infill, connect_polygons, area, outline_offset, roof ? config.support_roof_line_width : config.support_line_width, support_infill_distance, support_roof_overlap, infill_multiplier, fill_angle, z, support_shift, config.maximum_resolution, config.maximum_deviation, wall_line_count, infill_origin, perimeter_gaps, connected_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size); + Infill roof_computation(pattern, zig_zaggify_infill, connect_polygons, area, roof ? config.support_roof_line_width : config.support_line_width, support_infill_distance, support_roof_overlap, infill_multiplier, fill_angle, z, support_shift, config.maximum_resolution, config.maximum_deviation, wall_line_count, infill_origin, perimeter_gaps, connected_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size); Polygons areas; Polygons lines; - roof_computation.generate(areas, lines, cross_fill_provider); + roof_computation.generate(toolpaths, areas, lines, config.settings, cross_fill_provider); lines.add(toPolylines(areas)); return lines; } @@ -665,14 +644,14 @@ SierpinskiFillProvider* TreeSupport::generateCrossFillProvider(const SliceMeshSt } -void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector>& move_bounds, SliceDataStorage& storage) +void TreeSupport::generateInitialAreas(const SliceMeshStorage& mesh, std::vector>& move_bounds, SliceDataStorage& storage) { Polygon base_circle; const int base_radius = 10; for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; i++) { const AngleRadians angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * TAU; - base_circle.emplace_back(cos(angle) * base_radius, sin(angle) * base_radius); + base_circle.emplace_back(coord_t(cos(angle) * base_radius), coord_t(sin(angle) * base_radius)); } TreeSupportSettings mesh_config(mesh.settings); @@ -688,7 +667,7 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< const EFillMethod support_pattern = mesh.settings.get("support_pattern"); const coord_t connect_length = (mesh_config.support_line_width * 100 / mesh.settings.get("support_tree_top_rate")) + std::max(2 * mesh_config.min_radius - 1.0 * mesh_config.support_line_width, 0.0); const coord_t support_tree_branch_distance = (support_pattern == EFillMethod::TRIANGLES ? 3 : (support_pattern == EFillMethod::GRID ? 2 : 1)) * connect_length; - const coord_t circle_length_to_half_linewidth_change = mesh_config.min_radius < mesh_config.support_line_width ? mesh_config.min_radius / 2 : sqrt(square(mesh_config.min_radius) - square(mesh_config.min_radius - mesh_config.support_line_width / 2)); // As r*r=x*x+y*y (circle equation): If a circle with center at (0,0) the top most point is at (0,r) as in y=r. This calculates how far one has to move on the x-axis so that y=r-support_line_width/2. In other words how far does one need to move on the x-axis to be support_line_width/2 away from the circle line. As a circle is round this length is identical for every axis as long as the 90° angle between both remains. + const coord_t circle_length_to_half_linewidth_change = mesh_config.min_radius < mesh_config.support_line_width ? mesh_config.min_radius / 2 : sqrt(square(mesh_config.min_radius) - square(mesh_config.min_radius - mesh_config.support_line_width / 2)); // As r*r=x*x+y*y (circle equation): If a circle with center at (0,0) the top most point is at (0,r) as in y=r. This calculates how far one has to move on the x-axis so that y=r-support_line_width/2. In other words how far does one need to move on the x-axis to be support_line_width/2 away from the circle line. As a circle is round this length is identical for every axis as long as the 90� angle between both remains. const coord_t support_outset = mesh.settings.get("support_offset"); const coord_t roof_outset = mesh.settings.get("support_roof_offset"); const coord_t extra_outset = std::max(coord_t(0), mesh_config.min_radius - mesh_config.support_line_width) + (xy_overrides ? 0 : mesh_config.support_line_width / 2); // extra support offset to compensate for larger tip radiis. Also outset a bit more when z overwrites xy, because supporting something with a part of a support line is better than not supporting it at all. @@ -703,24 +682,25 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< } std::vector> already_inserted(mesh.overhang_areas.size() - z_distance_delta); - // counting down as it is more likely to encounter areas that need much support at the top then at the bottom. This could improve performance because of the schedule(dynamic) -#pragma omp parallel for schedule(dynamic) - for (LayerIndex layer_idx = mesh.overhang_areas.size() - z_distance_delta - 1; layer_idx >= 1; layer_idx--) - { + + std::mutex critical_sections; + cura::parallel_for(1, mesh.overhang_areas.size() - z_distance_delta, 1, + [&](const LayerIndex layer_idx) + { if (mesh.overhang_areas[layer_idx + z_distance_delta].empty()) { - continue; + return; // This is a continue if imagined in a loop context } Polygons relevant_forbidden = (mesh_config.support_rests_on_model ? (only_gracious ? volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides) : volumes_.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides)) : volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides)); // take the least restrictive avoidance possible relevant_forbidden = relevant_forbidden.offset(5); // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. - std::function generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) + std::function generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) { const coord_t support_infill_distance = roof ? support_roof_line_distance : support_tree_branch_distance; return generateSupportInfillLines(area, roof, layer_idx, support_infill_distance, cross_fill_provider); }; - std::function, size_t, coord_t, size_t, bool)> addPointAsInfluenceArea = [&](std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof) + std::function, size_t, LayerIndex, size_t, bool, bool)> addPointAsInfluenceArea = [&](std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) { bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; @@ -736,13 +716,13 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< circle.add(p.first + corner); } Polygons area = circle.offset(0); -#pragma omp critical(moveBounds) { + std::lock_guard critical_section_movebounds(critical_sections); if (!already_inserted[insert_layer].count(p.first / ((mesh_config.min_radius + 1) / 10))) { // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing already_inserted[insert_layer].emplace(p.first / ((mesh_config.min_radius + 1) / 10)); - SupportElement* elem = new SupportElement(dtt, insert_layer, p.first, to_bp, gracious, !xy_overrides, dont_move_until, roof, safe_radius, force_tip_to_roof); + SupportElement* elem = new SupportElement(dtt, insert_layer, p.first, to_bp, gracious, !xy_overrides, dont_move_until, roof, safe_radius, force_tip_to_roof, skip_ovalisation); elem->area = new Polygons(area); move_bounds[insert_layer].emplace(elem); } @@ -750,11 +730,11 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< }; - std::function, size_t, LayerIndex)> addLinesAsInfluenceAreas = [&](std::vector lines, size_t roof_layers, LayerIndex layer_idx) + std::function, size_t, LayerIndex, bool, size_t)> addLinesAsInfluenceAreas = [&](std::vector lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, bool supports_roof, size_t dont_move_until) { // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible - size_t dtt_roof; - for (dtt_roof = 0; dtt_roof < roof_layers && layer_idx - dtt_roof >= 1; dtt_roof++) + size_t dtt_roof_tip; + for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; dtt_roof_tip++) { std::function)> evaluateRoofWillGenerate = [&](std::pair p) { @@ -764,16 +744,16 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< roof_circle.add(p.first + corner * mesh_config.min_radius); } Polygons area = roof_circle.offset(0); - return !generateLines(area, true, layer_idx - dtt_roof).empty(); + return !generateLines(area, true, insert_layer_idx - dtt_roof_tip).empty(); }; - std::pair, std::vector> split = splitLines(lines, getEvaluatePointForNextLayerFunction(layer_idx - dtt_roof)); // keep all lines that are still valid on the next layer + std::pair, std::vector> split = splitLines(lines, getEvaluatePointForNextLayerFunction(insert_layer_idx - dtt_roof_tip)); // keep all lines that are still valid on the next layer for (LineInformation line : split.second) // add all points that would not be valid { for (std::pair point_data : line) { - addPointAsInfluenceArea(point_data, 0, layer_idx - dtt_roof, roof_layers - dtt_roof, dtt_roof != 0); + addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); } } @@ -785,7 +765,7 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< { for (std::pair point_data : line) { - addPointAsInfluenceArea(point_data, 0, layer_idx - dtt_roof, roof_layers - dtt_roof, dtt_roof != 0); + addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); } } @@ -804,23 +784,25 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< } } added_roofs = added_roofs.unionPolygons(); -#pragma omp critical(storage) { - storage.support.supportLayers[layer_idx - dtt_roof].support_roof.add(added_roofs); + std::lock_guard critical_section_storage(critical_sections); + + storage.support.supportLayers[insert_layer_idx - dtt_roof_tip].support_roof.add(added_roofs); } } for (LineInformation line : lines) { + bool disable_ovalistation = mesh_config.min_radius < 3 * mesh_config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > 5; // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width for (auto point_data : line) { - addPointAsInfluenceArea(point_data, 0, layer_idx - dtt_roof, roof_layers - dtt_roof, dtt_roof != 0); + addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, dtt_roof_tip != 0 || supports_roof, disable_ovalistation); } } }; std::vector> overhang_processing; // every overhang has saved if a roof should be generated for it. This can NOT be done in the for loop as an area may NOT have a roof even if it is larger than the minimum_roof_area when it is only larger because of the support horizontal expansion and it would not have a roof if the overhang is offset by support roof horizontal expansion instead. (At least this is the current behavior of the regular support) - Polygons overhang_regular = safeOffsetInc(mesh.overhang_areas[layer_idx + z_distance_delta], support_outset, relevant_forbidden, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, 1); + Polygons overhang_regular = safeOffsetInc(mesh.overhang_areas[layer_idx + z_distance_delta], support_outset, relevant_forbidden, mesh_config.min_radius * 1.75 + mesh_config.xy_min_distance, 0, 1); Polygons remaining_overhang = mesh.overhang_areas[layer_idx + z_distance_delta].offset(support_outset).difference(overhang_regular.offset(mesh_config.support_line_width * 0.5)).intersection(relevant_forbidden); // offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang coord_t extra_total_offset_acc = 0; @@ -829,9 +811,9 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< { coord_t offset_current_step = extra_total_offset_acc + 2 * mesh_config.support_line_width > mesh_config.min_radius ? std::min(mesh_config.support_line_width / 8, extra_outset - extra_total_offset_acc) : std::min(circle_length_to_half_linewidth_change, extra_outset - extra_total_offset_acc); extra_total_offset_acc += offset_current_step; - Polygons overhang_offset = safeOffsetInc(overhang_regular, 1.5 * extra_total_offset_acc, volumes_.getCollision(0, layer_idx, true), mesh_config.xy_min_distance + mesh_config.support_line_width, mesh_config.xy_min_distance + mesh_config.support_line_width, 1); + Polygons overhang_offset = safeOffsetInc(overhang_regular, 1.5 * extra_total_offset_acc, volumes_.getCollision(0, layer_idx, true), mesh_config.xy_min_distance + mesh_config.support_line_width, 0, 1); remaining_overhang = remaining_overhang.difference(overhang_offset).unionPolygons(); - Polygons next_overhang = safeOffsetInc(remaining_overhang, extra_total_offset_acc, volumes_.getCollision(0, layer_idx, true), mesh_config.xy_min_distance + mesh_config.support_line_width, mesh_config.xy_min_distance + mesh_config.support_line_width, 1); + Polygons next_overhang = safeOffsetInc(remaining_overhang, extra_total_offset_acc, volumes_.getCollision(0, layer_idx, true), mesh_config.xy_min_distance + mesh_config.support_line_width, 0, 1); overhang_regular = overhang_regular.unionPolygons(next_overhang.difference(relevant_forbidden)); } @@ -868,14 +850,14 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< overhang_lines = split.first; std::vector fresh_valid_points = convertLinesToInternal(convertInternalToLines(split.second), layer_idx - lag_ctr); // set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. - addLinesAsInfluenceAreas(fresh_valid_points, (force_tip_to_roof && lag_ctr <= support_roof_layers) ? support_roof_layers : 0, layer_idx - lag_ctr); + addLinesAsInfluenceAreas(fresh_valid_points, (force_tip_to_roof && lag_ctr <= support_roof_layers) ? support_roof_layers : 0, layer_idx - lag_ctr, false, roof_enabled ? support_roof_layers : 0); } } Polygons overhang_roofs; if (roof_enabled) { - overhang_roofs = safeOffsetInc(mesh.overhang_areas[layer_idx + z_distance_delta], roof_outset, relevant_forbidden, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, 1); + overhang_roofs = safeOffsetInc(mesh.overhang_areas[layer_idx + z_distance_delta], roof_outset, relevant_forbidden, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, 0, 1); overhang_roofs.removeSmallAreas(minimum_roof_area); overhang_regular = overhang_regular.difference(overhang_roofs); for (Polygons roof_part : overhang_roofs.splitIntoParts(true)) @@ -944,8 +926,9 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< } } } -#pragma omp critical(storage) + { + std::lock_guard critical_section_storage(critical_sections); for (size_t idx = 0; idx < dtt_roof; idx++) { storage.support.supportLayers[layer_idx - idx].support_roof.add(added_roofs[idx]); // will be unioned in finalizeInterfaceAndSupportAreas @@ -955,10 +938,12 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< if (overhang_lines.empty()) { Polygons polylines = ensureMaximumDistancePolyline(generateLines(overhang_outset, dtt_roof != 0, layer_idx - layer_generation_dtt), dtt_roof == 0 ? mesh_config.min_radius / 2 : connect_length, 1); // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, but not only is this the only reasonable choice, but it ensures consistant behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. This is not doen when a roof is above as the roof will support the model and the trees only need to support the roof + + if (polylines.pointCount() <= min_support_points) { // add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the the support line width. - Polygons reduced_overhang_outset = overhang_outset.offset(-mesh_config.support_line_width / 2.2); // I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60° so there is a fallback, as some support is better than none. + Polygons reduced_overhang_outset = overhang_outset.offset(-mesh_config.support_line_width / 2.2); // I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, as some support is better than none. if (!reduced_overhang_outset.empty() && overhang_outset.difference(reduced_overhang_outset.offset(std::max(mesh_config.support_line_width, connect_length))).area() < 1) { polylines = ensureMaximumDistancePolyline(toPolylines(reduced_overhang_outset), connect_length, min_support_points); @@ -974,43 +959,63 @@ void TreeSupport::generateInitalAreas(const SliceMeshStorage& mesh, std::vector< if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part) // reached buildplate { -#pragma omp critical(storage) { + std::lock_guard critical_section_storage(critical_sections); storage.support.supportLayers[0].support_roof.add(overhang_outset); } } else // normal trees have to be generated { - addLinesAsInfluenceAreas(overhang_lines, force_tip_to_roof ? support_roof_layers - dtt_roof : 0, layer_idx - dtt_roof); + addLinesAsInfluenceAreas(overhang_lines, force_tip_to_roof ? support_roof_layers - dtt_roof : 0, layer_idx - dtt_roof, dtt_roof > 0, roof_enabled ? support_roof_layers - dtt_roof : 0); } } - } + }); delete cross_fill_provider; } -Polygons TreeSupport::safeOffsetInc(const Polygons& me, coord_t distance, const Polygons& collision, coord_t safe_step_size, coord_t last_safe_step_size, size_t min_amount_offset) const +Polygons TreeSupport::safeOffsetInc(const Polygons& me, coord_t distance, const Polygons& collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset) const { + bool do_final_difference = last_step_offset_without_check == 0; Polygons ret = safeUnion(me); // ensure sane input if (distance == 0) { - return ret.difference(collision).unionPolygons(); + return (do_final_difference ? ret.difference(collision) : ret).unionPolygons(); } - if (safe_step_size < 0 || last_safe_step_size < 0) + if (safe_step_size < 0 || last_step_offset_without_check < 0) { - logError("Offset increase got negative distance!\n"); - return ret.difference(collision).unionPolygons(); + logError("Offset increase got invalid parameter!\n"); + return (do_final_difference ? ret.difference(collision) : ret).unionPolygons(); } coord_t step_size = safe_step_size; - size_t steps = distance > last_safe_step_size ? (distance - last_safe_step_size) / step_size : 0; - if (steps + (distance < last_safe_step_size || distance % step_size != 0) < min_amount_offset && min_amount_offset > 1) // yes one can add a bool as the standard specifies that a result from compare operators has to be 0 or 1 + size_t steps = distance > last_step_offset_without_check ? (distance - last_step_offset_without_check) / step_size : 0; + if (distance - steps * step_size > last_step_offset_without_check) { - // we need to reduce the stepsize to ensure we offset the required amount (could be avoided if arcRadiance were exposed as we could just increase this when me has not enough vertices) - step_size = distance / (min_amount_offset - 1); - steps = distance / step_size; + if ((steps + 1) * step_size <= distance) + { + steps++; // This will be the case when last_step_offset_without_check >= safe_step_size + } + else + { + do_final_difference = true; + } + } + if (steps + (distance < last_step_offset_without_check || distance % step_size != 0) < min_amount_offset && min_amount_offset > 1) // yes one can add a bool as the standard specifies that a result from compare operators has to be 0 or 1 + { + // reduce the stepsize to ensure it is offset the required amount of times + step_size = distance / min_amount_offset; + if (step_size >= safe_step_size) + { + // effectivly reduce last_step_offset_without_check + step_size = safe_step_size; + steps = min_amount_offset; + } + else + { + steps = distance / step_size; + } } - // offset in steps for (size_t i = 0; i < steps; i++) { @@ -1021,10 +1026,15 @@ Polygons TreeSupport::safeOffsetInc(const Polygons& me, coord_t distance, const ret.simplify(15); } } - ret = ret.offset(distance - steps * step_size, ClipperLib::jtRound); // offset the remainder - return ret.difference(collision).unionPolygons(); // ensure sane output + ret.simplify(15); + + if (do_final_difference) + { + ret = ret.difference(collision); + } + return ret.unionPolygons(); } @@ -1117,7 +1127,7 @@ void TreeSupport::mergeHelper(std::map& reduced_aabb, std: // If this area has any intersections with the influence area of the larger collision radius, a branch (of the larger collision radius) placed in this intersection, has already engulfed the branch of the smaller collision radius. // Because of this a merge may happen even if the influence areas (that represent possible center points of branches) do not intersect yet. // Remember that collision radius <= real radius as otherwise this assumption would be false. - Polygons small_rad_increased_by_big_minus_small = safeOffsetInc(smaller_rad.second, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 2 * (config.xy_distance + smaller_collision_radius - 3), 0); // -3 avoids possible rounding errors + Polygons small_rad_increased_by_big_minus_small = safeOffsetInc(smaller_rad.second, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); // -3 avoids possible rounding errors Polygons intersect = small_rad_increased_by_big_minus_small.intersection(bigger_rad.second); if (intersect.area() > 1) // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) @@ -1148,7 +1158,7 @@ void TreeSupport::mergeHelper(std::map& reduced_aabb, std: Polygons intersect_influence; Polygons infl_small = insert_influence.count(smaller_rad.first) ? insert_influence[smaller_rad.first] : (influence_areas.count(smaller_rad.first) ? influence_areas.at(smaller_rad.first) : Polygons()); Polygons infl_big = insert_influence.count(bigger_rad.first) ? insert_influence[bigger_rad.first] : (influence_areas.count(bigger_rad.first) ? influence_areas.at(bigger_rad.first) : Polygons()); - Polygons small_rad_increased_by_big_minus_small_infl = safeOffsetInc(infl_small, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 2 * (config.xy_distance + smaller_collision_radius - 3), 0); + Polygons small_rad_increased_by_big_minus_small_infl = safeOffsetInc(infl_small, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); intersect_influence = small_rad_increased_by_big_minus_small_infl.intersection(infl_big); // if the one with the bigger radius with the lower radius removed overlaps we can merge intersect_influence = safeUnion(intersect_influence, intersect); // Rounding errors again. Do not ask me where or why. @@ -1159,7 +1169,7 @@ void TreeSupport::mergeHelper(std::map& reduced_aabb, std: { Polygons sec_small = insert_model_areas.count(smaller_rad.first) ? insert_model_areas[smaller_rad.first] : (to_model_areas.count(smaller_rad.first) ? to_model_areas.at(smaller_rad.first) : Polygons()); Polygons sec_big = insert_model_areas.count(bigger_rad.first) ? insert_model_areas[bigger_rad.first] : (to_model_areas.count(bigger_rad.first) ? to_model_areas.at(bigger_rad.first) : Polygons()); - Polygons small_rad_increased_by_big_minus_small_sec = safeOffsetInc(sec_small, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 2 * (config.xy_distance + smaller_collision_radius - 3), 0); + Polygons small_rad_increased_by_big_minus_small_sec = safeOffsetInc(sec_small, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); intersect_sec = small_rad_increased_by_big_minus_small_sec.intersection(sec_big); // if the one with the bigger radius with the lower radius removed overlaps we can merge intersect_influence = safeUnion(intersect_influence, intersect_sec); // still rounding errors } @@ -1216,10 +1226,8 @@ void TreeSupport::mergeInfluenceAreas(std::unordered_map(1, buckets_area.size(), 2, + [&](const size_t idx) // +=2 as in the beginning only uneven buckets will be filled + { for (const std::pair& input_pair : buckets_area[idx]) { AABB outer_support_wall_aabb = AABB(input_pair.second); outer_support_wall_aabb.expand(config.getRadius(input_pair.first)); buckets_aabb[idx].emplace(input_pair.first, outer_support_wall_aabb); } - } + }); while (buckets_area.size() > 1) { @@ -1276,14 +1285,15 @@ void TreeSupport::mergeInfluenceAreas(std::unordered_map> insert_influence(buckets_area.size() / 2); std::vector> erase(buckets_area.size() / 2); -#pragma omp parallel for schedule(dynamic) - for (coord_t i = 0; i < (coord_t)buckets_area.size() - 1; i = i + 2) - { + + cura::parallel_for(0, (coord_t)buckets_area.size() - 1, 2, + [&](const size_t bucket_pair_idx) + { // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets - mergeHelper(buckets_aabb[i], buckets_aabb[i + 1], to_bp_areas, to_model_areas, influence_areas, insert_main[i / 2], insert_secondary[i / 2], insert_influence[i / 2], erase[i / 2], layer_idx); - buckets_area[i + 1].clear(); // clear now irrelevant max_bucket_count, and delete them later - buckets_aabb[i + 1].clear(); - } + mergeHelper(buckets_aabb[bucket_pair_idx], buckets_aabb[bucket_pair_idx + 1], to_bp_areas, to_model_areas, influence_areas, insert_main[bucket_pair_idx / 2], insert_secondary[bucket_pair_idx / 2], insert_influence[bucket_pair_idx / 2], erase[bucket_pair_idx / 2], layer_idx); + buckets_area[bucket_pair_idx + 1].clear(); // clear now irrelevant max_bucket_count, and delete them later + buckets_aabb[bucket_pair_idx + 1].clear(); + }); for (coord_t i = 0; i < (coord_t)buckets_area.size() - 1; i = i + 2) { @@ -1309,8 +1319,8 @@ void TreeSupport::mergeInfluenceAreas(std::unordered_map x) mutable { return x.empty(); }); - buckets_area.erase(position, buckets_area.end()); + auto position_rem = std::remove_if(buckets_area.begin(), buckets_area.end(), [&](const std::map x) mutable { return x.empty(); }); + buckets_area.erase(position_rem, buckets_area.end()); auto position_aabb = std::remove_if(buckets_aabb.begin(), buckets_aabb.end(), [&](const std::map x) mutable { return x.empty(); }); buckets_aabb.erase(position_aabb, buckets_aabb.end()); @@ -1333,7 +1343,9 @@ std::optional TreeSupport::increaseSingleArea(AreaI increased = relevant_offset; if (overspeed > 0) { - increased = safeOffsetInc(increased, overspeed, volumes_.getWallRestiction(config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist), current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance, radius + (current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance), 1); // offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. May not be a problem anymore though. + const coord_t safe_movement_distance = (current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); + // The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later. The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later. + increased = safeOffsetInc(increased, overspeed, volumes_.getWallRestriction(config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist), safe_movement_distance, safe_movement_distance + radius, 1); } if (settings.no_error && settings.move) { @@ -1472,14 +1484,15 @@ std::optional TreeSupport::increaseSingleArea(AreaI void TreeSupport::increaseAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, std::vector& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, const bool mergelayer) { -#pragma omp parallel for schedule(dynamic) - for (long long unsigned int i = 0; i < last_layer.size(); i++) - { - SupportElement* parent = last_layer[i]; + std::mutex critical_sections; + cura::parallel_for(0, last_layer.size(), 1, + [&](const size_t idx) + { + SupportElement* parent = last_layer[idx]; SupportElement elem(parent); // also increases dtt - Polygons wall_restriction = volumes_.getWallRestiction(config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist); // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. + Polygons wall_restriction = volumes_.getWallRestriction(config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist); // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. Polygons to_bp_data, to_model_data; coord_t radius = config.getCollisionRadius(elem); @@ -1494,6 +1507,15 @@ void TreeSupport::increaseAreas(std::unordered_map& to const coord_t ceiled_parent_radius = volumes_.ceilRadius(config.getCollisionRadius(*parent), parent->use_min_xy_dist); coord_t projected_radius_increased = config.getRadius(parent->effective_radius_height + 1, parent->elephant_foot_increases); coord_t projected_radius_delta = projected_radius_increased - config.getCollisionRadius(*parent); + + // When z distance is more than one layer up and down the Collision used to calculate the wall restriction will always include the wall (and not just the xy_min_distance) of the layer above and below like this (d = blocked area because of z distance): + /* + * layer z+1:dddddiiiiiioooo + * layer z+0:xxxxxdddddddddd + * layer z-1:dddddxxxxxxxxxx + * For more detailed visualisation see calculateWallRestrictions + */ + const coord_t safe_movement_distance = (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); if (ceiled_parent_radius == volumes_.ceilRadius(projected_radius_increased, parent->use_min_xy_dist) || projected_radius_increased < config.increase_radius_until_radius) { // If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of the slower moving wall @@ -1571,7 +1593,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to { insertSetting(AreaIncreaseSettings(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 i have chosen to only use the user setting for radius increases as a friendly recommendation. + // as such idx have chosen to only use the user setting for radius increases as a friendly recommendation. insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, !increase_radius, no_error, !use_min_radius, move), true); // a if (elem.distance_to_top < config.tip_layers) { @@ -1603,34 +1625,46 @@ void TreeSupport::increaseAreas(std::unordered_map& to } Polygons inc_wo_collision; - + // Check whether it is faster to calculate the area increased with the fast speed independently from the slow area, or time could be saved by reusing the slow area to calculate the fast one. + // Calculated by comparing the steps saved when calcualting idependently with the saved steps when not. + bool offset_independant_faster = (radius / safe_movement_distance - (((config.maximum_move_distance + extra_speed) < (radius + safe_movement_distance)) ? 1 : 0)) > (round_up_divide((extra_speed + extra_slow_speed + config.maximum_move_distance_slow), safe_movement_distance)); for (AreaIncreaseSettings settings : order) { - coord_t overspeed = settings.no_error ? 0 : config.maximum_move_distance / 2; - if (offset_slow.empty()) + if (settings.move) { - offset_slow = safeOffsetInc(*parent->area, extra_speed + extra_slow_speed + config.maximum_move_distance_slow, wall_restriction, elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance, (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance), 2).unionPolygons(); // offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. At this point one can see that the Polygons class was never made for precision in the single digit micron range. - } + if (offset_slow.empty() && (settings.increase_speed == slow_speed || !offset_independant_faster)) + { + offset_slow = safeOffsetInc(*parent->area, extra_speed + extra_slow_speed + config.maximum_move_distance_slow, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2).unionPolygons(); // offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. At this point one can see that the Polygons class was never made for precision in the single digit micron range. + } - if ((settings.increase_speed != slow_speed) && offset_fast.empty()) - { - const coord_t delta_slow_fast = config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed); - offset_fast = safeOffsetInc(offset_slow, delta_slow_fast, wall_restriction, elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance, (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance), 1).unionPolygons(); + if ((settings.increase_speed != slow_speed) && offset_fast.empty()) + { + if (offset_independant_faster) + { + offset_fast = safeOffsetInc(*parent->area, extra_speed + config.maximum_move_distance, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 1).unionPolygons(); + } + else + { + const coord_t delta_slow_fast = config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed); + offset_fast = safeOffsetInc(offset_slow, delta_slow_fast, wall_restriction, safe_movement_distance, safe_movement_distance + radius, offset_independant_faster ? 2 : 1).unionPolygons(); + } + } } std::optional result; if (!settings.no_error) // ERROR CASE { // if the area becomes for whatever reason something that clipper sees as a line, offset would stop working, so ensure that even if if wrongly would be a line, it still actually has an area that can be increased - Polygons lines_offset = safeOffsetInc(toPolylines(*parent->area).offsetPolyLine(5), extra_speed + config.maximum_move_distance, wall_restriction, elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance, (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance), 2).unionPolygons(); - result = increaseSingleArea(settings, layer_idx, parent, offset_fast.unionPolygons(lines_offset), to_bp_data, to_model_data, inc_wo_collision, overspeed, mergelayer); + Polygons lines_offset = toPolylines(*parent->area).offsetPolyLine(5); + Polygons base_error_area = parent->area->unionPolygons(lines_offset); + result = increaseSingleArea(settings, layer_idx, parent, base_error_area, to_bp_data, to_model_data, inc_wo_collision, (config.maximum_move_distance + extra_speed) * 1.5, mergelayer); logError("Influence area could not be increased! Data about the Influence area: " "Radius: %lld at layer: %d NextTarget: %lld Distance to top: %lld Elephant foot increases %llf use_min_xy_dist %d to buildplate %d gracious %d safe %d until move %d \n " "Parent %lld: Radius: %lld at layer: %d NextTarget: %lld Distance to top: %lld Elephant foot increases %llf use_min_xy_dist %d to buildplate %d gracious %d safe %d until move %d\n", - radius, layer_idx - 1, elem.next_height, elem.distance_to_top, elem.elephant_foot_increases, elem.use_min_xy_dist, elem.to_buildplate, elem.to_model_gracious, elem.can_use_safe_radius, elem.dont_move_until, parent, config.getCollisionRadius(*parent), layer_idx, parent->next_height, parent->distance_to_top, parent->elephant_foot_increases, parent->use_min_xy_dist, parent->to_buildplate, parent->to_model_gracious, parent->can_use_safe_radius, parent->dont_move_until); + radius, layer_idx - 1, elem.next_height, elem.distance_to_top, elem.elephant_foot_increases, elem.use_min_xy_dist, elem.to_buildplate, elem.to_model_gracious, elem.can_use_safe_radius, elem.dont_move_until, parent, config.getCollisionRadius(*parent), layer_idx, parent->next_height, parent->distance_to_top, parent->elephant_foot_increases, parent->use_min_xy_dist, parent->to_buildplate, parent->to_model_gracious, parent->can_use_safe_radius, parent->dont_move_until); } else { - result = increaseSingleArea(settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, overspeed, mergelayer); + result = increaseSingleArea(settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); } if (result) @@ -1671,8 +1705,8 @@ void TreeSupport::increaseAreas(std::unordered_map& to { Polygons max_influence_area = safeUnion(inc_wo_collision.difference(volumes_.getCollision(radius, layer_idx - 1, elem.use_min_xy_dist)), safeUnion(to_bp_data, to_model_data)); // union seems useless, but some rounding errors somewhere can cause to_bp_data to be slightly bigger than it should be -#pragma omp critical(newLayer) { + std::lock_guard critical_section_newLayer(critical_sections); if (bypass_merge) { Polygons* new_area = new Polygons(max_influence_area); @@ -1697,14 +1731,14 @@ void TreeSupport::increaseAreas(std::unordered_map& to { parent->result_on_layer = Point(-1, -1); // If the bottom most point of a branch is set, later functions will assume that the position is valid, and ignore it. But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. A point can be set on the top most tip layer (maybe more if it should not move for a few layers). } - } + }); } void TreeSupport::createLayerPathing(std::vector>& move_bounds) { const double data_size_inverse = 1 / double(move_bounds.size()); - double progress_total = PROGRESS_PRECALC_AVO + PROGRESS_PRECALC_COLL + PROGRESS_GENERATE_NODES; + double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES; auto dur_inc = std::chrono::duration_values::zero(); auto dur_merge = std::chrono::duration_values::zero(); @@ -1788,8 +1822,8 @@ void TreeSupport::createLayerPathing(std::vector>& mov move_bounds[layer_idx - 1].emplace(elem); } - progress_total += data_size_inverse * PROGRESS_AREA_CALC; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, PROGRESS_TOTAL); + progress_total += data_size_inverse * TREE_PROGRESS_AREA_CALC; + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); } log("Time spent with creating influence areas' subtasks: Increasing areas %lld ms merging areas: %lld ms\n", dur_inc.count() / 1000000, dur_merge.count() / 1000000); @@ -1975,41 +2009,44 @@ void TreeSupport::createNodesFromArea(std::vector>& mo } } -void TreeSupport::generateBranchAreas(std::vector>& linear_data, std::vector>& layer_tree_polygons, const std::map& inverese_tree_order) +void TreeSupport::generateBranchAreas(std::vector>& linear_data, std::vector>& layer_tree_polygons, const std::map& inverse_tree_order) { - double progress_total = PROGRESS_PRECALC_AVO + PROGRESS_PRECALC_COLL + PROGRESS_GENERATE_NODES + PROGRESS_AREA_CALC; + double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC; constexpr int progress_report_steps = 10; 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 * TAU; - branch_circle.emplace_back(cos(angle) * config.branch_radius, sin(angle) * config.branch_radius); + branch_circle.emplace_back(coord_t(cos(angle) * config.branch_radius), coord_t(sin(angle) * config.branch_radius)); } std::vector linear_inserts(linear_data.size()); const size_t progress_inserts_check_interval = linear_data.size() / progress_report_steps; - // parallel iterating over all elements -#pragma omp parallel for - for (coord_t i = 0; i < static_cast(linear_data.size()); i++) - { - SupportElement* elem = linear_data[i].second; + std::mutex critical_sections; + cura::parallel_for(0, linear_data.size(), 1, + [&](const size_t idx) + { + SupportElement* elem = linear_data[idx].second; coord_t radius = config.getRadius(*elem); bool parent_uses_min = false; - SupportElement* child_elem = inverese_tree_order.count(elem) ? inverese_tree_order.at(elem) : nullptr; + SupportElement* child_elem = inverse_tree_order.count(elem) ? inverse_tree_order.at(elem) : nullptr; // Calculate multiple ovalized circles, to connect with every parent and child. Also generate regular circle for the current layer. Merge all these into one area. std::vector> movement_directions{ std::pair(Point(0, 0), radius) }; - if (child_elem != nullptr) + if (!elem->skip_ovalisation) { - Point movement = (child_elem->result_on_layer - elem->result_on_layer); - movement_directions.emplace_back(movement, radius); - } - for (SupportElement* parent : elem->parents) - { - Point movement = (parent->result_on_layer - elem->result_on_layer); - movement_directions.emplace_back(movement, std::max(config.getRadius(parent), config.support_line_width)); - parent_uses_min |= parent->use_min_xy_dist; + if (child_elem != nullptr) + { + Point movement = (child_elem->result_on_layer - elem->result_on_layer); + movement_directions.emplace_back(movement, radius); + } + for (SupportElement* parent : elem->parents) + { + Point movement = (parent->result_on_layer - elem->result_on_layer); + movement_directions.emplace_back(movement, std::max(config.getRadius(parent), config.support_line_width)); + parent_uses_min |= parent->use_min_xy_dist; + } } coord_t max_speed = 0; @@ -2043,7 +2080,8 @@ void TreeSupport::generateBranchAreas(std::vectoruse_min_xy_dist)); // There seem to be some rounding errors, causing a branch to be a tiny bit further away from the model that it has to be. This can cause the tip to be slightly further away front the overhang (x/y wise) than optimal. This fixes it, and for every other part, 0.05mm will not be noticed. + + poly = poly.unionPolygons().offset(std::min(coord_t(50), config.support_line_width / 4)).difference(volumes_.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist)); // There seem to be some rounding errors, causing a branch to be a tiny bit further away from the model that it has to be. This can cause the tip to be slightly further away front the overhang (x/y wise) than optimal. This fixes it, and for every other part, 0.05mm will not be noticed. return poly; }; @@ -2051,18 +2089,18 @@ void TreeSupport::generateBranchAreas(std::vector radius * 0.75; // ensure branch area will not overlap with model/collision. This can happen because of e.g. ovalization or increase_until_radius. - linear_inserts[i] = generateArea(0); + linear_inserts[idx] = generateArea(0); if (fast_relative_movement || config.getRadius(*elem) - config.getCollisionRadius(*elem) > config.support_line_width) { // simulate the path the nozzle will take on the outermost wall // if multiple parts exist, the outer line will not go all around the support part potentially causing support material to be printed mid air - Polygons nozzle_path = linear_inserts[i].offset(-config.support_line_width / 2); + Polygons nozzle_path = linear_inserts[idx].offset(-config.support_line_width / 2); if (nozzle_path.splitIntoParts(false).size() > 1) { // Just try to make the area a tiny bit larger. - linear_inserts[i] = generateArea(config.support_line_width / 2); - nozzle_path = linear_inserts[i].offset(-config.support_line_width / 2); + linear_inserts[idx] = generateArea(config.support_line_width / 2); + nozzle_path = linear_inserts[idx].offset(-config.support_line_width / 2); // if larger area did not fix the problem, all parts off the nozzle path that do not contain the center point are removed, hoping for the best if (nozzle_path.splitIntoParts(false).size() > 1) @@ -2085,21 +2123,21 @@ void TreeSupport::generateBranchAreas(std::vectoruse_min_xy_dist)).unionPolygons(); + linear_inserts[idx] = polygons_with_correct_center.offset(config.support_line_width / 2).unionPolygons(); // Increase the area again, to ensure the nozzle path when calculated later is very similar to the one assumed above. + linear_inserts[idx] = linear_inserts[idx].difference(volumes_.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist)).unionPolygons(); } } } - if (i % progress_inserts_check_interval == 0) + if (idx % progress_inserts_check_interval == 0) { -#pragma omp critical(progress) { - progress_total += PROGRESS_GENERATE_BRANCH_AREAS / progress_report_steps; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, PROGRESS_TOTAL); + std::lock_guard critical_section_progress(critical_sections); + progress_total += TREE_PROGRESS_GENERATE_BRANCH_AREAS / progress_report_steps; + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); } } - } + }); // single threaded combining all elements to the right layers. ONLY COPYS DATA! for (coord_t i = 0; i < static_cast(linear_data.size()); i++) @@ -2110,7 +2148,7 @@ void TreeSupport::generateBranchAreas(std::vector>& layer_tree_polygons) { - double progress_total = PROGRESS_PRECALC_AVO + PROGRESS_PRECALC_COLL + PROGRESS_GENERATE_NODES + PROGRESS_AREA_CALC + PROGRESS_GENERATE_BRANCH_AREAS; + double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS; const coord_t max_radius_change_per_layer = 1 + config.support_line_width / 2; // this is the upper limit a radius may change per layer. +1 to avoid rounding errors // smooth upwards @@ -2119,9 +2157,9 @@ void TreeSupport::smoothBranchAreas(std::vector> processing; processing.insert(processing.end(), layer_tree_polygons[layer_idx].begin(), layer_tree_polygons[layer_idx].end()); std::vector>> update_next(processing.size()); // with this a lock can be avoided -#pragma omp parallel for schedule(static, 1) - for (coord_t processing_idx = 0; processing_idx < coord_t(processing.size()); processing_idx++) - { + cura::parallel_for(0, processing.size(), 1, + [&](const size_t processing_idx) + { std::pair data_pair = processing[processing_idx]; coord_t max_outer_wall_distance = 0; @@ -2146,7 +2184,8 @@ void TreeSupport::smoothBranchAreas(std::vector> data_vector : update_next) { for (std::pair data_pair : data_vector) @@ -2156,8 +2195,8 @@ void TreeSupport::smoothBranchAreas(std::vector updated_last_iteration; @@ -2166,9 +2205,10 @@ void TreeSupport::smoothBranchAreas(std::vector> processing; processing.insert(processing.end(), layer_tree_polygons[layer_idx].begin(), layer_tree_polygons[layer_idx].end()); std::vector> update_next(processing.size(), std::pair(nullptr, Polygons())); // with this a lock can be avoided -#pragma omp parallel for schedule(static, 1) - for (int processing_idx = 0; processing_idx < int(processing.size()); processing_idx++) - { + + cura::parallel_for(0, processing.size(), 1, + [&](const size_t processing_idx) + { std::pair data_pair = processing[processing_idx]; bool do_something = false; Polygons max_allowed_area; @@ -2198,7 +2238,8 @@ void TreeSupport::smoothBranchAreas(std::vector(data_pair.first, result); } } - } + }); + updated_last_iteration.clear(); for (std::pair data_pair : update_next) { @@ -2210,18 +2251,18 @@ void TreeSupport::smoothBranchAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, const std::map& inverese_tree_order) +void TreeSupport::dropNonGraciousAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, const std::map& inverse_tree_order) { -#pragma omp parallel for schedule(static, 1) - for (coord_t idx = 0; idx < coord_t(linear_data.size()); idx++) - { + cura::parallel_for(0, linear_data.size(), 1, + [&](const size_t idx) + { SupportElement* elem = linear_data[idx].second; - bool non_gracious_model_contact = !elem->to_model_gracious && !inverese_tree_order.count(elem); // if a element has no child, it connects to whatever is below as no support further down for it will exist. + bool non_gracious_model_contact = !elem->to_model_gracious && !inverse_tree_order.count(elem); // if a element has no child, it connects to whatever is below as no support further down for it will exist. if (non_gracious_model_contact) { @@ -2234,19 +2275,20 @@ void TreeSupport::dropNonGraciousAreas(std::vector& support_layer_storage, std::vector& support_roof_storage, SliceDataStorage& storage) { - InterfacePreferrance interface_pref = config.interface_preferrance; // InterfacePreferrance::SUPPORT_LINES_OVERWRITE_INTERFACE; - double progress_total = PROGRESS_PRECALC_AVO + PROGRESS_PRECALC_COLL + PROGRESS_GENERATE_NODES + PROGRESS_AREA_CALC + PROGRESS_GENERATE_BRANCH_AREAS + PROGRESS_SMOOTH_BRANCH_AREAS; + InterfacePreference interface_pref = config.interface_preference; // InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; + double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS + TREE_PROGRESS_SMOOTH_BRANCH_AREAS; // Iterate over the generated circles in parallel and clean them up. Also add support floor. -#pragma omp parallel for shared(support_layer_storage, support_roof_storage, storage) schedule(dynamic) - for (LayerIndex layer_idx = 0; layer_idx < static_cast(support_layer_storage.size()); layer_idx++) - { + std::mutex critical_sections; + cura::parallel_for(0, static_cast(support_layer_storage.size()), 1, + [&](const LayerIndex layer_idx) + { support_layer_storage[layer_idx] = support_layer_storage[layer_idx].unionPolygons().smooth(50); // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. support_layer_storage[layer_idx].simplify(std::min(coord_t(30), config.maximum_resolution), std::min(coord_t(10), config.maximum_deviation)); // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. // Subtract support lines of the branches from the roof @@ -2255,19 +2297,19 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& suppor { switch (interface_pref) { - case InterfacePreferrance::INTERFACE_AREA_OVERWRITES_SUPPORT: + case InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT: support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(storage.support.supportLayers[layer_idx].support_roof); break; - case InterfacePreferrance::SUPPORT_AREA_OVERWRITES_INTERFACE: + case InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE: storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.difference(support_layer_storage[layer_idx]); break; - case InterfacePreferrance::INTERFACE_LINES_OVERWRITE_SUPPORT: + case InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT: { Polygons interface_lines = toPolylines(generateSupportInfillLines(storage.support.supportLayers[layer_idx].support_roof, true, layer_idx, config.support_roof_line_distance, storage.support.cross_fill_provider)).offsetPolyLine(config.support_roof_line_width / 2); support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(interface_lines); } break; - case InterfacePreferrance::SUPPORT_LINES_OVERWRITE_INTERFACE: + case InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE: { Polygons tree_lines; tree_lines = tree_lines.unionPolygons(toPolylines(generateSupportInfillLines(support_layer_storage[layer_idx], false, layer_idx, config.support_line_distance, storage.support.cross_fill_provider, true)).offsetPolyLine(config.support_line_width / 2)); @@ -2275,7 +2317,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& suppor } break; - case InterfacePreferrance::NOTHING: + case InterfacePreference::NOTHING: break; } } @@ -2309,25 +2351,23 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& suppor for (PolygonsPart part : support_layer_storage[layer_idx].splitIntoParts(true)) // Convert every part into a PolygonsPart for the support. { - PolygonsPart outline; - outline.add(part); - storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(outline, config.support_line_width, config.support_wall_count); + storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(part, config.support_line_width, config.support_wall_count); } -#pragma omp critical(progress) { - progress_total += PROGRESS_FINALIZE_BRANCH_AREAS / support_layer_storage.size(); - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, PROGRESS_TOTAL); + std::lock_guard critical_section_progress(critical_sections); + progress_total += TREE_PROGRESS_FINALIZE_BRANCH_AREAS / support_layer_storage.size(); + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); } -#pragma omp critical(storage) { + std::lock_guard critical_section_storage(critical_sections); if (!storage.support.supportLayers[layer_idx].support_infill_parts.empty() || !storage.support.supportLayers[layer_idx].support_roof.empty()) { storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, static_cast(layer_idx)); } } - } + }); } void TreeSupport::drawAreas(std::vector>& move_bounds, SliceDataStorage& storage) @@ -2407,1052 +2447,5 @@ void TreeSupport::drawAreas(std::vector>& move_bounds, } -ModelVolumes::ModelVolumes(const SliceDataStorage& storage, 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) : max_move_{ std::max(max_move - 2, coord_t(0)) }, max_move_slow_{ std::max(max_move_slow - 2, coord_t(0)) }, progress_multiplier{ progress_multiplier }, progress_offset{ progress_offset }, machine_border_{ calculateMachineBorderCollision(storage.getMachineBorder()) } // -2 to avoid rounding errors -{ - anti_overhang_ = std::vector(storage.support.supportLayers.size(), Polygons()); - std::unordered_map mesh_to_layeroutline_idx; - min_maximum_deviation_ = std::numeric_limits::max(); - min_maximum_resolution_ = std::numeric_limits::max(); - support_rests_on_model = false; - for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) - { - SliceMeshStorage mesh = storage.meshes[mesh_idx]; - bool added = false; - for (size_t idx = 0; idx < layer_outlines_.size(); idx++) - { - if (checkSettingsEquality(layer_outlines_[idx].first, mesh.settings)) - { - added = true; - mesh_to_layeroutline_idx[mesh_idx] = idx; - } - } - if (!added) - { - mesh_to_layeroutline_idx[mesh_idx] = layer_outlines_.size(); - layer_outlines_.emplace_back(mesh.settings, std::vector(storage.support.supportLayers.size(), Polygons())); - } - } - - for (auto data_pair : layer_outlines_) - { - support_rests_on_model |= data_pair.first.get("support_type") == ESupportType::EVERYWHERE; - min_maximum_deviation_ = std::min(min_maximum_deviation_, data_pair.first.get("meshfix_maximum_deviation")); - min_maximum_resolution_ = std::min(min_maximum_resolution_, data_pair.first.get("meshfix_maximum_resolution")); - } - - min_maximum_deviation_ = std::min(coord_t(SUPPORT_TREE_MAX_DEVIATION), min_maximum_deviation_); - current_outline_idx = mesh_to_layeroutline_idx[current_mesh_idx]; - TreeSupport::TreeSupportSettings config(layer_outlines_[current_outline_idx].first); - - if (config.support_overrides == SupportDistPriority::Z_OVERRIDES_XY) - { - current_min_xy_dist = std::max(config.xy_min_distance, coord_t(100)); - current_min_xy_dist_delta = std::max(config.xy_distance, coord_t(100)) - current_min_xy_dist; - } - else - { - current_min_xy_dist = config.xy_distance; - current_min_xy_dist_delta = 0; - } - increase_until_radius = config.increase_radius_until_radius; - - for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) - { - SliceMeshStorage mesh = storage.meshes[mesh_idx]; -#pragma omp parallel for - for (LayerIndex layer_idx = 0; layer_idx < coord_t(layer_outlines_[mesh_to_layeroutline_idx[mesh_idx]].second.size()); layer_idx++) - { - if (mesh.layer_nr_max_filled_layer < layer_idx) - { - continue; // cant break as omp for loop wont allow it - } - Polygons outline = extractOutlineFromMesh(mesh, layer_idx); - layer_outlines_[mesh_to_layeroutline_idx[mesh_idx]].second[layer_idx].add(outline); - } - } -#pragma omp parallel for - for (LayerIndex layer_idx = 0; layer_idx < coord_t(anti_overhang_.size()); layer_idx++) - { - if (layer_idx < coord_t(additional_excluded_areas.size())) - { - anti_overhang_[layer_idx].add(additional_excluded_areas[layer_idx]); - } - - if (SUPPORT_TREE_AVOID_SUPPORT_BLOCKER) - { - anti_overhang_[layer_idx].add(storage.support.supportLayers[layer_idx].anti_overhang); - } - - if (storage.primeTower.enabled) - { - anti_overhang_[layer_idx].add(layer_idx == 0 ? storage.primeTower.outer_poly_first_layer : storage.primeTower.outer_poly); - } - anti_overhang_[layer_idx] = anti_overhang_[layer_idx].unionPolygons(); - } - for (size_t idx = 0; idx < layer_outlines_.size(); idx++) - { -#pragma omp parallel for - for (LayerIndex layer_idx = 0; layer_idx < coord_t(anti_overhang_.size()); layer_idx++) - { - layer_outlines_[idx].second[layer_idx] = layer_outlines_[idx].second[layer_idx].unionPolygons(); - } - } - radius_0 = config.getRadius(0); -} - - -void ModelVolumes::precalculate(coord_t max_layer) -{ - auto t_start = std::chrono::high_resolution_clock::now(); - 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. - TreeSupport::TreeSupportSettings config(layer_outlines_[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) + current_min_xy_dist_delta)); - } - // 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) + 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) + 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; - } - } - - // 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()); - - // Append additional radiis needed for collision. - - radius_until_layer[ceilRadius(increase_until_radius, false)] = max_layer; // To calculate collision holefree for every radius, the collision of radius increase_until_radius will be required. - // 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 (current_min_xy_dist_delta != 0) - { - radius_until_layer[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. - - - // ### 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; - for (RadiusLayerPair key : relevant_avoidance_radiis) - { - if (key.first < increase_until_radius + current_min_xy_dist_delta) - { - relevant_hole_collision_radiis.emplace_back(key); - } - } - - // ### Calculate collisions without holes, build from regular collision - calculateCollisionHolefree(relevant_hole_collision_radiis); - - auto t_coll = std::chrono::high_resolution_clock::now(); - - // ### Calculate the relevant avoidances - -#pragma omp parallel // calculateAvoidance does NOT include a parallel block to enable working on multiple calculations at the same time - { - calculateAvoidance(relevant_avoidance_radiis); - calculateWallRestictions(relevant_avoidance_radiis); - - if (support_rests_on_model) - { - calculatePlaceables(relevant_avoidance_radiis_to_model); - calculateAvoidanceToModel(relevant_avoidance_radiis_to_model); - } - } - 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(); - - log("Precalculating collision took %.3lf ms. Precalculating avoidance took %.3lf ms.\n", dur_col, dur_avo); -} - -const Polygons& ModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) -{ - coord_t orig_radius = radius; - std::optional> result; - if (!min_xy_dist) - { - radius += 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 }; - -#pragma omp critical(collision_cache_) - { - result = getArea(collision_cache_, key); - } - if (result) - { - return result.value().get(); - } - if (precalculated) - { - logWarning("Had to calculate collision at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); - } - calculateCollision(key); - return getCollision(orig_radius, layer_idx, min_xy_dist); -} - -const Polygons& ModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) -{ - coord_t orig_radius = radius; - std::optional> result; - if (!min_xy_dist) - { - radius += current_min_xy_dist_delta; - } - if (radius >= increase_until_radius + current_min_xy_dist_delta) - { - return getCollision(orig_radius, layer_idx, min_xy_dist); - } - RadiusLayerPair key{ radius, layer_idx }; - -#pragma omp critical(collision_cache_holefree_) - { - result = getArea(collision_cache_holefree_, key); - } - if (result) - { - return result.value().get(); - } - if (precalculated) - { - logWarning("Had to calculate collision holefree at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); - } - calculateCollisionHolefree(key); - return getCollisionHolefree(orig_radius, layer_idx, min_xy_dist); -} - -const Polygons& ModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) -{ - 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); - } - - coord_t orig_radius = radius; - - std::optional> result; - - if (!min_xy_dist) - { - radius += current_min_xy_dist_delta; - } - radius = ceilRadius(radius); - - if (radius >= increase_until_radius + current_min_xy_dist_delta && type == AvoidanceType::FAST_SAFE) // no holes anymore by definition at this request - { - type = AvoidanceType::FAST; - } - - const RadiusLayerPair key{ radius, layer_idx }; - - std::unordered_map* cache_ptr = nullptr; - if (!to_model && type == AvoidanceType::FAST) - { - cache_ptr = &avoidance_cache_; - } - else if (!to_model && type == AvoidanceType::SLOW) - { - cache_ptr = &avoidance_cache_slow_; - } - else if (!to_model && type == AvoidanceType::FAST_SAFE) - { - cache_ptr = &avoidance_cache_hole_; - } - else if (to_model && type == AvoidanceType::FAST) - { - cache_ptr = &avoidance_cache_to_model_; - } - else if (to_model && type == AvoidanceType::SLOW) - { - cache_ptr = &avoidance_cache_to_model_slow_; - } - else if (to_model && type == AvoidanceType::FAST_SAFE) - { - cache_ptr = &avoidance_cache_hole_to_model; - } - else - { - logError("Invalid Avoidance Request\n"); - } - - - if (to_model) - { -#pragma omp critical(avoidance_cache_to_model_) - { - result = getArea(*cache_ptr, key); - } - if (result) - { - return result.value().get(); - } - if (precalculated) - { - logWarning("Had to calculate Avoidance to model at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); - } - calculateAvoidanceToModel(key); - } - else - { -#pragma omp critical(avoidance_cache_) - { - result = getArea(*cache_ptr, key); - } - if (result) - { - return result.value().get(); - } - if (precalculated) - { - logWarning("Had to calculate Avoidance at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); - } - 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 Polygons& ModelVolumes::getPlaceableAreas(coord_t radius, LayerIndex layer_idx) -{ - std::optional> result; - const coord_t orig_radius = radius; - radius = ceilRadius(radius); - RadiusLayerPair key{ radius, layer_idx }; - -#pragma omp critical(placeable_areas_cache_) - { - result = getArea(placeable_areas_cache_, key); - } - if (result) - { - return result.value().get(); - } - if (precalculated) - { - logWarning("Had to calculate Placeable Areas at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", radius, layer_idx); - } - if (radius != 0) - { - calculatePlaceables(key); - } - else - { - getCollision(0, layer_idx, true); - } - return getPlaceableAreas(orig_radius, layer_idx); -} - - -const Polygons& ModelVolumes::getWallRestiction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) -{ - 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); - } - - coord_t orig_radius = radius; - min_xy_dist = min_xy_dist && current_min_xy_dist_delta > 0; - - std::optional> result; - - radius = ceilRadius(radius); - const RadiusLayerPair key{ radius, layer_idx }; - - std::unordered_map* cache_ptr; - if (min_xy_dist) - { - cache_ptr = &wall_restictions_cache_min_; - } - else - { - cache_ptr = &wall_restictions_cache_; - } - - - if (min_xy_dist) - { -#pragma omp critical(wall_restictions_cache_min_) - { - result = getArea(*cache_ptr, key); - } - if (result) - { - return result.value().get(); - } - if (precalculated) - { - logWarning("Had to calculate Wall restricions at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); - } - } - else - { -#pragma omp critical(wall_restictions_cache_) - { - result = getArea(*cache_ptr, key); - } - if (result) - { - return result.value().get(); - } - if (precalculated) - { - logWarning("Had to calculate Wall restricions at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); - } - } - calculateWallRestictions(key); - return getWallRestiction(orig_radius, layer_idx, min_xy_dist); // Retrieve failed and correct result was calculated. Now it has to be retrieved. -} - -coord_t ModelVolumes::ceilRadius(coord_t radius, bool min_xy_dist) const -{ - if (!min_xy_dist) - { - radius += current_min_xy_dist_delta; - } - return ceilRadius(radius); -} -coord_t ModelVolumes::getRadiusNextCeil(coord_t radius, bool min_xy_dist) const -{ - coord_t ceiled_radius = ceilRadius(radius, min_xy_dist); - - if (!min_xy_dist) - ceiled_radius -= current_min_xy_dist_delta; - return ceiled_radius; -} - -bool ModelVolumes::checkSettingsEquality(const Settings& me, const Settings& other) const -{ - return TreeSupport::TreeSupportSettings(me) == TreeSupport::TreeSupportSettings(other); -} - - -Polygons ModelVolumes::extractOutlineFromMesh(const SliceMeshStorage& mesh, 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 = total.unionPolygons(layer.openPolyLines.offsetPolyLine(100)); - } - coord_t maximum_resolution = mesh.settings.get("meshfix_maximum_resolution"); - coord_t maximum_deviation = mesh.settings.get("meshfix_maximum_deviation"); - total.simplify(maximum_resolution, maximum_deviation); - return total; -} - -LayerIndex ModelVolumes::getMaxCalculatedLayer(coord_t radius, const std::unordered_map& map) const -{ - coord_t 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 ModelVolumes::calculateCollision(std::deque keys) -{ -#pragma omp parallel for schedule(static, 1) - for (long long unsigned int i = 0; i < keys.size(); i++) - { - coord_t radius = keys[i].first; - RadiusLayerPair key(radius, 0); - std::unordered_map data_outer; - std::unordered_map data_placeable_outer; - for (size_t outline_idx = 0; outline_idx < layer_outlines_.size(); outline_idx++) - { - std::unordered_map data; - std::unordered_map data_placeable; - - const coord_t layer_height = layer_outlines_[outline_idx].first.get("layer_height"); - const bool support_rests_on_this_model = layer_outlines_[outline_idx].first.get("support_type") == ESupportType::EVERYWHERE; - const coord_t z_distance_bottom = layer_outlines_[outline_idx].first.get("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(layer_outlines_[outline_idx].first.get("support_top_distance"), layer_height); - const LayerIndex max_anti_overhang_layer = anti_overhang_.size() - 1; - const LayerIndex max_required_layer = keys[i].second + std::max(coord_t(1), z_distance_top_layers); - const coord_t xy_distance = outline_idx == current_outline_idx ? current_min_xy_dist : layer_outlines_[outline_idx].first.get("support_xy_distance"); - // technically this causes collision for the normal xy_distance to be larger by 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; -#pragma omp critical(collision_cache_) - { - min_layer_bottom = getMaxCalculatedLayer(radius, collision_cache_) - z_distance_bottom_layers; - } - - if (min_layer_bottom < 0) - { - min_layer_bottom = 0; - } - for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; layer_idx++) - { - key.second = layer_idx; - Polygons collision_areas = machine_border_; - if (size_t(layer_idx) < layer_outlines_[outline_idx].second.size()) - { - collision_areas.add(layer_outlines_[outline_idx].second[layer_idx]); - } - collision_areas = collision_areas.offset(radius + xy_distance); // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. - data[key].add(collision_areas); // if a key does not exist when it is accessed it is added! - } - - - // Add layers below, to ensure correct support_bottom_distance. Also save placeable areas of radius 0, if required for this mesh. - for (LayerIndex layer_idx = 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++) - { - data[key].add(data[RadiusLayerPair(radius, layer_idx - layer_offset)]); - } - if (support_rests_on_this_model && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) - { - data[key] = data[key].unionPolygons(); - Polygons above = data[RadiusLayerPair(radius, layer_idx + 1)]; - if (max_anti_overhang_layer >= layer_idx + 1) - { - above = above.unionPolygons(anti_overhang_[layer_idx]); - } - else - { - above = above.unionPolygons(); // just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly. - } - Polygons placeable = data[key].difference(above); - data_placeable[RadiusLayerPair(radius, layer_idx + 1)] = data_placeable[RadiusLayerPair(radius, layer_idx + 1)].unionPolygons(placeable); - } - } - - // 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; - for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + layer_idx <= max_required_layer; layer_offset++) - { - data[key].add(data[RadiusLayerPair(radius, layer_idx + layer_offset)]); - } - if (max_anti_overhang_layer >= layer_idx) - { - data[key] = data[key].unionPolygons(anti_overhang_[layer_idx].offset(radius)); - } - else - { - data[key] = data[key].unionPolygons(); - } - } - - for (LayerIndex layer_idx = max_required_layer; layer_idx > keys[i].second; layer_idx--) - { - data.erase(RadiusLayerPair(radius, layer_idx)); // all these dont have the correct z_distance_top_layers as they can still have areas above them - } - - for (auto pair : data) - { - pair.second.simplify(min_maximum_resolution_, min_maximum_deviation_); - data_outer[pair.first] = data_outer[pair.first].unionPolygons(pair.second); - } - if (radius == 0) - { - for (auto pair : data_placeable) - { - pair.second.simplify(min_maximum_resolution_, min_maximum_deviation_); - data_placeable_outer[pair.first] = data_placeable_outer[pair.first].unionPolygons(pair.second); - } - } - } -#pragma omp critical(progress) - { - if (precalculated && precalculation_progress < PROGRESS_PRECALC_COLL) - { - precalculation_progress += PROGRESS_PRECALC_COLL / keys.size(); - Progress::messageProgress(Progress::Stage::SUPPORT, precalculation_progress * progress_multiplier + progress_offset, PROGRESS_TOTAL); - } - } -#pragma omp critical(collision_cache_) - { - collision_cache_.insert(data_outer.begin(), data_outer.end()); - } - if (radius == 0) - { -#pragma omp critical(placeable_areas_cache_) - { - placeable_areas_cache_.insert(data_placeable_outer.begin(), data_placeable_outer.end()); - } - } - } -} -void ModelVolumes::calculateCollisionHolefree(std::deque 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); - } - -#pragma omp parallel - { - std::unordered_map data; -#pragma omp for schedule(guided) - for (coord_t layer_idx = 0; layer_idx <= max_layer; layer_idx++) - { - for (RadiusLayerPair key : keys) - { - // Logically increase the collision by increase_until_radius - coord_t radius = key.first; - coord_t increase_radius_ceil = ceilRadius(increase_until_radius, false) - ceilRadius(radius, true); - Polygons col = getCollision(increase_until_radius, layer_idx, false).offset(5 - increase_radius_ceil, ClipperLib::jtRound).unionPolygons(); // 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. - col.simplify(min_maximum_resolution_, min_maximum_deviation_); - data[RadiusLayerPair(radius, layer_idx)] = col; - } - } -#pragma omp critical(collision_cache_holefree_) - { - collision_cache_holefree_.insert(data.begin(), data.end()); - } - } -} - - -// 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 -Polygons ModelVolumes::safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) const -{ - const size_t steps = std::abs(distance / max_safe_step_distance); - assert(distance * max_safe_step_distance >= 0); - Polygons ret = me; - - for (size_t i = 0; i < steps; i++) - { - ret = ret.offset(max_safe_step_distance, jt).unionPolygons(collision); - } - ret = ret.offset(distance % max_safe_step_distance, jt); - - return ret.unionPolygons(collision); -} - -void ModelVolumes::calculateAvoidance(std::deque keys) -{ - const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; - for (long long unsigned int i = 0; i < keys.size(); i++) - { -#pragma omp for schedule(dynamic) nowait - for (size_t type_idx = 0; type_idx < all_types.size(); type_idx++) - { - AvoidanceType type = all_types[type_idx]; - const bool slow = type == AvoidanceType::SLOW; - const bool hole = type == AvoidanceType::FAST_SAFE; - - coord_t radius = keys[i].first; - LayerIndex max_required_layer = keys[i].second; - - // do not calculate not needed safe avoidances - if (hole && radius >= increase_until_radius + current_min_xy_dist_delta) - { - continue; - } - - const coord_t offset_speed = slow ? max_move_slow_ : max_move_; - const coord_t max_step_move = std::max(1.9 * radius, current_min_xy_dist * 1.9); - RadiusLayerPair key(radius, 0); - Polygons latest_avoidance; - LayerIndex start_layer; -#pragma omp critical(avoidance_cache_) - { - start_layer = 1 + getMaxCalculatedLayer(radius, slow ? avoidance_cache_slow_ : hole ? avoidance_cache_hole_ : avoidance_cache_); - } - if (start_layer > max_required_layer) - { - logDebug("Requested calculation for value already calculated ?\n"); - continue; - } - start_layer = std::max(start_layer, LayerIndex(1)); // Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1 - std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); - - - latest_avoidance = getAvoidance(radius, start_layer - 1, type, false, true); // minDist as the delta was already added, also avoidance for layer 0 will return the collision. - - // ### main loop doing the calculation - for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) - { - key.second = layer; - Polygons col; - if ((slow && radius < increase_until_radius + current_min_xy_dist_delta) || hole) - { - col = getCollisionHolefree(radius, layer, true); - } - else - { - col = getCollision(radius, layer, true); - } - latest_avoidance = safeOffset(latest_avoidance, -offset_speed, ClipperLib::jtRound, -max_step_move, col); - latest_avoidance.simplify(min_maximum_resolution_, min_maximum_deviation_); - data[layer] = std::pair(key, latest_avoidance); - } -#pragma omp critical(progress) - { - if (precalculated && precalculation_progress < PROGRESS_PRECALC_COLL + PROGRESS_PRECALC_AVO) - { - precalculation_progress += support_rests_on_model ? 0.4 : 1 * PROGRESS_PRECALC_AVO / (keys.size() * 3); - Progress::messageProgress(Progress::Stage::SUPPORT, precalculation_progress * progress_multiplier + progress_offset, PROGRESS_TOTAL); - } - } -#pragma omp critical(avoidance_cache_) - { - if (slow) - { - avoidance_cache_slow_.insert(data.begin(), data.end()); - } - else - { - if (hole) - { - avoidance_cache_hole_.insert(data.begin(), data.end()); - } - else - { - avoidance_cache_.insert(data.begin(), data.end()); - } - } - } - } - } -} - -void ModelVolumes::calculatePlaceables(std::deque keys) -{ -#pragma omp for schedule(static, 1) - for (long long unsigned int i = 0; i < keys.size(); i++) - { - const coord_t radius = keys[i].first; - const LayerIndex max_required_layer = keys[i].second; - std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); - RadiusLayerPair key(radius, 0); - - LayerIndex start_layer; -#pragma omp critical(placeable_areas_cache_) - { - start_layer = 1 + getMaxCalculatedLayer(radius, placeable_areas_cache_); - } - if (start_layer > max_required_layer) - { - logDebug("Requested calculation for value already calculated ?\n"); - continue; - } - - if (start_layer == 0) - { - data[0] = std::pair(key, machine_border_.difference(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.simplify(min_maximum_resolution_, min_maximum_deviation_); // it is faster to do this here in each thread than once in calculateCollision. - placeable = placeable.offset(-radius); - - data[layer] = std::pair(key, placeable); - } -#pragma omp critical(progress) - { - if (precalculated && precalculation_progress < PROGRESS_PRECALC_COLL + PROGRESS_PRECALC_AVO) - { - precalculation_progress += 0.2 * PROGRESS_PRECALC_AVO / (keys.size()); - Progress::messageProgress(Progress::Stage::SUPPORT, precalculation_progress * progress_multiplier + progress_offset, PROGRESS_TOTAL); - } - } - - -#pragma omp critical(placeable_areas_cache_) - { - placeable_areas_cache_.insert(data.begin(), data.end()); - } - } -} - - -void ModelVolumes::calculateAvoidanceToModel(std::deque keys) -{ - const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; - - for (long long unsigned int i = 0; i < keys.size(); i++) - { -#pragma omp for schedule(dynamic) nowait - for (size_t type_idx = 0; type_idx < all_types.size(); type_idx++) - { - AvoidanceType type = all_types[type_idx]; - bool slow = type == AvoidanceType::SLOW; - bool hole = type == AvoidanceType::FAST_SAFE; - coord_t radius = keys[i].first; - LayerIndex max_required_layer = keys[i].second; - - // do not calculate not needed safe avoidances - if (hole && radius >= increase_until_radius + current_min_xy_dist_delta) - { - continue; - } - getPlaceableAreas(radius, max_required_layer); // ensuring Placeableareas are calculated - const coord_t offset_speed = slow ? max_move_slow_ : max_move_; - const coord_t max_step_move = std::max(1.9 * radius, current_min_xy_dist * 1.9); - Polygons latest_avoidance; - std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); - RadiusLayerPair key(radius, 0); - - LayerIndex start_layer; -#pragma omp critical(avoidance_cache_to_model_) - { - start_layer = 1 + getMaxCalculatedLayer(radius, slow ? avoidance_cache_to_model_slow_ : hole ? avoidance_cache_hole_to_model : avoidance_cache_to_model_); - } - if (start_layer > max_required_layer) - { - logDebug("Requested calculation for value already calculated ?\n"); - continue; - } - start_layer = std::max(start_layer, LayerIndex(1)); - latest_avoidance = getAvoidance(radius, start_layer - 1, type, true, true); // minDist as the delta was already added, also avoidance for layer 0 will return the collision. - - // ### main loop doing the calculation - for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) - { - key.second = layer; - Polygons col = getCollision(radius, layer, true); - - if ((slow && radius < increase_until_radius + current_min_xy_dist_delta) || hole) - { - col = getCollisionHolefree(radius, layer, true); - } - else - { - col = getCollision(radius, layer, true); - } - - latest_avoidance = safeOffset(latest_avoidance, -offset_speed, ClipperLib::jtRound, -max_step_move, col).difference(getPlaceableAreas(radius, layer)); - - latest_avoidance.simplify(min_maximum_resolution_, min_maximum_deviation_); - data[layer] = std::pair(key, latest_avoidance); - } -#pragma omp critical(progress) - { - if (precalculated && precalculation_progress < PROGRESS_PRECALC_COLL + PROGRESS_PRECALC_AVO) - { - precalculation_progress += 0.4 * PROGRESS_PRECALC_AVO / (keys.size() * 3); - Progress::messageProgress(Progress::Stage::SUPPORT, precalculation_progress * progress_multiplier + progress_offset, PROGRESS_TOTAL); - } - } -#pragma omp critical(avoidance_cache_to_model_) - { - if (slow) - { - avoidance_cache_to_model_slow_.insert(data.begin(), data.end()); - } - else - { - if (hole) - { - avoidance_cache_hole_to_model.insert(data.begin(), data.end()); - } - else - { - avoidance_cache_to_model_.insert(data.begin(), data.end()); - } - } - } - } - } -} - - -void ModelVolumes::calculateWallRestictions(std::deque keys) -{ -#pragma omp for nowait schedule(dynamic) - for (long long unsigned int i = 0; i < keys.size(); i++) - { - coord_t radius = keys[i].first; - RadiusLayerPair key(radius, 0); - coord_t min_layer_bottom; - std::unordered_map data; - std::unordered_map data_min; - -#pragma omp critical(wall_restictions_cache_) - { - min_layer_bottom = getMaxCalculatedLayer(radius, wall_restictions_cache_); - } - - if (min_layer_bottom < 1) - { - min_layer_bottom = 1; - } - for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= keys[i].second; layer_idx++) - { - key.second = layer_idx; - coord_t layer_idx_below = layer_idx - 1; - Polygons wall_restriction = getCollision(0, layer_idx, false).intersection(getCollision(radius, layer_idx_below, true)); // radius contains current_min_xy_dist_delta already if required - wall_restriction.simplify(min_maximum_resolution_, min_maximum_deviation_); - data.emplace(key, wall_restriction); - if (current_min_xy_dist_delta > 0) - { - Polygons wall_restriction_min = getCollision(0, layer_idx, true).intersection(getCollision(radius, layer_idx_below, true)); - wall_restriction_min.simplify(min_maximum_resolution_, min_maximum_deviation_); - data_min.emplace(key, wall_restriction_min); - } - } - -#pragma omp critical(wall_restictions_cache_) - { - wall_restictions_cache_.insert(data.begin(), data.end()); - } -#pragma omp critical(wall_restictions_cache_min_) - { - wall_restictions_cache_min_.insert(data_min.begin(), data_min.end()); - } - } -} - -coord_t ModelVolumes::ceilRadius(coord_t radius) const -{ - if (radius == 0) - { - return 0; - } - - if (radius <= radius_0) - { - return radius_0; - } - - if (SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION) - { - // 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 - radius_0) / (SUPPORT_TREE_PRE_EXPONENTIAL_STEPS + 1); - coord_t result = radius_0; - for (size_t step = 0; step < SUPPORT_TREE_PRE_EXPONENTIAL_STEPS; step++) - { - result += stepsize; - if (result >= radius && !ignorable_radii_.count(result)) - { - return result; - } - } - - while (exponential_result < radius || 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 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 - radius_0) / SUPPORT_TREE_COLLISION_RESOLUTION; - coord_t resulting_ceil = radius_0 + (ceil_step_n + ((radius - radius_0) % SUPPORT_TREE_COLLISION_RESOLUTION != 0)) * SUPPORT_TREE_COLLISION_RESOLUTION; - - if (radius <= radius_0 && radius != 0) - { - return radius_0; - } - else if (ignorable_radii_.count(resulting_ceil)) - { - return ceilRadius(resulting_ceil + 1); - } - else - { - return resulting_ceil; - } - } -} - -template -const std::optional> ModelVolumes::getArea(const std::unordered_map& cache, const KEY key) const -{ - const auto it = cache.find(key); - if (it != cache.end()) - { - return std::optional>{ it->second }; - } - else - { - return std::optional>(); - } -} - - -Polygons ModelVolumes::calculateMachineBorderCollision(Polygon machine_border) -{ - Polygons machine_volume_border; - machine_volume_border.add(machine_border.offset(1000000)); // Put a border of 1m around the print volume so that we don't collide. - machine_border.reverse(); // Makes the polygon negative so that we subtract the actual volume from the collision area. - machine_volume_border.add(machine_border); - return machine_volume_border; -} } // namespace cura diff --git a/src/TreeSupport.h b/src/TreeSupport.h index d7b6860ff1..3fc85bf5ff 100644 --- a/src/TreeSupport.h +++ b/src/TreeSupport.h @@ -4,398 +4,41 @@ #ifndef TREESUPPORT_H #define TREESUPPORT_H +#include "TreeModelVolumes.h" +#include "boost/functional/hash.hpp" // For combining hashes +#include "polyclipping/clipper.hpp" #include "settings/EnumSettings.h" #include "sliceDataStorage.h" #include "utils/polygon.h" -#include -#include -namespace cura -{ -class ModelVolumes -{ - public: - ModelVolumes() = default; - ModelVolumes(const SliceDataStorage& storage, 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 = std::vector()); - ModelVolumes(ModelVolumes&&) = default; - ModelVolumes& operator=(ModelVolumes&&) = default; - - ModelVolumes(const ModelVolumes&) = delete; - ModelVolumes& operator=(const ModelVolumes&) = delete; - - enum class AvoidanceType - { - SLOW, - FAST_SAFE, - FAST - }; - - void precalculate(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. - * - * The result is a 2D area that would cause nodes of radius \p radius to - * collide with the model. - * - * \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& getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist = false); - - /*! - * \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 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); - - - /*! - * \brief Provides the areas that have to be avoided by the tree's branches - * in order to reach the build plate. - * - * The result is a 2D area that would cause nodes of radius \p radius to - * collide with the model or be unable to reach the build platform. - * - * The input collision areas are inset by the maximum move distance and - * propagated upwards. - * - * \param radius The radius of the node of interest - * \param layer_idx The layer of interest - * \param slow Is the propgation 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); - /*! - * \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 - * \param layer_idx The layer of interest - * \return Polygons object - */ - const Polygons& getPlaceableAreas(coord_t radius, LayerIndex layer_idx); - /*! - * \brief Provides the area that represents the walls, as in the printed area, of the model. This is an abstract representation not equal with the outline. See calculateWallRestictions for better description. - * \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& getWallRestiction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist); - /*! - * \brief Round \p radius upwards to either a multiple of radius_sample_resolution_ or a exponentially increasing value - * - * It also adds the difference between the minimum xy distance and the regular one. - * - * \param radius The radius of the node of interest - * \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; - /*! - * \brief Round \p radius upwards to the maximum that would still round up to the same value as the provided one. - * - * \param radius The radius of the node of interest - * \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; - - - private: - /*! - * \brief Convenience typedef for the keys to the caches - */ - using RadiusLayerPair = std::pair; - - using AvoidanceToModelPair = std::pair; - - - /*! - * \brief Round \p radius upwards to either a multiple of radius_sample_resolution_ or a exponentially increasing value - * - * \param radius The radius of the node of interest - */ - coord_t ceilRadius(coord_t radius) const; - - /*! - * \brief Extracts the relevant outling 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 SliceMeshStorage& mesh, 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. - * - * 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 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) }); - } - /*! - * \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. - * - * The result is a 2D area that would cause nodes of given radius to - * collide with the model or be inside a hole. Result is saved in the cache. - * A Hole is defined as an area, in which a branch with 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); - - /*! - * \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. - * - * The result is a 2D area that would cause nodes of given radius to - * collide with the model or be inside a hole. Result is saved in the cache. - * A Hole is defined as an area, in which a branch with increase_until_radius radius would collide with the wall. - * \param key RadiusLayerPairs the requested areas. The radius will be calculated up to the provided layer. - */ - void calculateCollisionHolefree(RadiusLayerPair key) - { - calculateCollisionHolefree(std::deque{ RadiusLayerPair(key) }); - } - - Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) const; - - /*! - * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model. - * - * The result is a 2D area that would cause nodes of radius \p radius to - * 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(RadiusLayerPair key) - { -#pragma omp parallel // required as otherwise the "omp for" inside calculateAvoidance will lock up - { - calculateAvoidance(std::deque{ RadiusLayerPair(key) }); - } - } - - /*! - * \brief Creates the areas where a branch of a given radius can be place on 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 calculatePlaceables(RadiusLayerPair key) - { -#pragma omp parallel - { - calculatePlaceables(std::deque{ key }); - } - } - - /*! - * \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); - - /*! - * \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) - { -#pragma omp parallel // required as otherwise the "omp for" inside calculateAvoidance will lock up - { - 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). - * - * These areas are at least xy_min_dist wide. When calculating it is always assumed that every wall is printed on top of another (as in has an overlap with the wall a layer below). Result is saved in the corresponding cache. - * - * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. - */ - void calculateWallRestictions(std::deque 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). - * These areas are at least xy_min_dist wide. When calculating it is always assumed that every wall is printed on top of another (as in has an overlap with the wall a layer below). Result is saved in the corresponding cache. - * \param key RadiusLayerPair of the requested area. It well be will be calculated up to the provided layer. - */ - void calculateWallRestictions(RadiusLayerPair key) - { -#pragma omp parallel - calculateWallRestictions(std::deque{ RadiusLayerPair(key) }); - } - - /*! - * \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) - */ - template - const std::optional> getArea(const std::unordered_map& cache, const KEY key) const; - bool checkSettingsEquality(const Settings& me, const Settings& other) const; - /*! - * \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::unordered_map& map) const; - - Polygons calculateMachineBorderCollision(Polygon machine_border); - /*! - * \brief The maximum distance that the center point of a tree branch may move in consecutive layers if it has to avoid the model. - */ - coord_t max_move_; - /*! - * \brief The maximum distance that the centrepoint of a tree branch may - * move in consequtive layers if it does not have to avoid the model - */ - coord_t max_move_slow_; - /*! - * \brief The smallest maximum resolution for simplify - */ - coord_t min_maximum_resolution_; - /*! - * \brief The smallest maximum deviation for simplify - */ - coord_t min_maximum_deviation_; - /*! - * \brief Whether the precalculate was called, meaning every required value should be cached. - */ - bool precalculated = false; - /*! - * \brief The index to access the outline corresponding with the currently processing mesh - */ - size_t current_outline_idx; - /*! - * \brief The minimum required clearance between the model and the tree branches - */ - coord_t current_min_xy_dist; - /*! - * \brief The difference between the minimum required clearance between the model and the tree branches and the regular one. - */ - coord_t current_min_xy_dist_delta; - /*! - * \brief Does at least one mesh allow support to rest on a model. - */ - bool support_rests_on_model; - /*! - * \brief The progress of the precalculate function for communicating it to the progress bar. - */ - coord_t precalculation_progress = 0; - /*! - * \brief The progress multiplier of all values added progress bar. - * Required for the progress bar the behave as expected when areas have to be calculated multiple times - */ - double progress_multiplier; - /*! - * \brief The progress offset added to all values communicated to the progress bar. - * Required for the progress bar the behave as expected when areas have to be calculated multiple times - */ - double progress_offset; - /*! - * \brief Increase radius in the resulting drawn branches, even if the avoidance does not allow it. Will be cut later to still fit. - */ - coord_t increase_until_radius; - - /*! - * \brief Polygons representing the limits of the printable area of the - * machine - */ - Polygons machine_border_; - /*! - * \brief Storage for layer outlines and the corresponding settings of the meshes grouped by meshes with identical setting. - */ - std::vector>> layer_outlines_; - /*! - * \brief Storage for areas that should be avoided, like support blocker or previous generated trees. - */ - std::vector anti_overhang_; - /*! - * \brief Radiis that can be ignored by ceilRadius as they will never be requested. - */ - std::unordered_set ignorable_radii_; - - /*! - * \brief Caches for the collision, avoidance and areas on the model where support can be placed safely - * at given radius and layer indices. - * - * These are mutable to allow modification from const function. This is - * generally considered OK as the functions are still logically const - * (ie there is no difference in behaviour for the user between - * calculating the values each time vs caching the results). - */ - mutable std::unordered_map collision_cache_; - mutable std::unordered_map collision_cache_holefree_; - - mutable std::unordered_map avoidance_cache_; - mutable std::unordered_map avoidance_cache_slow_; - mutable std::unordered_map avoidance_cache_to_model_; - mutable std::unordered_map avoidance_cache_to_model_slow_; - mutable std::unordered_map placeable_areas_cache_; +#define SUPPORT_TREE_CIRCLE_RESOLUTION 25 // The number of vertices in each circle. - /*! - * \brief Smalles radius a branch can have. This is the radius of a SupportElement with DTT=0. - */ - coord_t radius_0; +// The various stages of the process can be weighted differently in the progress bar. +// These weights are obtained experimentally using a small sample size. Sensible weights can differ drastically based on the assumed default settings and model. +#define TREE_PROGRESS_TOTAL 10000 +#define TREE_PROGRESS_PRECALC_COLL TREE_PROGRESS_TOTAL * 0.1 +#define TREE_PROGRESS_PRECALC_AVO TREE_PROGRESS_TOTAL * 0.4 +#define TREE_PROGRESS_GENERATE_NODES TREE_PROGRESS_TOTAL * 0.1 +#define TREE_PROGRESS_AREA_CALC TREE_PROGRESS_TOTAL * 0.3 +#define TREE_PROGRESS_DRAW_AREAS TREE_PROGRESS_TOTAL * 0.1 +#define TREE_PROGRESS_GENERATE_BRANCH_AREAS TREE_PROGRESS_DRAW_AREAS / 3 +#define TREE_PROGRESS_SMOOTH_BRANCH_AREAS TREE_PROGRESS_DRAW_AREAS / 3 +#define TREE_PROGRESS_FINALIZE_BRANCH_AREAS TREE_PROGRESS_DRAW_AREAS / 3 - /*! - * \brief Caches to avoid holes smaller than the radius until which the radius is always increased. Also called safe avoidances, as they are save regarding not running into holes. - */ - mutable std::unordered_map avoidance_cache_hole_; - mutable std::unordered_map avoidance_cache_hole_to_model; +#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 - /*! - * \brief Caches to represent walls not allowed to be passed over. - */ - mutable std::unordered_map wall_restictions_cache_; - mutable std::unordered_map wall_restictions_cache_min_; // 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. -}; +#define SUPPORT_TREE_MAX_DEVIATION 0 -// Some forward definitions. -class SliceDataStorage; -class SliceMeshStorage; -class ModelVolumes; +namespace cura +{ /*! @@ -405,8 +48,8 @@ class ModelVolumes; class TreeSupport { public: - using AvoidanceType = ModelVolumes::AvoidanceType; - enum class InterfacePreferrance + using AvoidanceType = TreeModelVolumes::AvoidanceType; + enum class InterfacePreference { INTERFACE_AREA_OVERWRITES_SUPPORT, SUPPORT_AREA_OVERWRITES_INTERFACE, @@ -449,7 +92,7 @@ class TreeSupport bool no_error; bool use_min_distance; bool move; - bool operator==(const AreaIncreaseSettings& other) + bool operator==(const AreaIncreaseSettings& other) const { return increase_radius == other.increase_radius && increase_speed == other.increase_speed && type == other.type && no_error == other.no_error && use_min_distance == other.use_min_distance && move == other.move; } @@ -457,26 +100,7 @@ class TreeSupport struct SupportElement { - SupportElement(coord_t distance_to_top, size_t target_height, Point target_position, bool to_buildplate, bool to_model_gracious, bool use_min_xy_dist, size_t dont_move_until, bool supports_roof, bool can_use_safe_radius, bool force_tips_to_roof) - : // SupportElement(target_height,target_position,target_height,target_position,distance_to_top,distance_to_top,to_buildplate) - 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) + SupportElement(coord_t distance_to_top, size_t target_height, Point target_position, bool to_buildplate, bool to_model_gracious, bool use_min_xy_dist, size_t dont_move_until, bool supports_roof, bool can_use_safe_radius, bool force_tips_to_roof, bool skip_ovalisation) : 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) { } @@ -500,7 +124,8 @@ class TreeSupport dont_move_until(elem.dont_move_until), can_use_safe_radius(elem.can_use_safe_radius), last_area_increase(elem.last_area_increase), - missing_roof_layers(elem.missing_roof_layers) + missing_roof_layers(elem.missing_roof_layers), + skip_ovalisation(elem.skip_ovalisation) { parents.insert(parents.begin(), elem.parents.begin(), elem.parents.end()); @@ -528,13 +153,14 @@ class TreeSupport dont_move_until(element_above->dont_move_until), can_use_safe_radius(element_above->can_use_safe_radius), last_area_increase(element_above->last_area_increase), - missing_roof_layers(element_above->missing_roof_layers) + missing_roof_layers(element_above->missing_roof_layers), + skip_ovalisation(false) { parents = { element_above }; } // ONLY to be called in merge as it assumes a few assurances made by it. - SupportElement(const SupportElement& first, const SupportElement& second, size_t next_height, Point next_position, coord_t increased_to_model_radius, 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)) + 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) @@ -551,7 +177,7 @@ class TreeSupport distance_to_top = std::max(first.distance_to_top, second.distance_to_top); to_buildplate = first.to_buildplate && second.to_buildplate; - to_model_gracious = first.to_model_gracious && second.to_model_gracious; // valid as we do not merge non gracious with gracious + to_model_gracious = first.to_model_gracious && second.to_model_gracious; // valid as we do not merge non-gracious with gracious AddParents(first.parents); AddParents(second.parents); @@ -627,7 +253,7 @@ class TreeSupport */ coord_t increased_to_model_radius; // how much to model we increased only relevant for merging /*! - * \brief Will the branch be able to rest completly on a flat surface, be it buildplate or model ? + * \brief Will the branch be able to rest completely on a flat surface, be it buildplate or model ? */ bool to_model_gracious; @@ -661,11 +287,16 @@ class TreeSupport */ AreaIncreaseSettings last_area_increase; - /* + /*! * \brief Amount of roof layers that were not yet added, because the branch needed to move. */ size_t missing_roof_layers; + /*! + * \brief Skip the ovalisation to parent and children when generating the final circles. + */ + bool skip_ovalisation; + bool operator==(const SupportElement& other) const { return target_position == other.target_position && target_height == other.target_height; @@ -688,7 +319,7 @@ class TreeSupport return other.target_position.X == target_position.X ? other.target_position.Y < target_position.Y : other.target_position.X < target_position.X; } - void AddParents(std::vector adding) + void AddParents(const std::vector& adding) { for (SupportElement* ptr : adding) { @@ -714,7 +345,7 @@ class TreeSupport maximum_move_distance((angle < TAU / 4) ? (coord_t)(tan(angle) * layer_height) : std::numeric_limits::max()), maximum_move_distance_slow((angle_slow < TAU / 4) ? (coord_t)(tan(angle_slow) * layer_height) : std::numeric_limits::max()), support_bottom_layers(mesh_group_settings.get("support_bottom_enable") ? round_divide(mesh_group_settings.get("support_bottom_height"), layer_height) : 0), - tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // ensures lines always stack nicely even if layer height is large + tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // Ensure lines always stack nicely even if layer height is large diameter_angle_scale_factor(sin(mesh_group_settings.get("support_tree_branch_diameter_angle")) * layer_height / branch_radius), max_to_model_radius_increase(mesh_group_settings.get("support_tree_max_diameter_increase_by_merges_when_support_to_model") / 2), min_dtt_to_model(round_up_divide(mesh_group_settings.get("support_tree_min_height_to_model"), layer_height)), @@ -723,7 +354,7 @@ class TreeSupport support_rests_on_model(mesh_group_settings.get("support_type") == ESupportType::EVERYWHERE), xy_distance(mesh_group_settings.get("support_xy_distance")), bp_radius(mesh_group_settings.get("support_tree_bp_diameter") / 2), - diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40° or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. + diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. support_overrides(mesh_group_settings.get("support_xy_overrides_z")), xy_min_distance(support_overrides == SupportDistPriority::Z_OVERRIDES_XY ? mesh_group_settings.get("support_xy_distance_overhang") : xy_distance), z_distance_top_layers(round_up_divide(mesh_group_settings.get("support_top_distance"), layer_height)), @@ -736,32 +367,31 @@ class TreeSupport support_roof_line_width(mesh_group_settings.get("support_roof_line_width")), support_line_distance(mesh_group_settings.get("support_line_distance")), support_bottom_offset(mesh_group_settings.get("support_bottom_offset")), - support_wall_count(mesh_group_settings.get("support_wall_count")), + support_wall_count(mesh_group_settings.get("support_wall_count")), zig_zaggify_support(mesh_group_settings.get("zig_zaggify_support")), maximum_deviation(mesh_group_settings.get("meshfix_maximum_deviation")), maximum_resolution(mesh_group_settings.get("meshfix_maximum_resolution")), - support_roof_line_distance(mesh_group_settings.get("support_roof_line_distance")), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preferrance. + support_roof_line_distance(mesh_group_settings.get("support_roof_line_distance")), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. skip_some_zags(mesh_group_settings.get("support_skip_some_zags")), zag_skip_count(mesh_group_settings.get("support_zag_skip_count")), - connect_zigzags(mesh_group_settings.get("support_connect_zigzags")) + connect_zigzags(mesh_group_settings.get("support_connect_zigzags")), + settings(mesh_group_settings), + min_feature_size(mesh_group_settings.get("min_feature_size")) { layer_start_bp_radius = (bp_radius - branch_radius) / (branch_radius * diameter_scale_bp_radius); - // safeOffsetInc can only work in steps of the size xy_min_distance (in the worst case) => xy_min_distance has be larger than 0 and should be large enough for performance to not suffer extremely - // for performance reasons it is not possible to set a VERY low xy_distance or xy_min_distance (<0.1mm) - // as such if such a value is set the support will be outset by .1mm to ensure all points are supported that should be. + + // safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely + // When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size // This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance. - constexpr coord_t min_distance = 100; // If set to low rounding errors WILL cause errors. Best to keep it above 25. - if (xy_distance < min_distance) + if (has_to_rely_on_min_xy_dist_only) { - xy_distance = min_distance; - } - if (xy_min_distance < min_distance) - { - xy_min_distance = min_distance; + xy_min_distance = std::max(coord_t(100), xy_min_distance); // If set to low rounding errors WILL cause errors. Best to keep it above 25. } + xy_distance = std::max(xy_distance, xy_min_distance); + std::function&, EFillMethod)> getInterfaceAngles = [&](std::vector& angles, EFillMethod pattern) { // (logic) from getInterfaceAngles in FFFGcodeWriter. if (angles.empty()) { @@ -792,8 +422,8 @@ class TreeSupport getInterfaceAngles(support_infill_angles, support_pattern); getInterfaceAngles(support_roof_angles, roof_pattern); - const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreferrance::SUPPORT_AREA_OVERWRITES_INTERFACE }, { "interface_area_overwrite_support_area", InterfacePreferrance::INTERFACE_AREA_OVERWRITES_SUPPORT }, { "support_lines_overwrite_interface_area", InterfacePreferrance::SUPPORT_LINES_OVERWRITE_INTERFACE }, { "interface_lines_overwrite_support_area", InterfacePreferrance::INTERFACE_LINES_OVERWRITE_SUPPORT }, { "nothing", InterfacePreferrance::NOTHING } }; - interface_preferrance = interface_map.at(mesh_group_settings.get("support_interface_priority")); + const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE }, { "interface_area_overwrite_support_area", InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT }, { "support_lines_overwrite_interface_area", InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE }, { "interface_lines_overwrite_support_area", InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT }, { "nothing", InterfacePreference::NOTHING } }; + interface_preference = interface_map.at(mesh_group_settings.get("support_interface_priority")); } private: @@ -802,8 +432,10 @@ class TreeSupport std::vector known_z; public: - static bool some_model_contains_thick_roof; // static variable, because TreeSupportConfig which needs this, will be used in ModelVolumes as this reduces redundancy. - + // some static variables dependent on other meshes that are not currently processed. + // Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy. + inline static bool some_model_contains_thick_roof = false; + inline static bool has_to_rely_on_min_xy_dist_only = false; /*! * \brief Width of a single line of support. */ @@ -927,7 +559,7 @@ class TreeSupport /* * \brief Amount of walls the support area will have. */ - size_t support_wall_count; + int support_wall_count; /* * \brief Whether support infill lines will be connected. Only required to calculate infill patterns. */ @@ -959,18 +591,32 @@ class TreeSupport /* * \brief How overlaps of an interface area with a support area should be handled. */ - InterfacePreferrance interface_preferrance; + InterfacePreference interface_preference; + + /* + * \brief The infill class wants a settings object. This one will be the correct one for all settings it uses. + */ + Settings settings; + + /* + * \brief Minimum thickness of any model features. + */ + coord_t min_feature_size; public: bool operator==(const TreeSupportSettings& other) const { return branch_radius == other.branch_radius && tip_layers == other.tip_layers && diameter_angle_scale_factor == other.diameter_angle_scale_factor && layer_start_bp_radius == other.layer_start_bp_radius && bp_radius == other.bp_radius && diameter_scale_bp_radius == other.diameter_scale_bp_radius && min_radius == other.min_radius && xy_min_distance == other.xy_min_distance && // as a recalculation of the collision areas is required to set a new min_radius. xy_distance - xy_min_distance == other.xy_distance - other.xy_min_distance && // if the delta of xy_min_distance and xy_distance is different the collision areas have to be recalculated. - support_rests_on_model == other.support_rests_on_model && increase_radius_until_layer == other.increase_radius_until_layer && min_dtt_to_model == other.min_dtt_to_model && max_to_model_radius_increase == other.max_to_model_radius_increase && maximum_move_distance == other.maximum_move_distance && maximum_move_distance_slow == other.maximum_move_distance_slow && z_distance_bottom_layers == other.z_distance_bottom_layers && support_line_width == other.support_line_width && support_overrides == other.support_overrides && // requires new avoidance calculation. Could be changed so it does not, but because i expect that this case happens seldom i dont think it is worth it. - support_line_distance == other.support_line_distance && support_roof_line_width == other.support_roof_line_width && // can not be set on a per mesh basis currently, so code to enable processing different roof line width in the same iteration seems useless. - support_bottom_offset == other.support_bottom_offset && support_wall_count == other.support_wall_count && support_pattern == other.support_pattern && roof_pattern == other.roof_pattern && // can not be set on a per mesh basis currently, so code to enable processing different roof patterns in the same iteration seems useless. + support_rests_on_model == other.support_rests_on_model && increase_radius_until_layer == other.increase_radius_until_layer && min_dtt_to_model == other.min_dtt_to_model && max_to_model_radius_increase == other.max_to_model_radius_increase && maximum_move_distance == other.maximum_move_distance && maximum_move_distance_slow == other.maximum_move_distance_slow && z_distance_bottom_layers == other.z_distance_bottom_layers && support_line_width == other.support_line_width && support_overrides == other.support_overrides && support_line_distance == other.support_line_distance && support_roof_line_width == other.support_roof_line_width && // can not be set on a per-mesh basis currently, so code to enable processing different roof line width in the same iteration seems useless. + support_bottom_offset == other.support_bottom_offset && support_wall_count == other.support_wall_count && support_pattern == other.support_pattern && roof_pattern == other.roof_pattern && // can not be set on a per-mesh basis currently, so code to enable processing different roof patterns in the same iteration seems useless. support_roof_angles == other.support_roof_angles && support_infill_angles == other.support_infill_angles && increase_radius_until_radius == other.increase_radius_until_radius && support_bottom_layers == other.support_bottom_layers && layer_height == other.layer_height && z_distance_top_layers == other.z_distance_top_layers && maximum_deviation == other.maximum_deviation && // Infill generation depends on deviation and resolution. - maximum_resolution == other.maximum_resolution && support_roof_line_distance == other.support_roof_line_distance && skip_some_zags == other.skip_some_zags && zag_skip_count == other.zag_skip_count && connect_zigzags == other.connect_zigzags && interface_preferrance == other.interface_preferrance; // So they should be identical to ensure the tree will correctly support the roof. + maximum_resolution == other.maximum_resolution && support_roof_line_distance == other.support_roof_line_distance && skip_some_zags == other.skip_some_zags && zag_skip_count == other.zag_skip_count && connect_zigzags == other.connect_zigzags && interface_preference == other.interface_preference + && min_feature_size == other.min_feature_size // interface_preference should be identical to ensure the tree will correctly interact with the roof. + // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry + && (interface_preference == InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT || interface_preference == InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE + || (settings.get("fill_outline_gaps") == other.settings.get("fill_outline_gaps") && settings.get("min_bead_width") == other.settings.get("min_bead_width") && settings.get("wall_transition_angle") == other.settings.get("wall_transition_angle") && settings.get("wall_transition_length") == other.settings.get("wall_transition_length") && settings.get("wall_split_middle_threshold") == other.settings.get("wall_split_middle_threshold") && settings.get("wall_add_middle_threshold") == other.settings.get("wall_add_middle_threshold") && settings.get("wall_distribution_count") == other.settings.get("wall_distribution_count") && settings.get("wall_transition_filter_distance") == other.settings.get("wall_transition_filter_distance") && settings.get("wall_transition_filter_deviation") == other.settings.get("wall_transition_filter_deviation") && settings.get("wall_line_width_x") == other.settings.get("wall_line_width_x") + && settings.get("meshfix_maximum_extrusion_area_deviation") == other.settings.get("meshfix_maximum_extrusion_area_deviation"))); } @@ -979,7 +625,7 @@ class TreeSupport * \param elem[in] The SupportElement one wants to know the effectiveDTT * \return The Effective DTT. */ - inline size_t getEffectiveDTT(const TreeSupport::SupportElement& elem) const + [[nodiscard]] inline size_t getEffectiveDTT(const TreeSupport::SupportElement& elem) const { return elem.effective_radius_height < increase_radius_until_layer ? (elem.distance_to_top < increase_radius_until_layer ? elem.distance_to_top : increase_radius_until_layer) : elem.effective_radius_height; } @@ -990,11 +636,11 @@ class TreeSupport * \param elephant_foot_increases[in] The elephant_foot_increases of the element. * \return The radius an element with these attributes would have. */ - inline coord_t getRadius(size_t distance_to_top, const double elephant_foot_increases = 0) const + [[nodiscard]] inline coord_t getRadius(size_t distance_to_top, const double elephant_foot_increases = 0) const { return (distance_to_top <= tip_layers ? min_radius + (branch_radius - min_radius) * distance_to_top / tip_layers : // tip - branch_radius + // base - branch_radius * (distance_to_top - tip_layers) * diameter_angle_scale_factor) + branch_radius + // base + branch_radius * (distance_to_top - tip_layers) * diameter_angle_scale_factor) + // gradual increase branch_radius * elephant_foot_increases * (std::max(diameter_scale_bp_radius - diameter_angle_scale_factor, 0.0)); } @@ -1004,7 +650,7 @@ class TreeSupport * \param elem[in] The Element. * \return The radius the element has. */ - inline coord_t getRadius(const TreeSupport::SupportElement& elem) const + [[nodiscard]] inline coord_t getRadius(const TreeSupport::SupportElement& elem) const { return getRadius(getEffectiveDTT(elem), elem.elephant_foot_increases); } @@ -1014,7 +660,7 @@ class TreeSupport * \param elem[in] The Element. * \return The collision radius the element has. */ - inline coord_t getCollisionRadius(const TreeSupport::SupportElement& elem) const + [[nodiscard]] inline coord_t getCollisionRadius(const TreeSupport::SupportElement& elem) const { return getRadius(elem.effective_radius_height, elem.elephant_foot_increases); } @@ -1024,7 +670,7 @@ class TreeSupport * \param layer_idx[in] The layer. * \return The radius every element should aim to achieve. */ - inline coord_t recommendedMinRadius(LayerIndex layer_idx) const + [[nodiscard]] inline coord_t recommendedMinRadius(LayerIndex layer_idx) const { double scale = (layer_start_bp_radius - layer_idx) * diameter_scale_bp_radius; return scale > 0 ? branch_radius + branch_radius * scale : 0; @@ -1035,7 +681,7 @@ class TreeSupport * \param layer_idx[in] The layer. * \return The radius every element should aim to achieve. */ - inline coord_t getActualZ(LayerIndex layer_idx) + [[nodiscard]] inline coord_t getActualZ(LayerIndex layer_idx) { return layer_idx < coord_t(known_z.size()) ? known_z[layer_idx] : (layer_idx - known_z.size()) * layer_height + known_z.size() ? known_z.back() : 0; } @@ -1045,7 +691,7 @@ class TreeSupport * \param z[in] The z every LayerIndex is printed. Vector is used as a map with the index of each element being the corresponding LayerIndex * \return The radius every element should aim to achieve. */ - void setActualZ(std::vector z) + void setActualZ(std::vector& z) { known_z = z; } @@ -1088,7 +734,7 @@ class TreeSupport */ Polygons convertInternalToLines(std::vector lines); /*! - * \brief Returns a function, evaluating if a point has to be added now. Required for a splitLines call in generateInitalAreas. + * \brief Returns a function, evaluating if a point has to be added now. Required for a splitLines call in generateInitialAreas. * * \param current_layer[in] The layer on which the point lies * \return A function that can be called to evaluate a point. @@ -1103,7 +749,16 @@ class TreeSupport */ std::pair, std::vector> splitLines(std::vector lines, std::function)> evaluatePoint); // assumes all Points on the current line are valid + /*! + * \brief Eensures that every line segment is about distance in length. The resulting lines may differ from the original but all points are on the original + * + * \param input[in] The lines on which evenly spaced points should be placed. + * \param distance[in] The distance the points should be from each other. + * \param min_points[in] The amount of points that have to be placed. If not enough can be placed the distance will be reduced to place this many points. + * \return A Polygons object containing the evenly spaced points. Does not represent an area, more a collection of points on lines. + */ Polygons ensureMaximumDistancePolyline(const Polygons& input, coord_t distance, size_t min_points) const; + /*! * \brief Adds the implicit line from the last vertex of a Polygon to the first one. * @@ -1132,7 +787,7 @@ class TreeSupport * \param second[in] The second Polygon. * \return The union of both Polygons */ - Polygons safeUnion(const Polygons first, const Polygons second = Polygons()) const; + [[nodiscard]] Polygons safeUnion(const Polygons first, const Polygons second = Polygons()) const; /*! * \brief Creates a valid CrossInfillProvider @@ -1146,7 +801,7 @@ class TreeSupport /*! - * \brief Creates the inital influence areas (that can later be propergated down) by placing them below the overhang. + * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. * * Generates Points where the Model should be supported and creates the areas where these points have to be placed. * @@ -1154,18 +809,18 @@ class TreeSupport * \param move_bounds[out] Storage for the influence areas. * \param storage[in] Background storage, required for adding roofs. */ - void generateInitalAreas(const SliceMeshStorage& mesh, std::vector>& move_bounds, SliceDataStorage& storage); + void generateInitialAreas(const SliceMeshStorage& mesh, std::vector>& move_bounds, SliceDataStorage& storage); /*! * \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. * \param distance[in] The distance by which me should be offset. Expects values >=0. * \param collision[in] The area representing obstacles. - * \param last_safe_step_size[in] The most it is allowed to offset in one step. + * \param last_step_offset_without_check[in] The most it is allowed to offset in one step. * \param min_amount_offset[in] How many steps have to be done at least. As this uses round offset this increases the amount of vertices, which may be required if Polygons get very small. Required as arcTolerance is not exposed in offset, which should result with a similar result. * \return The resulting Polygons object. */ - Polygons safeOffsetInc(const Polygons& me, coord_t distance, const Polygons& collision, coord_t safe_step_size, coord_t last_safe_step_size, size_t min_amount_offset) const; + [[nodiscard]] Polygons safeOffsetInc(const Polygons& me, coord_t distance, const Polygons& collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset) const; /*! @@ -1214,12 +869,13 @@ class TreeSupport * \param settings[in] Which settings have to be used to check validity. * \param layer_idx[in] Number of the current layer. * \param parent[in] The metadata of the parents influence area. - * \param relevant_offset[in] The maximal possible influence area. Will be reduced in size if required. - * \param to_bp_data[out] The part of the Influence area that can reach the buildplate + * \param relevant_offset[in] The maximal possible influence area. No guarantee regarding validity with current layer collision required, as it is ensured in-function! + * \param to_bp_data[out] The part of the Influence area that can reach the buildplate. * \param to_model_data[out] The part of the Influence area that do not have to reach the buildplate. This has overlap with new_layer_data. * \param increased[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. * \param overspeed[in] How much should the already offset area be offset again. Usually this is 0. * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. + * \return A valid support element for the next layer regarding the calculated influence areas. Empty if no influence are can be created using the supplied influence area and settings. */ std::optional increaseSingleArea(AreaIncreaseSettings settings, LayerIndex layer_idx, SupportElement* parent, const Polygons& relevant_offset, Polygons& to_bp_data, Polygons& to_model_data, Polygons& increased, const coord_t overspeed, const bool mergelayer); /*! @@ -1233,7 +889,7 @@ class TreeSupport * * \param to_bp_areas[out] Influence areas that can reach the buildplate * \param to_model_areas[out] Influence areas that do not have to reach the buildplate. This has overlap with new_layer_data, as areas that can reach the buildplate are also considered valid areas to the model. - * This redundency is required if a to_buildplate influence area is allowed to merge with a to model influence area. + * This redundancy is required if a to_buildplate influence area is allowed to merge with a to model influence area. * \param influence_areas[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. * \param bypass_merge_areas[out] Influence areas ready to be added to the layer below that do not need merging. * \param last_layer[in] Influence areas of the current layer. @@ -1243,7 +899,7 @@ class TreeSupport void increaseAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, std::vector& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, const bool mergelayer); /*! - * \brief Propergates influence downwards, and merges overlapping ones. + * \brief Propagates influence downwards, and merges overlapping ones. * * \param move_bounds[in,out] All currently existing influence areas */ @@ -1277,10 +933,10 @@ class TreeSupport * \brief Draws circles around result_on_layer points of the influence areas * * \param linear_data[in] All currently existing influence areas with the layer they are on - * \param layer_tree_polygons[out] Resulting branch areas with the layerindex they appear on. layer_tree_polygons.size() has to be at least linear_data.size() as each Influence area in linear_data will save have at least one (thats why it's a vector) corresponding branch area in layer_tree_polygons. - * \param inverese_tree_order[in] A mapping that returns the child of every influence area. + * \param layer_tree_polygons[out] Resulting branch areas with the layerindex they appear on. layer_tree_polygons.size() has to be at least linear_data.size() as each Influence area in linear_data will save have at least one (that's why it's a vector) corresponding branch area in layer_tree_polygons. + * \param inverse_tree_order[in] A mapping that returns the child of every influence area. */ - void generateBranchAreas(std::vector>& linear_data, std::vector>& layer_tree_polygons, const std::map& inverese_tree_order); + void generateBranchAreas(std::vector>& linear_data, std::vector>& layer_tree_polygons, const std::map& inverse_tree_order); /*! * \brief Applies some smoothing to the outer wall, intended to smooth out sudden jumps as they can happen when a branch moves though a hole. @@ -1294,10 +950,10 @@ class TreeSupport * * \param layer_tree_polygons[in] Resulting branch areas with the layerindex they appear on. * \param linear_data[in] All currently existing influence areas with the layer they are on - * \param dropped_down_areas[out] Areas that have to be added to support all non-gracefull areas. - * \param inverese_tree_order[in] A mapping that returns the child of every influence area. + * \param dropped_down_areas[out] Areas that have to be added to support all non-graceful areas. + * \param inverse_tree_order[in] A mapping that returns the child of every influence area. */ - void dropNonGraciousAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, const std::map& inverese_tree_order); + void dropNonGraciousAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, const std::map& inverse_tree_order); /*! @@ -1327,7 +983,7 @@ class TreeSupport * \brief Generator for model collision, avoidance and internal guide volumes. * */ - ModelVolumes volumes_; + TreeModelVolumes volumes_; /*! * \brief Contains config settings to avoid loading them in every function. This was done to improve readability of the code. @@ -1341,7 +997,7 @@ class TreeSupport double progress_multiplier = 1; /*! - * \brief The progress offset added to all values communitcated to the progress bar. + * \brief The progress offset added to all values communicated to the progress bar. * Required for the progress bar the behave as expected when areas have to be calculated multiple times */ double progress_offset = 0; @@ -1357,7 +1013,9 @@ struct hash { size_t operator()(const cura::TreeSupport::SupportElement& node) const { - return hash()(node.target_position) + node.target_height; + size_t hash_node = hash()(node.target_position); + boost::hash_combine(hash_node, size_t(node.target_height)); + return hash_node; } }; } // namespace std diff --git a/src/support.cpp b/src/support.cpp index 0034ad19a1..00e2b0b5ec 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -175,7 +175,8 @@ void AreaSupport::generateGradualSupport(SliceDataStorage& storage) const coord_t wall_count = infill_extruder.settings.get("support_wall_count"); const coord_t wall_width = infill_extruder.settings.get("support_line_width"); - const coord_t overlap = infill_extruder.settings.get("infill_overlap_mm"); + const bool tree_support_enabled = infill_extruder.settings.get("support_structure") == ESupportStructure::TREE; + // no early-out for this function; it needs to initialize the [infill_area_per_combine_per_density] float layer_skip_count = 8; // skip every so many layers as to ignore small gaps in the model making computation more easy @@ -207,8 +208,19 @@ void AreaSupport::generateGradualSupport(SliceDataStorage& storage) { continue; } + + Settings wall_settings=infill_extruder.settings; + if(tree_support_enabled){ + // For some reason having a 0.4mm wall with 0.4mm line width means the result will be two 0.2mm lines, but no matter the minimum wall thickness setting it seems to always draw one line. + // TreeSupport would rather have one line of 0.4 mm than two of 0.2mm. + // This was the best way I found to make SURE it actually adheres to this. + // This causes errors and warnings down the road (when generating the wallToolPaths), BUT i did not see any issues with the result, so this stays as some kind of stop-gap solution. + wall_settings.add("wall_split_middle_threshold","100"); + wall_settings.add("wall_add_middle_threshold","100"); + } + // NOTE: This both generates the walls _and_ returns the _actual_ infill area (the one _without_ walls) for use in the rest of the method. - const Polygons infill_area = Infill::generateWallToolPaths(support_infill_part.wall_toolpaths, original_area, wall_count, wall_width, overlap, infill_extruder.settings); + const Polygons infill_area = Infill::generateWallToolPaths(support_infill_part.wall_toolpaths, original_area, wall_count, wall_width, 0, wall_settings); const AABB& this_part_boundary_box = support_infill_part.outline_boundary_box; // calculate density areas for this island diff --git a/src/utils/algorithm.h b/src/utils/algorithm.h index 8373973d14..9f47e90a24 100644 --- a/src/utils/algorithm.h +++ b/src/utils/algorithm.h @@ -45,9 +45,11 @@ std::vector order(const std::vector &in) return order; } + + /* An implementation of parallel for. - * There are still a lot of compilers that claim to be fully C++17 compatible, but don't implement the Parallel Execution TS of the accompanying standard lybrary. - * This means that we moslty have to fall back to the things that C++11/14 provide when it comes to threading/parallelism/etc. + * There are still a lot of compilers that claim to be fully C++17 compatible, but don't implement the Parallel Execution TS of the accompanying standard library. + * This means that we mostly have to fall back to the things that C++11/14 provide when it comes to threading/parallelism/etc. * * \param from The index starts here (inclusive). * \param to The index ends here (not inclusive). @@ -57,43 +59,49 @@ std::vector order(const std::vector &in) template void parallel_for(T from, T to, T increment, const std::function& body) { + parallel_for_nowait(from,to,increment,body).wait(); +} + +/* An implementation of parallel for nowait. + * There are still a lot of compilers that claim to be fully C++17 compatible, but don't implement the Parallel Execution TS of the accompanying standard library. + * This means that we mostly have to fall back to the things that C++11/14 provide when it comes to threading/parallelism/etc. + * + * Ensure your capture is correct if you leave scope before waiting on the returned future! + * + * \param from The index starts here (inclusive). + * \param to The index ends here (not inclusive). + * \param increment Add this to the index each time. + * \param body The loop-body, as a closure. Receives the index on invocation. + * \return A future to wait on. + */ +template +std::future parallel_for_nowait(T from, T to, T increment, const std::function& body) +{ + // Sanity tests. - assert(increment > 0); - assert(from <= to); - - // Set the values so that 'to' is a whole integer multiple apart from 'from' (& early out if needed). - to = from + increment * (((to - from) + (increment - static_cast(1))) / increment); - if (to == from) - { - return; - } - - // Set an atomic countdown variable to how many tasks need to be completed. - std::atomic tasks_pending((to - from) / increment); - - // Wrap the loop-body, so that the outer scope can be notified by 'all_tasks_done'. - std::promise all_tasks_done; - const auto func = - [&body, &tasks_pending, &all_tasks_done](const T index) + assert((increment > 0 && from <= to) || (increment < 0 && from >= to) ); + + const std::function starter = [=](){ + + // Run all tasks. + std::vector> scope_guard; + for (T index = from; index < to; index += increment) { - body(index); - if (--tasks_pending == 0) - { - all_tasks_done.set_value(); - } - }; - - // Run all tasks. - std::vector> scope_guard; - for (size_t index = from; index != to; index += increment) - { - scope_guard.push_back(std::async(std::launch::async, func, index)); - } - - // Wait for the end-result before return. - all_tasks_done.get_future().wait(); + scope_guard.push_back(std::async(std::launch::async, body, index)); + } + + for (std::future& loop_iteration : scope_guard) + { + loop_iteration.wait(); + } + + }; + + auto ret = std::async(std::launch::async, starter); + return ret; } + } // namespace cura #endif // UTILS_ALGORITHM_H