From b74fde237d131aa6f21000b7066c89bfd0438e07 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 19 Jul 2022 10:55:43 +0200 Subject: [PATCH 01/29] WIP Porting tree supports by Thomas Rahm, losely based on Cura tree supports. https://github.com/ThomasRahm/CuraEngine --- src/libslic3r/TreeModelVolumes.cpp | 1108 +++++++++++++ src/libslic3r/TreeModelVolumes.hpp | 430 +++++ src/libslic3r/TreeSupport.cpp | 2484 ++++++++++++++++++++++++++++ src/libslic3r/TreeSupport.hpp | 1029 ++++++++++++ 4 files changed, 5051 insertions(+) create mode 100644 src/libslic3r/TreeModelVolumes.cpp create mode 100644 src/libslic3r/TreeModelVolumes.hpp create mode 100644 src/libslic3r/TreeSupport.cpp create mode 100644 src/libslic3r/TreeSupport.hpp diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp new file mode 100644 index 000000000..591b2c540 --- /dev/null +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -0,0 +1,1108 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#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 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); + TreeSupport::showError("Not precalculated Collision requested.", false); + } + 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) +{ + 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) + { + logWarning("Had to calculate collision holefree at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); + TreeSupport::showError("Not precalculated Holefree Collision requested.", false); + } + calculateCollisionHolefree(key); + return getCollisionHolefree(orig_radius, layer_idx, min_xy_dist); +} + +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); + + 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) + { + 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 + { + logError("Invalid Avoidance Request\n"); + TreeSupport::showError("Invalid Avoidance Request.\n", true); + } + + + 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); + TreeSupport::showError("Not precalculated Avoidance(to model) requested.", false); + } + 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); + TreeSupport::showError("Not precalculated Avoidance(to buildplate) requested.", false); + } + 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::getPlaceableAreas(coord_t radius, LayerIndex layer_idx) +{ + std::optional> result; + const coord_t orig_radius = radius; + radius = ceilRadius(radius); + RadiusLayerPair key{ radius, layer_idx }; + + { + 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); + TreeSupport::showError("Not precalculated Placeable areas requested.", false); + } + if (radius != 0) + { + calculatePlaceables(key); + } + else + { + getCollision(0, layer_idx, true); + } + return getPlaceableAreas(orig_radius, layer_idx); +} + + +const Polygons& TreeModelVolumes::getWallRestriction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) +{ + 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_restrictions_cache_min_; + } + else + { + 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); + TreeSupport::showError("Not precalculated Wall restriction of minimum xy distance requested ).", false); + } + } + 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); + TreeSupport::showError("Not precalculated Wall restriction requested ).", false); + } + } + 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, bool min_xy_dist) const +{ + 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; +} + +LayerIndex TreeModelVolumes::getMaxCalculatedLayer(coord_t radius, const std::unordered_map& map) const +{ + LayerIndex max_layer = -1; + + // the placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist. + const RadiusLayerPair key_layer_1(radius, 1); + if (getArea(map, key_layer_1)) + { + max_layer = 1; + } + + while (map.count(RadiusLayerPair(radius, max_layer + 1))) + { + max_layer++; + } + + return max_layer; +} + + +void TreeModelVolumes::calculateCollision(std::deque keys) +{ + 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++) + { + 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); + } + } + } + + { + 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) + { + { + std::lock_guard critical_section(*critical_placeable_areas_cache_); + placeable_areas_cache_.insert(data_placeable_outer.begin(), data_placeable_outer.end()); + } + } + }); +} +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); + } + 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; +} + +coord_t TreeModelVolumes::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> TreeModelVolumes::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 TreeModelVolumes::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; +} + +} \ No newline at end of file diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp new file mode 100644 index 000000000..4c8b5fbdd --- /dev/null +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -0,0 +1,430 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#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 TreeModelVolumes +{ + 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 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! + * + */ + 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 propagation with the maximum move distance slow required. + * \param to_model Does the avoidance allow good connections with the model. + * \param min_xy_dist is the minimum xy distance used. + * \return Polygons object + */ + const Polygons& getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model = false, bool min_xy_dist = false); + /*! + * \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 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; + + + /*! + * \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 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. + * + * 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. + * \return A future that has to be waited on + */ + [[nodiscard]] std::future calculateAvoidance(std::deque keys); + + /*! + * \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 key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer. + */ + void calculateAvoidance(RadiusLayerPair key) + { + calculateAvoidance(std::deque{ RadiusLayerPair(key) }).wait(); + } + + /*! + * \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) + { + calculatePlaceables(std::deque{ key }).wait(); + } + + /*! + * \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 + */ + [[nodiscard]] std::future 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. + * + * \return A future that has to be waited on + */ + [[nodiscard]] std::future calculateAvoidanceToModel(std::deque keys); + + /*! + * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model without being able to place a branch with given radius on a single layer. + * + * The result is a 2D area that would cause nodes of radius \p radius to + * collide with the model in a not wanted way. Result is saved in the cache. + * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. + */ + void calculateAvoidanceToModel(RadiusLayerPair key) + { + calculateAvoidanceToModel(std::deque{ RadiusLayerPair(key) }).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. + * + * \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 + */ + [[nodiscard]] std::future calculateWallRestrictions(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 calculateWallRestrictions(RadiusLayerPair key) + { + calculateWallRestrictions(std::deque{ RadiusLayerPair(key) }).wait(); + } + + /*! + * \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 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. + */ + std::unordered_set ignorable_radii_; + + /*! + * \brief Smallest radius a branch can have. This is the radius of a SupportElement with DTT=0. + */ + coord_t radius_0; + + + /*! + * \brief Caches for the collision, avoidance and areas on the model where support can be placed safely + * at given radius and layer indices. + * + * 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_; + 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_; + 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(); +}; + +} + +#endif //TREEMODELVOLUMES_H \ No newline at end of file diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp new file mode 100644 index 000000000..5f579f114 --- /dev/null +++ b/src/libslic3r/TreeSupport.cpp @@ -0,0 +1,2484 @@ +// Copyright (c) 2019 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "TreeSupport.h" +#include "Application.h" //To get settings. +#include "infill.h" +#include "infill/SierpinskiFillProvider.h" +#include "progress/Progress.h" +#include "settings/EnumSettings.h" +#include "support.h" //For precomputeCrossInfillTree +#include "utils/logoutput.h" +#include "utils/math.h" //For round_up_divide and PI. +#include "utils/polygonUtils.h" //For moveInside. +#include +#include +#include +#include +#include +#include +#include +#include //todo Remove! ONLY FOR PUBLIC BETA!! + +namespace cura +{ + +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. + for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) + { + SliceMeshStorage mesh = storage.meshes[mesh_idx]; + + const bool non_supportable_mesh = mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh") || storage.meshes[mesh_idx].settings.get("support_mesh"); + if (storage.meshes[mesh_idx].settings.get("support_structure") != ESupportStructure::TREE || !storage.meshes[mesh_idx].settings.get("support_enable") || non_supportable_mesh) + { + continue; + } + + bool added = false; + + TreeSupportSettings next_settings(mesh.settings); + + for (size_t idx = 0; idx < grouped_meshes.size(); idx++) + { + if (next_settings == grouped_meshes[idx].first) + { + added = true; + grouped_meshes[idx].second.emplace_back(mesh_idx); + // handle some settings that are only used for performance reasons. This ensures that a horrible set setting intended to improve performance can not reduce it drastically. + grouped_meshes[idx].first.performance_interface_skip_layers = std::min(grouped_meshes[idx].first.performance_interface_skip_layers, next_settings.performance_interface_skip_layers); + } + } + if (!added) + { + grouped_meshes.emplace_back(next_settings, std::vector{ mesh_idx }); + } + + // no need to do this per mesh group as adaptive layers and raft setting are not setable per mesh. + if (storage.meshes[largest_printed_mesh_idx].layers.back().printZ < mesh.layers.back().printZ) + { + largest_printed_mesh_idx = mesh_idx; + } + } + std::vector known_z(storage.meshes[largest_printed_mesh_idx].layers.size()); + + for (size_t z = 0; z < storage.meshes[largest_printed_mesh_idx].layers.size(); z++) + { + known_z[z] = storage.meshes[largest_printed_mesh_idx].layers[z].printZ; + } + + for (size_t idx = 0; idx < grouped_meshes.size(); idx++) + { + grouped_meshes[idx].first.setActualZ(known_z); + } +} + + +// todo remove as only for debugging relevant +std::string getPolygonAsString(const Polygons& poly) +{ + std::string ret = ""; + for (auto path : poly) + { + for (Point p : path) + { + if (ret != "") + ret += ","; + ret += "(" + std::to_string(p.X) + "," + std::to_string(p.Y) + ")"; + } + } + return ret; +} + + +void TreeSupport::showError(std::string message, bool critical) +{ // todo Remove! ONLY FOR PUBLIC BETA!! + + std::string bugtype = std::string(critical ? " This is a critical bug. It may cause missing or malformed branches.\n" : "This bug should only decrease performance.\n"); + bool show = (critical && !TreeSupport::showed_critical) || (!critical && !TreeSupport::showed_performance); + (critical ? TreeSupport::showed_critical : TreeSupport::showed_performance) = true; + + if (show) + { + MessageBox(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + message + "\n" + bugtype).c_str(), "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); + } +} + + + +void TreeSupport::generateSupportAreas(SliceDataStorage& storage) +{ + if (grouped_meshes.empty()) + { + return; + } + + if (storage.support.cross_fill_provider == nullptr) + { + AreaSupport::precomputeCrossInfillTree(storage); + } + + size_t counter = 0; + + // Process every mesh group. These groups can not be processed parallel, as the processing in each group is parallelized, and nested parallelization is disables and slow. + for (std::pair> processing : grouped_meshes) + { + // process each combination of meshes + std::vector> move_bounds(storage.support.supportLayers.size()); // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in drawAreas + log("Processing support tree mesh group %lld of %lld containing %lld meshes.\n", counter + 1, grouped_meshes.size(), grouped_meshes[counter].second.size()); + 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 + 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); + for (auto part : storage.support.supportLayers[layer_idx].support_infill_parts) + { + 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 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 : 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); + auto t_precalc = std::chrono::high_resolution_clock::now(); + + // ### Place tips of the support tree + for (size_t mesh_idx : processing.second) + { + generateInitialAreas(storage.meshes[mesh_idx], move_bounds, storage); + } + auto t_gen = std::chrono::high_resolution_clock::now(); + + // ### Propagate the influence areas downwards. + createLayerPathing(move_bounds); + auto t_path = std::chrono::high_resolution_clock::now(); + + // ### Set a point in each influence area + createNodesFromArea(move_bounds); + auto t_place = std::chrono::high_resolution_clock::now(); + + // ### draw these points as circles + drawAreas(move_bounds, storage); + + auto t_draw = std::chrono::high_resolution_clock::now(); + auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); + auto dur_gen = 0.001 * std::chrono::duration_cast(t_gen - t_precalc).count(); + auto dur_path = 0.001 * std::chrono::duration_cast(t_path - t_gen).count(); + auto dur_place = 0.001 * std::chrono::duration_cast(t_place - t_path).count(); + auto dur_draw = 0.001 * std::chrono::duration_cast(t_draw - t_place).count(); + auto dur_total = 0.001 * std::chrono::duration_cast(t_draw - t_start).count(); + log("Total time used creating Tree support for the currently grouped meshes: %.3lf ms. Different subtasks:\nCalculating Avoidance: %.3lf ms Creating inital influence areas: %.3lf ms Influence area creation: %.3lf ms Placement of Points in InfluenceAreas: %.3lf ms Drawing result as support %.3lf ms\n", dur_total, dur_pre_gen, dur_gen, dur_path, dur_place, dur_draw); + if(config.branch_radius==2121) + { + showError("Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)",false); + } + + for (auto& layer : move_bounds) + { + for (auto elem : layer) + { + delete elem->area; + delete elem; + } + } + + counter++; + } + + storage.support.generated = true; +} + + +void TreeSupport::precalculate(const SliceDataStorage& storage, std::vector currently_processing_meshes) +{ + // calculate top most layer that is relevant for support + LayerIndex max_layer = 0; + for (size_t mesh_idx : currently_processing_meshes) + { + const SliceMeshStorage& mesh = storage.meshes[mesh_idx]; + const coord_t layer_height = mesh.settings.get("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. + if (mesh.overhang_areas.size() <= z_distance_top_layers) + { + continue; + } + for (LayerIndex layer_idx = (mesh.overhang_areas.size() - z_distance_top_layers) - 1; layer_idx != 0; layer_idx--) + { + // look for max relevant layer + const Polygons& overhang = mesh.overhang_areas[layer_idx + z_distance_top_layers]; + if (!overhang.empty()) + { + if (layer_idx > max_layer) // iterates over multiple meshes + { + max_layer = 1 + layer_idx; // plus one to avoid problems if something is of by one + } + break; + } + } + } + + // ### The actual precalculation happens in TreeModelVolumes. + volumes_.precalculate(max_layer); +} + + +std::vector TreeSupport::convertLinesToInternal(Polygons polylines, LayerIndex layer_idx) +{ + const bool xy_overrides = config.support_overrides == SupportDistPriority::XY_OVERRIDES_Z; + + std::vector result; + // Also checks if the position is valid, if it is NOT, it deletes that point + for (auto line : polylines) + { + LineInformation res_line; + for (Point p : line) + { + if (!volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST_SAFE, false, !xy_overrides).inside(p, true)) + { + res_line.emplace_back(p, LineStatus::TO_BP_SAFE); + } + else if (!volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides).inside(p, true)) + { + res_line.emplace_back(p, LineStatus::TO_BP); + } + else if (config.support_rests_on_model && !volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST_SAFE, true, !xy_overrides).inside(p, true)) + { + res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS_SAFE); + } + else if (config.support_rests_on_model && !volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides).inside(p, true)) + { + res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS); + } + else if (config.support_rests_on_model && !volumes_.getCollision(config.getRadius(0), layer_idx, !xy_overrides).inside(p, true)) + { + res_line.emplace_back(p, LineStatus::TO_MODEL); + } + else + { + if (!res_line.empty()) + { + result.emplace_back(res_line); + res_line.clear(); + } + } + } + if (!res_line.empty()) + { + result.emplace_back(res_line); + res_line.clear(); + } + } + + return result; +} + +Polygons TreeSupport::convertInternalToLines(std::vector lines) +{ + Polygons result; + + for (LineInformation line : lines) + { + Polygon path; + for (auto point_data : line) + { + path.add(point_data.first); + } + result.add(path); + } + return result; +} + + +std::function)> TreeSupport::getEvaluatePointForNextLayerFunction(size_t current_layer) +{ + const bool xy_overrides = config.support_overrides == SupportDistPriority::XY_OVERRIDES_Z; + std::function)> evaluatePoint = [=](std::pair p) + { + if (!volumes_.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, false, !xy_overrides).inside(p.first, true)) + { + return true; + } + if (config.support_rests_on_model && (p.second != LineStatus::TO_BP && p.second != LineStatus::TO_BP_SAFE)) + { + if (p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE) + { + return !volumes_.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, true, !xy_overrides).inside(p.first, true); + } + else + { + return !volumes_.getCollision(config.getRadius(0), current_layer - 1, !xy_overrides).inside(p.first, true); + } + } + return false; + }; + return evaluatePoint; +} + + +std::pair, std::vector> TreeSupport::splitLines(std::vector lines, std::function)> evaluatePoint) +{ + // assumes all Points on the current line are valid + + std::vector keep(1); + std::vector set_free(1); + enum STATE + { + keeping, + freeing + }; + for (std::vector> line : lines) + { + STATE current = keeping; + LineInformation resulting_line; + for (std::pair me : line) + { + if (evaluatePoint(me)) + { + if (keeping != current) + { + if (!resulting_line.empty()) + { + set_free.emplace_back(resulting_line); + resulting_line.clear(); + } + current = keeping; + } + resulting_line.emplace_back(me); + } + else + { + if (freeing != current) + { + if (!resulting_line.empty()) + { + keep.emplace_back(resulting_line); + resulting_line.clear(); + } + current = freeing; + } + resulting_line.emplace_back(me); + } + } + if (!resulting_line.empty()) + { + if (current == keeping) + { + keep.emplace_back(resulting_line); + } + else + { + set_free.emplace_back(resulting_line); + } + } + } + return std::pair>>, std::vector>>>(keep, set_free); +} + + +void writePolylines(SVG& svg, Polygons polylines, SVG::Color color) // todo remove as only for debugging relevant +{ + for (auto path : polylines) + { + if (path.size() == 0) + { + continue; + } + if (path.size() == 1) + { + svg.writePoint(path[0], false, 2, color); + } + Point before = path[0]; + for (size_t i = 1; i < path.size(); i++) + { + svg.writeLine(before, path[i], color, 2); + before = path[i]; + } + } +} + +void writePoints(SVG& svg, Polygons polylines, SVG::Color color) // todo remove as only for debugging relevant +{ + for (auto path : polylines) + { + for (Point p : path) + { + svg.writePoint(p, false, 2, color); + } + } +} + +Polygons TreeSupport::ensureMaximumDistancePolyline(const Polygons& input, coord_t distance, size_t min_points) const +{ + Polygons result; + for (auto part : input) + { + if (part.size() == 0) + { + continue; + } + coord_t length = Polygon(part).offset(0).polyLineLength(); + Polygon line; + coord_t current_distance = std::max(distance, coord_t(100)); + if (length < 2 * distance && min_points <= 1) + { + ClosestPolygonPoint middle_point(part[0], 0, part); + middle_point = PolygonUtils::walk(middle_point, coord_t(length / 2)); + line.add(middle_point.location); + } + else + { + size_t optimal_end_index = part.size() - 1; + + if (part.front() == part.back()) + { + size_t optimal_start_index = 0; + // If the polyline was a polygon, there is a high chance it was an overhang. Overhangs that are <60� tend to be very thin areas, so lets get the beginning and end of them and ensure that they are supported. + // The first point of the line will always be supported, so rotate the order of points in this polyline that one of the two corresponding points that are furthest from each other is in the beginning. + // The other will be manually added (optimal_end_index) + coord_t max_dist2_between_vertecies = 0; + for (size_t idx = 0; idx < part.size() - 1; idx++) + { + for (size_t inner_idx = 0; inner_idx < part.size() - 1; inner_idx++) + { + if (vSize2(part[idx] - part[inner_idx]) > max_dist2_between_vertecies) + { + optimal_start_index = idx; + optimal_end_index = inner_idx; + max_dist2_between_vertecies = vSize2(part[idx] - part[inner_idx]); + } + } + } + std::rotate(part.begin(), part.begin() + optimal_start_index, part.end() - 1); + part[part.size() - 1] = part[0]; // restore that property that this polyline ends where it started. + optimal_end_index = (optimal_end_index - optimal_start_index + part.size() - 1) % (part.size() - 1); + } + + + while (line.size() < min_points && current_distance >= coord_t(100)) + { + line.clear(); + Point current_point = part[0]; + line.add(part[0]); + if (min_points > 1 || vSize(part[0] - part[optimal_end_index]) > current_distance) + { + line.add(part[optimal_end_index]); + } + size_t current_index = 0; + GivenDistPoint next_point; + coord_t next_distance = current_distance; + // Get points so that at least min_points are added and they each are current_distance away from each other. If that is impossible, decrease current_distance a bit. + while (PolygonUtils::getNextPointWithDistance(current_point, next_distance, part, current_index, 0, next_point) && next_point.pos < coord_t(part.size())) // The input are lines, that means that the line from the last to the first vertex does not have to exist, so exclude all points that are on this line! + { + // Not every point that is distance away, is valid, as it may be much closer to another point. This is especially the case when the overhang is very thin. + // So this ensures that the points are actually a certain distance from each other. + // This assurance is only made on a per polygon basis, as different but close polygon may not be able to use support below the other polygon. + coord_t min_distance_to_existing_point = std::numeric_limits::max(); + for (Point p : line) + { + min_distance_to_existing_point = std::min(min_distance_to_existing_point, vSize(p - next_point.location)); + } + if (min_distance_to_existing_point >= current_distance) + { + // viable point was found. Add to possible result. + line.add(next_point.location); + current_point = next_point.location; + current_index = next_point.pos; + next_distance = current_distance; + } + else + { + if (current_point == next_point.location) + { + // In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here... + logWarning("Tree Support: Encountered a fixpoint in getNextPointWithDistance. This is expected to happen if the distance (currently %lld) is smaller than 100\n", next_distance); + TreeSupport::showError("Encountered issue while placing tips. Some tips may be missing.", true); + if (next_distance > 2 * current_distance) + { + // This case should never happen, but better safe than sorry. + break; + } + next_distance += current_distance; + continue; + } + // if the point was too close, the next possible viable point is at least distance-min_distance_to_existing_point away from the one that was just checked. + next_distance = std::max(current_distance - min_distance_to_existing_point, coord_t(100)); + current_point = next_point.location; + current_index = next_point.pos; + } + } + current_distance *= 0.9; + } + } + result.add(line); + } + return result; +} + +// adds the implicit line from the last vertex to the first explicitly +Polygons TreeSupport::toPolylines(const Polygons& poly) const +{ + Polygons result; + for (auto path : poly) + { + Polygon part; + for (size_t i = 0; i < path.size(); i++) + { + part.add(path[i]); + } + part.add(path[0]); + result.add(part); + } + return result; +} + + +Polygons TreeSupport::generateSupportInfillLines(const Polygons& area, bool roof, LayerIndex layer_idx, coord_t support_infill_distance, SierpinskiFillProvider* cross_fill_provider, bool include_walls) +{ + Polygons gaps; + // as we effectivly use lines to place our supportPoints we may use the Infill class for it, while not made for it it works perfect + + const EFillMethod pattern = roof ? config.roof_pattern : config.support_pattern; + + const bool zig_zaggify_infill = roof ? pattern == EFillMethod::ZIG_ZAG : config.zig_zaggify_support; + const bool connect_polygons = false; + constexpr coord_t support_roof_overlap = 0; + constexpr size_t infill_multiplier = 1; + constexpr coord_t outline_offset = 0; + const int support_shift = roof ? 0 : support_infill_distance / 2; + const size_t wall_line_count = include_walls && !roof ? config.support_wall_count : 0; + const Point infill_origin; + constexpr Polygons* perimeter_gaps = nullptr; + constexpr bool use_endpieces = true; + const bool connected_zigzags = roof ? false : config.connect_zigzags; + const bool skip_some_zags = roof ? false : config.skip_some_zags; + const size_t zag_skip_count = roof ? 0 : config.zag_skip_count; + constexpr coord_t pocket_size = 0; + 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, 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(toolpaths, areas, lines, config.settings, cross_fill_provider); + lines.add(toPolylines(areas)); + return lines; +} + +Polygons TreeSupport::safeUnion(const Polygons first, const Polygons second) const +{ + // unionPolygons can slowly remove Polygons under certain circumstances, because of rounding issues (Polygons that have a thin area). + // This does not cause a problem when actually using it on large areas, but as influence areas (representing centerpoints) can be very thin, this does occur so this ugly workaround is needed + // Here is an example of a Polygons object that will loose vertices when unioning, and will be gone after a few times unionPolygons was called: + /* + Polygons example; + Polygon exampleInner; + exampleInner.add(Point(120410,83599));//A + exampleInner.add(Point(120384,83643));//B + exampleInner.add(Point(120399,83618));//C + exampleInner.add(Point(120414,83591));//D + exampleInner.add(Point(120423,83570));//E + exampleInner.add(Point(120419,83580));//F + example.add(exampleInner); + for(int i=0;i<10;i++){ + log("Iteration %d Example area: %f\n",i,example.area()); + example=example.unionPolygons(); + } +*/ + + + bool was_empty = first.empty() && second.empty(); + + Polygons result = first.unionPolygons(second); + + if (result.empty() && !was_empty) // error occurred + { + logDebug("Caught an area destroying union, enlarging areas a bit.\n"); + return toPolylines(first).offsetPolyLine(2).unionPolygons(toPolylines(second).offsetPolyLine(2)); // just take the few lines we have, and offset them a tiny bit. Needs to be offsetPolylines, as offset may aleady have problems with the area. + } + + else + { + return result; + } +} + +SierpinskiFillProvider* TreeSupport::generateCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width) +{ + const EFillMethod& support_pattern = mesh.settings.get("support_pattern"); + if (support_pattern == EFillMethod::CROSS || support_pattern == EFillMethod::CROSS_3D) + { + AABB3D aabb; + if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) + { + logWarning("Tree support tried to generate a CrossFillProvider for a non model mesh.\n"); + TreeSupport::showError("Tried to generate a CrossFillProvider for a non model mesh..\n", true); + return nullptr; + } + + const coord_t aabb_expansion = mesh.settings.get("support_offset"); + AABB3D aabb_here(mesh.bounding_box); + aabb_here.include(aabb_here.min - Point3(-aabb_expansion, -aabb_expansion, 0)); + aabb_here.include(aabb_here.max + Point3(-aabb_expansion, -aabb_expansion, 0)); + aabb.include(aabb_here); + + std::string cross_subdisivion_spec_image_file = mesh.settings.get("cross_support_density_image"); + std::ifstream cross_fs(cross_subdisivion_spec_image_file.c_str()); + if (cross_subdisivion_spec_image_file != "" && cross_fs.good()) + { + return new SierpinskiFillProvider(aabb, line_distance, line_width, cross_subdisivion_spec_image_file); + } + else + { + return new SierpinskiFillProvider(aabb, line_distance, line_width); + } + } + return nullptr; +} + + +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(coord_t(cos(angle) * base_radius), coord_t(sin(angle) * base_radius)); + } + TreeSupportSettings mesh_config(mesh.settings); + + const size_t z_distance_delta = mesh_config.z_distance_top_layers + 1; // To ensure z_distance_top_layers are left empty between the overhang (zeroth empty layer), the support has to be added z_distance_top_layers+1 layers below + + const bool xy_overrides = mesh_config.support_overrides == SupportDistPriority::XY_OVERRIDES_Z; + const coord_t support_roof_line_distance = mesh.settings.get("support_roof_line_distance"); + const double minimum_roof_area = mesh.settings.get("minimum_roof_area"); + const double minimum_support_area = mesh.settings.get("minimum_support_area"); + const size_t support_roof_layers = mesh.settings.get("support_roof_enable") ? round_divide(mesh.settings.get("support_roof_height"), mesh_config.layer_height) : 0; + const bool roof_enabled = support_roof_layers != 0; + const bool only_gracious = SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL; + 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 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. + const bool force_tip_to_roof = (mesh_config.min_radius * mesh_config.min_radius * M_PI > minimum_roof_area * (1000 * 1000)) && roof_enabled; + const double support_overhang_angle = mesh.settings.get("support_angle"); + const coord_t max_overhang_speed = (support_overhang_angle < TAU / 4) ? (coord_t)(tan(support_overhang_angle) * mesh_config.layer_height) : std::numeric_limits::max(); + const size_t max_overhang_insert_lag = std::max((size_t)round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers); // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. The 2*z_distance_delta is only a catch for when the support angle is very high. + SierpinskiFillProvider* cross_fill_provider = generateCrossFillProvider(mesh, support_tree_branch_distance, mesh_config.support_line_width); + if (mesh.overhang_areas.size() <= z_distance_delta) + { + return; + } + std::vector> already_inserted(mesh.overhang_areas.size() - z_distance_delta); + + + 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()) + { + 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) + { + 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, 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; + bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; + if (!mesh_config.support_rests_on_model && !to_bp) + { + logWarning("Tried to add an invalid support point\n"); + TreeSupport::showError("Unable to add tip. Some overhang may not be supported correctly.", true); + return; + } + Polygon circle; + for (Point corner : base_circle) + { + circle.add(p.first + corner); + } + Polygons area = circle.offset(0); + { + 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, skip_ovalisation); + elem->area = new Polygons(area); + move_bounds[insert_layer].emplace(elem); + } + } + }; + + + 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_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) + { + Polygon roof_circle; + for (Point corner : base_circle) + { + roof_circle.add(p.first + corner * mesh_config.min_radius); + } + Polygons area = roof_circle.offset(0); + return !generateLines(area, true, insert_layer_idx - dtt_roof_tip).empty(); + }; + + 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, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); + } + } + + // not all roofs are guaranteed to actually generate lines, so filter these out and add them as points + split = splitLines(split.first, evaluateRoofWillGenerate); + lines = split.first; + + for (LineInformation line : split.second) + { + for (std::pair point_data : line) + { + addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); + } + } + + // add all tips as roof to the roof storage + Polygons added_roofs; + for (LineInformation line : lines) + { + for (std::pair p : line) + { + Polygon roof_circle; + for (Point corner : base_circle) + { + roof_circle.add(p.first + corner * mesh_config.min_radius / base_radius); + } + added_roofs.add(roof_circle); + } + } + added_roofs = added_roofs.unionPolygons(); + { + 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, 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 * 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; + + // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. + while (extra_total_offset_acc + mesh_config.support_line_width / 8 < extra_outset) //+mesh_config.support_line_width / 80 to avoid calculating very small (useless) offsets because of rounding errors. + { + 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, 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, 0, 1); + overhang_regular = overhang_regular.unionPolygons(next_overhang.difference(relevant_forbidden)); + } + + // If the xy distance overrides the z distance, some support needs to be inserted further down. + //=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the the setting description, it would not produce reasonable results. ) + if (xy_overrides) + { + std::vector overhang_lines; + Polygons polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang, false, layer_idx), mesh_config.min_radius, 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 consistent behavior as some infill patterns generate each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that the area that is valid a layer below is to small for support roof. + if (polylines.pointCount() <= 3) + { + // add the outer wall to ensure it is correct supported instead + polylines = ensureMaximumDistancePolyline(toPolylines(remaining_overhang), connect_length, 3); + } + + for (auto line : polylines) + { + LineInformation res_line; + for (Point p : line) + { + res_line.emplace_back(p, LineStatus::INVALID); + } + overhang_lines.emplace_back(res_line); + } + + for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) + { + // get least restricted avoidance for layer_idx-lag_ctr + Polygons relevant_forbidden_below = (mesh_config.support_rests_on_model ? (only_gracious ? volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, true, !xy_overrides) : volumes_.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, !xy_overrides)) : volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, false, !xy_overrides)); + // it is not required to offset the forbidden area here as the points wont change: If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. + std::function)> evaluatePoint = [&](std::pair p) { return relevant_forbidden_below.inside(p.first, true); }; + + std::pair, std::vector> split = splitLines(overhang_lines, evaluatePoint); // keep all lines that are invalid + 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, 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, 0, 1); + overhang_roofs.removeSmallAreas(minimum_roof_area); + overhang_regular = overhang_regular.difference(overhang_roofs); + for (Polygons roof_part : overhang_roofs.splitIntoParts(true)) + { + overhang_processing.emplace_back(roof_part, true); + } + } + overhang_regular.removeSmallAreas(minimum_support_area); + + for (Polygons support_part : overhang_regular.splitIntoParts(true)) + { + overhang_processing.emplace_back(support_part, false); + } + + for (std::pair overhang_pair : overhang_processing) + { + const bool roof_allowed_for_this_part = overhang_pair.second; + Polygons overhang_outset = overhang_pair.first; + const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), overhang_outset.polygonLength() / connect_length)); + std::vector overhang_lines; + Polygons last_overhang = overhang_outset; + size_t dtt_roof = 0; + std::vector added_roofs(support_roof_layers); // sometimes roofs could be empty as the pattern does not generate lines if the area is narrow enough (i am looking at you, concentric infill). To catch these cases the added roofs are saved to be evaluated later. + + // Assumption is that roof will support roof further up to avoid a lot of unnecessary branches. Each layer down it is checked whether the roof area is still large enough to be a roof and aborted as soon as it is not. + // This part was already reworked a few times, and there could be an argument made to change it again if there are actual issues encountered regarding supporting roofs. + // Main problem is that some patterns change each layer, so just calculating points and checking if they are still valid an layer below is not useful, as the pattern may be different one layer below. + // Same with calculating which points are now no longer being generated as result from a decreasing roof, as there is no guarantee that a line will be above these points. + // Implementing a separate roof support behavior for each pattern harms maintainability as it very well could be >100 LOC + if (roof_allowed_for_this_part) + { + for (dtt_roof = 0; dtt_roof < support_roof_layers && layer_idx - dtt_roof >= 1; dtt_roof++) + { + // here the roof is handled. If roof can not be added the branches will try to not move instead + Polygons forbidden_next = (mesh_config.support_rests_on_model ? (only_gracious ? volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, true, !xy_overrides) : volumes_.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides)) : volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides)); + forbidden_next = forbidden_next.offset(5); // prevent rounding errors down the line + Polygons overhang_outset_next = overhang_outset.difference(forbidden_next); + if (overhang_outset_next.area() / (1000 * 1000) < minimum_roof_area) // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter + { + size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; + if (dtt_roof != 0) + { + overhang_lines = convertLinesToInternal(ensureMaximumDistancePolyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); + overhang_lines = splitLines(overhang_lines, getEvaluatePointForNextLayerFunction(layer_idx - dtt_before)).first; + } + + break; + } + added_roofs[dtt_roof] = overhang_outset; + last_overhang = overhang_outset; + overhang_outset = overhang_outset_next; + } + } + + size_t layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; // 1 inside max and -1 outside to avoid underflow. layer_generation_dtt=dtt_roof-1 if dtt_roof!=0; + if (overhang_lines.empty() && dtt_roof != 0 && generateLines(overhang_outset, true, layer_idx - layer_generation_dtt).empty()) // if the roof should be valid, check that the area does generate lines. This is NOT guaranteed. + { + for (size_t idx = 0; idx < dtt_roof; idx++) + { + // check for every roof area that it has resulting lines. Remember idx 1 means the 2. layer of roof => higher idx == lower layer + if (generateLines(added_roofs[idx], true, layer_idx - idx).empty()) + { + dtt_roof = idx; + layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; + break; + } + } + } + + { + std::lock_guard 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 + } + } + + 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. + 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); + } + else + { + polylines = ensureMaximumDistancePolyline(toPolylines(overhang_outset), connect_length, min_support_points); + } + } + LayerIndex last_insert_layer = layer_idx - dtt_roof; + overhang_lines = convertLinesToInternal(polylines, last_insert_layer); + } + + if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part) // reached buildplate + { + { + 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, 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_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 (do_final_difference ? ret.difference(collision) : ret).unionPolygons(); + } + if (safe_step_size < 0 || last_step_offset_without_check < 0) + { + logError("Offset increase got invalid parameter!\n"); + TreeSupport::showError("Negative offset distance... How did you manage this ?", true); + return (do_final_difference ? ret.difference(collision) : ret).unionPolygons(); + } + + coord_t step_size = safe_step_size; + 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) + { + 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++) + { + ret = ret.offset(step_size, ClipperLib::jtRound).difference(collision).unionPolygons(); + // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. + if (i % 10 == 7) + { + ret.simplify(15); + } + } + ret = ret.offset(distance - steps * step_size, ClipperLib::jtRound); // offset the remainder + + ret.simplify(15); + + if (do_final_difference) + { + ret = ret.difference(collision); + } + return ret.unionPolygons(); +} + + +void TreeSupport::mergeHelper(std::map& reduced_aabb, std::map& input_aabb, const std::unordered_map& to_bp_areas, const std::unordered_map& to_model_areas, const std::map& influence_areas, std::unordered_map& insert_bp_areas, std::unordered_map& insert_model_areas, std::unordered_map& insert_influence, std::vector& erase, const LayerIndex layer_idx) +{ + const bool first_merge_iteration = reduced_aabb.empty(); // If this is the first iteration, all elements in input have to be merged with each other + for (std::map::iterator influence_iter = input_aabb.begin(); influence_iter != input_aabb.end(); influence_iter++) + { + bool merged = false; + AABB influence_aabb = influence_iter->second; + for (std::map::iterator reduced_check_iter = reduced_aabb.begin(); reduced_check_iter != reduced_aabb.end(); reduced_check_iter++) + { + // As every area has to be checked for overlaps with other areas, some fast heuristic is needed to abort early if clearly possible + // This is so performance critical that using a map lookup instead of the direct access of the cached AABBs can have a surprisingly large performance impact + AABB aabb = reduced_check_iter->second; + if (aabb.hit(influence_aabb)) + { + if (!first_merge_iteration && input_aabb.count(reduced_check_iter->first)) + { + break; // Do not try to merge elements that already should have been merged. Done for potential performance improvement. + } + + bool merging_gracious_and_non_gracious = reduced_check_iter->first.to_model_gracious != influence_iter->first.to_model_gracious; // we do not want to merge a gracious with a non gracious area as bad placement could negatively impact the dependability of the whole subtree + bool merging_to_bp = reduced_check_iter->first.to_buildplate && influence_iter->first.to_buildplate; + bool merging_min_and_regular_xy = reduced_check_iter->first.use_min_xy_dist != influence_iter->first.use_min_xy_dist; // could cause some issues with the increase of one area, as it is assumed that if the smaller is increased by the delta to the larger it is engulfed by it already. But because a different collision may be removed from the in drawArea generated circles, this assumption could be wrong. + coord_t increased_to_model_radius = 0; + size_t larger_to_model_dtt = 0; + + if (!merging_to_bp) + { + coord_t infl_radius = config.getRadius(influence_iter->first); // get the real radius increase as the user does not care for the collision model. + coord_t redu_radius = config.getRadius(reduced_check_iter->first); + if (reduced_check_iter->first.to_buildplate != influence_iter->first.to_buildplate) + { + if (reduced_check_iter->first.to_buildplate) + { + if (infl_radius < redu_radius) + { + increased_to_model_radius = influence_iter->first.increased_to_model_radius + redu_radius - infl_radius; + } + } + else + { + if (infl_radius > redu_radius) + { + increased_to_model_radius = reduced_check_iter->first.increased_to_model_radius + infl_radius - redu_radius; + } + } + } + larger_to_model_dtt = std::max(influence_iter->first.distance_to_top, reduced_check_iter->first.distance_to_top); + } + + // if a merge could place a stable branch on unstable ground, would be increasing the radius further than allowed to when merging to model and to_bp trees or would merge to model before it is known they will even been drawn the merge is skipped + if (merging_min_and_regular_xy || merging_gracious_and_non_gracious || increased_to_model_radius > config.max_to_model_radius_increase || (!merging_to_bp && larger_to_model_dtt < config.min_dtt_to_model && !reduced_check_iter->first.supports_roof && !influence_iter->first.supports_roof)) + { + continue; + } + + Polygons relevant_infl; + Polygons relevant_redu; + + if (merging_to_bp) + { + relevant_infl = to_bp_areas.count(influence_iter->first) ? to_bp_areas.at(influence_iter->first) : Polygons(); // influence_iter->first is a new element => not required to check if it was changed + relevant_redu = insert_bp_areas.count(reduced_check_iter->first) ? insert_bp_areas[reduced_check_iter->first] : (to_bp_areas.count(reduced_check_iter->first) ? to_bp_areas.at(reduced_check_iter->first) : Polygons()); + } + else + { + relevant_infl = to_model_areas.count(influence_iter->first) ? to_model_areas.at(influence_iter->first) : Polygons(); + relevant_redu = insert_model_areas.count(reduced_check_iter->first) ? insert_model_areas[reduced_check_iter->first] : (to_model_areas.count(reduced_check_iter->first) ? to_model_areas.at(reduced_check_iter->first) : Polygons()); + } + + const bool red_bigger = config.getCollisionRadius(reduced_check_iter->first) > config.getCollisionRadius(influence_iter->first); + std::pair smaller_rad = red_bigger ? std::pair(influence_iter->first, relevant_infl) : std::pair(reduced_check_iter->first, relevant_redu); + std::pair bigger_rad = red_bigger ? std::pair(reduced_check_iter->first, relevant_redu) : std::pair(influence_iter->first, relevant_infl); + const coord_t real_radius_delta = std::abs(config.getRadius(bigger_rad.first) - config.getRadius(smaller_rad.first)); + const coord_t smaller_collision_radius = config.getCollisionRadius(smaller_rad.first); + + // the area of the bigger radius is used to ensure correct placement regarding the relevant avoidance, so if that would change an invalid area may be created + if (!bigger_rad.first.can_use_safe_radius && smaller_rad.first.can_use_safe_radius) + { + continue; + } + + // the bigger radius is used to verify that the area is still valid after the increase with the delta. If there were a point where the big influence area could be valid with can_use_safe_radius the element would already be can_use_safe_radius + // the smaller radius, which gets increased by delta may reach into the area where use_min_xy_dist is no longer required. + bool use_min_radius = bigger_rad.first.use_min_xy_dist && smaller_rad.first.use_min_xy_dist; + + // The idea is that the influence area with the smaller collision radius is increased by the radius difference. + // If this area has any intersections with the influence area of the larger collision radius, a branch (of the larger collision radius) placed in this intersection, has already engulfed the branch of the smaller collision radius. + // Because of this a merge may happen even if the influence areas (that represent possible center points of branches) do not intersect yet. + // Remember that collision radius <= real radius as otherwise this assumption would be false. + Polygons small_rad_increased_by_big_minus_small = safeOffsetInc(smaller_rad.second, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); // -3 avoids possible rounding errors + Polygons intersect = 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) + { + if (intersect.offset(-25).area() <= 1) // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). While 25 was guessed as enough, i did not have reason to change it. + { + continue; + } + + // Do the actual merge now that the branches are confirmed to be able to intersect. + + // calculate which point is closest to the point of the last merge (or tip center if no merge above it has happened) + // used at the end to estimate where to best place the branch on the bottom most layer + // could be replaced with a random point inside the new area + Point new_pos = reduced_check_iter->first.next_position; + if (!intersect.inside(new_pos, true)) + { + PolygonUtils::moveInside(intersect, new_pos); + } + + if (increased_to_model_radius == 0) + { + increased_to_model_radius = std::max(reduced_check_iter->first.increased_to_model_radius, influence_iter->first.increased_to_model_radius); + } + + SupportElement key(reduced_check_iter->first, influence_iter->first, layer_idx - 1, new_pos, increased_to_model_radius, config); + + Polygons intersect_influence; + Polygons infl_small = insert_influence.count(smaller_rad.first) ? insert_influence[smaller_rad.first] : (influence_areas.count(smaller_rad.first) ? influence_areas.at(smaller_rad.first) : Polygons()); + Polygons infl_big = insert_influence.count(bigger_rad.first) ? insert_influence[bigger_rad.first] : (influence_areas.count(bigger_rad.first) ? influence_areas.at(bigger_rad.first) : Polygons()); + Polygons small_rad_increased_by_big_minus_small_infl = safeOffsetInc(infl_small, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); + intersect_influence = 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. + + Polygons intersect_sec; + if (merging_to_bp && config.support_rests_on_model) + { + if (key.to_model_gracious) + { + Polygons sec_small = insert_model_areas.count(smaller_rad.first) ? insert_model_areas[smaller_rad.first] : (to_model_areas.count(smaller_rad.first) ? to_model_areas.at(smaller_rad.first) : Polygons()); + Polygons sec_big = insert_model_areas.count(bigger_rad.first) ? insert_model_areas[bigger_rad.first] : (to_model_areas.count(bigger_rad.first) ? to_model_areas.at(bigger_rad.first) : Polygons()); + Polygons small_rad_increased_by_big_minus_small_sec = safeOffsetInc(sec_small, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); + intersect_sec = 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 + } + else + { + intersect_sec = intersect_influence; + } + } + + // remove the now merged elements from all buckets, as they do not exist anymore in their old form + insert_bp_areas.erase(reduced_check_iter->first); + insert_bp_areas.erase(influence_iter->first); + insert_model_areas.erase(reduced_check_iter->first); + insert_model_areas.erase(influence_iter->first); + insert_influence.erase(reduced_check_iter->first); + insert_influence.erase(influence_iter->first); + + (merging_to_bp ? insert_bp_areas : insert_model_areas).emplace(key, intersect); + if (merging_to_bp && config.support_rests_on_model) + { + insert_model_areas.emplace(key, intersect_sec); + } + insert_influence.emplace(key, intersect_influence); + + erase.emplace_back(reduced_check_iter->first); + erase.emplace_back(influence_iter->first); + Polygons merge = intersect.unionPolygons(intersect_sec).offset(config.getRadius(key), ClipperLib::jtRound).difference(volumes_.getCollision(0, layer_idx - 1)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. + + reduced_aabb.erase(reduced_check_iter->first); // this invalidates reduced_check_iter + reduced_aabb.emplace(key, AABB(merge)); + + merged = true; + break; + } + } + } + + if (!merged) + { + reduced_aabb[influence_iter->first] = influence_aabb; + } + } +} + + +void TreeSupport::mergeInfluenceAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, LayerIndex layer_idx) +{ + /* + * Idea behind this is that the calculation of merges can be accelerated a bit using divide and conquer: + * If two groups of areas are already merged, only all elements in group 2 have to be merged into group one. + * This can only accelerate by factor 2 (as half the work is merging the last two groups). + * The actual merge logic is found in mergeHelper. This function only manages parallelization of different mergeHelper calls. + */ + + + const size_t input_size = influence_areas.size(); + size_t num_threads = std::max(size_t(1), size_t(std::thread::hardware_concurrency())); // For some reason hardware concurrency can return 0; + + if (input_size == 0) + { + return; + } + constexpr int min_elements_per_bucket = 2; + + // max_bucket_count is input_size/min_elements_per_bucket round down to the next 2^n. + // The rounding to 2^n is to ensure improved performance, as every iteration two buckets will be merged, halving the amount of buckets. + // If halving would cause an uneven count, e.g. 3 Then bucket 0 and 1 would have to be merged, and in the next iteration the last remaining buckets. This is assumed to not be optimal performance-wise. + const size_t max_bucket_count = std::pow(2, std::floor(std::log(round_up_divide(input_size, min_elements_per_bucket)))); + int bucket_count = std::min(max_bucket_count, num_threads); // do not use more buckets than available threads. + + // To achieve that every element in a bucket is already correctly merged with other elements in this bucket + // an extra empty bucket is created for each bucket, and the elements are merged into the empty one. + // Each thread will then process two buckets by merging all elements in the second bucket into the first one as mergeHelper will disable not trying to merge elements from the same bucket in this case. + std::vector> buckets_area(2 * bucket_count); + std::vector> buckets_aabb(2 * bucket_count); + + + size_t position = 0, counter = 0; + const size_t over_elements = input_size % bucket_count; + const size_t elements_per_step = input_size / bucket_count; + + // split the data in x parts to be able to divide and conquer + // the first "over_elements" of buckets gets elements_per_step+1 elements + for (std::map::iterator iter = influence_areas.begin(); iter != influence_areas.end(); iter++) + { + buckets_area[position * 2 + 1].emplace(iter->first, iter->second); // only use every second bucket beginning with 1 as this makes the parallel call later easier as we assume everything in a bucket i%2==0 is already processed + counter++; + if ((counter == elements_per_step && position >= over_elements) || counter > elements_per_step) + { + position++; + counter = 0; + } + } + + // precalculate the AABBs from the influence areas. + + cura::parallel_for(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) + { + // Some temporary storage, of elements that have to be inserted or removed from the background storage. Only one per two buckets required + std::vector> insert_main(buckets_area.size() / 2); + std::vector> insert_secondary(buckets_area.size() / 2); + std::vector> insert_influence(buckets_area.size() / 2); + std::vector> erase(buckets_area.size() / 2); + + + 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[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) + { + for (SupportElement& del : erase[i / 2]) + { + to_bp_areas.erase(del); + to_model_areas.erase(del); + influence_areas.erase(del); + } + + for (const std::pair& tup : insert_main[i / 2]) + { + to_bp_areas.emplace(tup); + } + + for (const std::pair& tup : insert_secondary[i / 2]) + { + to_model_areas.emplace(tup); + } + for (const std::pair& tup : insert_influence[i / 2]) + { + influence_areas.emplace(tup); + } + } + + 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()); + } +} + + +std::optional TreeSupport::increaseSingleArea(AreaIncreaseSettings settings, LayerIndex layer_idx, SupportElement* parent, const Polygons& relevant_offset, Polygons& to_bp_data, Polygons& to_model_data, Polygons& increased, const coord_t overspeed, const bool mergelayer) +{ + SupportElement current_elem(parent); // also increases DTT by one + Polygons check_layer_data; + if (settings.increase_radius) + { + current_elem.effective_radius_height += 1; + } + coord_t radius = config.getCollisionRadius(current_elem); + + if (settings.move) + { + increased = relevant_offset; + if (overspeed > 0) + { + const coord_t safe_movement_distance = (current_elem.use_min_xy_dist ? 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) + { + increased.simplify(25); // as ClipperLib::jtRound has to be used for offsets this simplify is VERY important for performance. + } + } + else // if no movement is done the areas keep parent area as no move == offset(0) + { + increased = *parent->area; + } + + if (mergelayer || current_elem.to_buildplate) + { + to_bp_data = safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); + if (!current_elem.to_buildplate && to_bp_data.area() > 1) // mostly happening in the tip, but with merges one should check every time, just to be sure. + { + current_elem.to_buildplate = true; // sometimes nodes that can reach the buildplate are marked as cant reach, tainting subtrees. This corrects it. + logDebug("Corrected taint leading to a wrong to model value on layer %lld targeting %lld with radius %lld\n", layer_idx - 1, current_elem.target_height, radius); + } + } + if (config.support_rests_on_model) + { + if (mergelayer || current_elem.to_model_gracious) + { + to_model_data = safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); + } + + if (!current_elem.to_model_gracious) + { + if (mergelayer && to_model_data.area() >= 1) + { + current_elem.to_model_gracious = true; + logDebug("Corrected taint leading to a wrong non gracious value on layer %lld targeting %lld with radius %lld\n", layer_idx - 1, current_elem.target_height, radius); + } + else + { + to_model_data = safeUnion(increased.difference(volumes_.getCollision(radius, layer_idx - 1, settings.use_min_distance))); + } + } + } + + check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; + + if (settings.increase_radius && check_layer_data.area() > 1) + { + std::function validWithRadius = [&](coord_t next_radius) + { + if (volumes_.ceilRadius(next_radius, settings.use_min_distance) <= volumes_.ceilRadius(radius, settings.use_min_distance)) + { + return true; + } + + Polygons to_bp_data_2; + if (current_elem.to_buildplate) + { + to_bp_data_2 = increased.difference(volumes_.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)).unionPolygons(); // regular union as output will not be used later => this area should always be a subset of the safeUnion one (i think) + } + Polygons to_model_data_2; + if (config.support_rests_on_model && !current_elem.to_buildplate) + { + if (!current_elem.to_model_gracious) + { + to_model_data_2 = increased.difference(volumes_.getCollision(next_radius, layer_idx - 1, settings.use_min_distance)).unionPolygons(); + } + else + { + to_model_data_2 = increased.difference(volumes_.getAvoidance(next_radius, layer_idx - 1, settings.type, true, settings.use_min_distance)).unionPolygons(); + } + } + Polygons check_layer_data_2 = current_elem.to_buildplate ? to_bp_data_2 : to_model_data_2; + + return check_layer_data_2.area() > 1; + }; + coord_t ceil_radius_before = volumes_.ceilRadius(radius, settings.use_min_distance); + + + if (config.getCollisionRadius(current_elem) < config.increase_radius_until_radius && config.getCollisionRadius(current_elem) < config.getRadius(current_elem)) + { + coord_t target_radius = std::min(config.getRadius(current_elem), config.increase_radius_until_radius); + coord_t current_ceil_radius = volumes_.getRadiusNextCeil(radius, settings.use_min_distance); + + while (current_ceil_radius < target_radius && validWithRadius(volumes_.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance))) + { + current_ceil_radius = volumes_.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance); + } + size_t resulting_eff_dtt = current_elem.effective_radius_height; + while (resulting_eff_dtt + 1 < current_elem.distance_to_top && config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= current_ceil_radius && config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= config.getRadius(current_elem)) + { + resulting_eff_dtt++; + } + current_elem.effective_radius_height = resulting_eff_dtt; + } + radius = config.getCollisionRadius(current_elem); + + const coord_t foot_radius_increase = config.branch_radius * (std::max(config.diameter_scale_bp_radius - config.diameter_angle_scale_factor, 0.0)); + double planned_foot_increase = std::min(1.0, double(config.recommendedMinRadius(layer_idx - 1) - config.getRadius(current_elem)) / foot_radius_increase); // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, which could cause the radius to become bigger than precalculated. + bool increase_bp_foot = planned_foot_increase > 0 && current_elem.to_buildplate; + + if (increase_bp_foot && config.getRadius(current_elem) >= config.branch_radius && config.getRadius(current_elem) >= config.increase_radius_until_radius) + { + if (validWithRadius(config.getRadius(current_elem.effective_radius_height, current_elem.elephant_foot_increases + planned_foot_increase))) + { + current_elem.elephant_foot_increases += planned_foot_increase; + radius = config.getCollisionRadius(current_elem); + } + } + + if (ceil_radius_before != volumes_.ceilRadius(radius, settings.use_min_distance)) + { + if (current_elem.to_buildplate) + { + to_bp_data = safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); + } + if (config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) + { + if (!current_elem.to_model_gracious) + { + to_model_data = safeUnion(increased.difference(volumes_.getCollision(radius, layer_idx - 1, settings.use_min_distance))); + } + else + { + to_model_data = safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); + } + } + check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; + if (check_layer_data.area() < 1) + { + logError("Lost area by doing catch up from %lld to radius %lld\n", ceil_radius_before, volumes_.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance)); + TreeSupport::showError("Area lost catching up radius. May not cause visible malformation.", true); + } + } + } + + return check_layer_data.area() > 1 ? std::optional(current_elem) : std::optional(); +} + + +void TreeSupport::increaseAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, std::vector& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, const bool mergelayer) +{ + 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_.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); + + // When the radius increases, the outer "support wall" of the branch will have been moved farther away from the center (as this is the definition of radius). + // As it is not specified that the support_tree_angle has to be one of the center of the branch, it is here seen as the smaller angle of the outer wall of the branch, to the outer wall of the same branch one layer above. + // As the branch may have become larger the distance between these 2 walls is smaller than the distance of the center points. + // These extra distance is added to the movement distance possible for this layer. + + coord_t extra_speed = 5; // The extra speed is added to both movement distances. Also move 5 microns faster than allowed to avoid rounding errors, this may cause issues at VERY VERY small layer heights. + coord_t extra_slow_speed = 0; // Only added to the slow movement distance. + const coord_t ceiled_parent_radius = 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 + extra_speed += projected_radius_delta; + } + else + { + // if a guaranteed radius increase is not possible, only increase the slow speed + extra_slow_speed += std::min(projected_radius_delta, (config.maximum_move_distance + extra_speed) - (config.maximum_move_distance_slow + extra_slow_speed)); // Ensure that the slow movement distance can not become larger than the fast one. + } + + if (config.layer_start_bp_radius > layer_idx && config.recommendedMinRadius(layer_idx - 1) < config.getRadius(elem.effective_radius_height + 1, elem.elephant_foot_increases)) + { + // can guarantee elephant foot radius increase + if (ceiled_parent_radius == volumes_.ceilRadius(config.getRadius(parent->effective_radius_height + 1, parent->elephant_foot_increases + 1), parent->use_min_xy_dist)) + { + extra_speed += config.branch_radius * config.diameter_scale_bp_radius; + } + else + { + extra_slow_speed += std::min(coord_t(config.branch_radius * config.diameter_scale_bp_radius), config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed)); + } + } + + const coord_t fast_speed = config.maximum_move_distance + extra_speed; + const coord_t slow_speed = config.maximum_move_distance_slow + extra_speed + extra_slow_speed; + + Polygons offset_slow, offset_fast; + + bool add = false; + bool bypass_merge = false; + constexpr bool increase_radius = true, no_error = true, use_min_radius = true, move = true; // aliases for better readability + + // Determine in which order configurations are checked if they result in a valid influence area. Check will stop if a valid area is found + std::deque order; + std::function insertSetting = [&](AreaIncreaseSettings settings, bool back) + { + if (std::find(order.begin(), order.end(), settings) == order.end()) + { + if (back) + { + order.emplace_back(settings); + } + else + { + order.emplace_front(settings); + } + } + }; + + const bool parent_moved_slow = elem.last_area_increase.increase_speed < config.maximum_move_distance; + const bool avoidance_speed_mismatch = parent_moved_slow && elem.last_area_increase.type != AvoidanceType::SLOW; + if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && !mergelayer && !avoidance_speed_mismatch && (elem.distance_to_top >= config.tip_layers || parent_moved_slow)) + { + // assume that the avoidance type that was best for the parent is best for me. Makes this function about 7% faster. + insertSetting(AreaIncreaseSettings(elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move), true); + insertSetting(AreaIncreaseSettings(elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, !increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move), true); + } + // branch may still go though a hole, so a check has to be done whether the hole was already passed, and the regular avoidance can be used. + if (!elem.can_use_safe_radius) + { + // if the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. + // order.emplace_back(AvoidanceType::SLOW,!increase_radius,no_error,!use_min_radius,move); + insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, !use_min_radius, !move), true); // did we go through the hole + // in many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. This CAN cause a branch to go though a hole it otherwise may have avoided. + if (elem.distance_to_top < round_up_divide(config.tip_layers, 2)) + { + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, slow_speed, increase_radius, no_error, !use_min_radius, !move), true); + } + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, !move), true); // did we manage to avoid the hole + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); + } + else + { + 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 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) + { + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, slow_speed, increase_radius, no_error, !use_min_radius, move), true); + } + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, move), true); // b + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); + } + + if (elem.use_min_xy_dist) + { + std::deque new_order; + // if the branch currently has to use min_xy_dist check if the configuration would also be valid with the regular xy_distance before checking with use_min_radius (Only happens when Support Distance priority is z overrides xy ) + for (AreaIncreaseSettings settings : order) + { + new_order.emplace_back(settings); + new_order.emplace_back(settings.type, settings.increase_speed, settings.increase_radius, settings.no_error, use_min_radius, settings.move); + } + order = new_order; + } + if (elem.to_buildplate || (elem.to_model_gracious && (parent->area->intersection(volumes_.getPlaceableAreas(radius, layer_idx)).empty()))) // error case + { + // it is normal that we wont be able to find a new area at some point in time if we wont be able to reach layer 0 aka have to connect with the model + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move), true); + } + if (elem.distance_to_top < elem.dont_move_until && elem.can_use_safe_radius) // only do not move when holes would be avoided in every case. + { + insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, 0, increase_radius, no_error, !use_min_radius, !move), false); // Only do not move when already in a no hole avoidance with the regular xy distance. + } + + 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) + { + if (settings.move) + { + 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()) + { + 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 = 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); + showError("Potentially lost branch!", true); + } + else + { + result = increaseSingleArea(settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); + } + + if (result) + { + elem = result.value(); + radius = config.getCollisionRadius(elem); + elem.last_area_increase = settings; + add = true; + bypass_merge = !settings.move || (settings.use_min_distance && elem.distance_to_top < config.tip_layers); // do not merge if the branch should not move or the priority has to be to get farther away from the model. + if (settings.move) + { + elem.dont_move_until = 0; + } + else + { + elem.result_on_layer = parent->result_on_layer; + } + + elem.can_use_safe_radius = settings.type != AvoidanceType::FAST; + + if (!settings.use_min_distance) + { + elem.use_min_xy_dist = false; + } + if (!settings.no_error) + { + logError("Trying to keep area by moving faster than intended: Success \n"); + } + break; + } + else if (!settings.no_error) + { + logError("Trying to keep area by moving faster than intended: FAILURE! WRONG BRANCHES LIKLY! \n"); + } + } + + if (add) + { + 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 + + { + std::lock_guard critical_section_newLayer(critical_sections); + if (bypass_merge) + { + Polygons* new_area = new Polygons(max_influence_area); + SupportElement* next = new SupportElement(elem, new_area); + bypass_merge_areas.emplace_back(next); + } + else + { + influence_areas.emplace(elem, max_influence_area); + if (elem.to_buildplate) + { + to_bp_areas.emplace(elem, to_bp_data); + } + if (config.support_rests_on_model) + { + to_model_areas.emplace(elem, to_model_data); + } + } + } + } + else + { + parent->result_on_layer = Point(-1, -1); // If the bottom most point of a branch is set, later functions will assume that the position is valid, and ignore it. But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. A point can be set on the top most tip layer (maybe more if it should not move for a few layers). + } + }); +} + + +void TreeSupport::createLayerPathing(std::vector>& move_bounds) +{ + const double data_size_inverse = 1 / double(move_bounds.size()); + double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES; + + auto dur_inc = std::chrono::duration_values::zero(); + auto dur_merge = std::chrono::duration_values::zero(); + + auto dur_inc_recent = std::chrono::duration_values::zero(); + auto dur_merge_recent = std::chrono::duration_values::zero(); + + LayerIndex last_merge = move_bounds.size(); + bool new_element = false; + + size_t max_merge_every_x_layers = std::min(std::min(5000 / (std::max(config.maximum_move_distance, coord_t(100))), 1000 / std::max(config.maximum_move_distance_slow, coord_t(20))), 3000 / config.layer_height); // Ensures at least one merge operation per 3mm height, 50 layers, 1 mm movement of slow speed or 5mm movement of fast speed (whatever is lowest). Values were guessed. + size_t merge_every_x_layers = 1; + // Calculate the influence areas for each layer below (Top down) + // This is done by first increasing the influence area by the allowed movement distance, and merging them with other influence areas if possible + for (LayerIndex layer_idx = move_bounds.size() - 1; layer_idx > 0; layer_idx--) + { + // merging is expensive and only parallelized to a max speedup of 2. As such it may be useful in some cases to only merge every few layers to improve performance. + bool merge_this_layer = size_t(last_merge - layer_idx) >= merge_every_x_layers; + + if (new_element) + { + merge_this_layer = true; + merge_every_x_layers = 1; + } + + std::map influence_areas; // Over this map will be iterated when merging, as such it has to be ordered to ensure deterministic results. + std::unordered_map to_bp_areas, to_model_areas; // The area of these SupportElement is not set, to avoid to much allocation and deallocation on the heap + std::vector bypass_merge_areas; // Different to the other maps of SupportElements as these here have the area already set, as they are already to be inserted into move_bounds. + + auto ta = std::chrono::high_resolution_clock::now(); + + std::vector last_layer; + last_layer.insert(last_layer.begin(), move_bounds[layer_idx].begin(), move_bounds[layer_idx].end()); + + // ### Increase the influence areas by the allowed movement distance + increaseAreas(to_bp_areas, to_model_areas, influence_areas, bypass_merge_areas, last_layer, layer_idx, merge_this_layer); + + auto tb = std::chrono::high_resolution_clock::now(); + if (merge_this_layer) + { + bool reduced_by_merging = false; + size_t count_before_merge = influence_areas.size(); + // ### Calculate which influence areas overlap, and merge them into a new influence area (simplified: an intersection of influence areas that have such an intersection) + mergeInfluenceAreas(to_bp_areas, to_model_areas, influence_areas, layer_idx); + + last_merge = layer_idx; + reduced_by_merging = count_before_merge > influence_areas.size(); + if (!reduced_by_merging && !new_element) + { + merge_every_x_layers = std::min(max_merge_every_x_layers, merge_every_x_layers + 1); + } + } + auto tc = std::chrono::high_resolution_clock::now(); + + dur_inc += tb - ta; + dur_merge += tc - tb; + + new_element = !move_bounds[layer_idx - 1].empty(); + + // Save calculated elements to output, and allocate Polygons on heap, as they will not be changed again. + for (std::pair tup : influence_areas) + { + const SupportElement elem = tup.first; + Polygons* new_area = new Polygons(safeUnion(tup.second)); + SupportElement* next = new SupportElement(elem, new_area); + move_bounds[layer_idx - 1].emplace(next); + + if (new_area->area() < 1) + { + logError("Insert Error of Influence area on layer %lld. Origin of %lld areas. Was to bp %d\n", layer_idx - 1, elem.parents.size(), elem.to_buildplate); + TreeSupport::showError("Insert error of area after merge.\n", true); + } + } + + // Place already fully constructed elements in the output. + for (SupportElement* elem : bypass_merge_areas) + { + if (elem->area->area() < 1) + { + logError("Insert Error of Influence area bypass on layer %lld.\n", layer_idx - 1); + TreeSupport::showError("Insert error of area after bypassing merge.\n", true); + + } + move_bounds[layer_idx - 1].emplace(elem); + } + + 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); +} + + +void TreeSupport::setPointsOnAreas(const SupportElement* elem) +{ + // Based on the branch center point of the current layer, the point on the next (further up) layer is calculated. + + if (elem->result_on_layer == Point(-1, -1)) + { + logError("Uninitialized support element\n"); + TreeSupport::showError("Uninitialized support element. A branch may be missing.\n", true); + return; + } + + for (SupportElement* next_elem : elem->parents) + { + if (next_elem->result_on_layer != Point(-1, -1)) // if the value was set somewhere else it it kept. This happens when a branch tries not to move after being unable to create a roof. + { + continue; + } + + Point from = elem->result_on_layer; + if (!(next_elem->area->inside(from, true))) + { + PolygonUtils::moveInside(*next_elem->area, from, 0); // Move inside has edgecases (see tests) so DONT use Polygons.inside to confirm correct move, Error with distance 0 is <= 1 + // it is not required to check if how far this move moved a point as is can be larger than maximum_movement_distance. While this seems like a problem it may for example occur after merges. + } + next_elem->result_on_layer = from; + // do not call recursive because then amount of layers would be restricted by the stack size + } +} + +bool TreeSupport::setToModelContact(std::vector>& move_bounds, SupportElement* first_elem, const LayerIndex layer_idx) +{ + if (first_elem->to_model_gracious) + { + SupportElement* check = first_elem; + + std::vector checked; + LayerIndex last_successfull_layer = layer_idx; + bool set = false; + + // check for every layer upwards, up to the point where this influence area was created (either by initial insert or merge) if the branch could be placed on it, and highest up layer index. + + for (LayerIndex layer_check = layer_idx; check->next_height >= layer_check; layer_check++) + { + if (!check->area->intersection(volumes_.getPlaceableAreas(config.getCollisionRadius(*check), layer_check)).empty()) + { + set = true; + last_successfull_layer = layer_check; + } + checked.emplace_back(check); + if (check->parents.size() == 1) + { + check = check->parents[0]; + } + else + { + break; // reached merge point + } + } + + // Could not find valid placement, even though it should exist => error handling + if (!set) + { + if (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL) + { + logWarning("No valid placement found for to model gracious element on layer %lld: REMOVING BRANCH\n", layer_idx); + TreeSupport::showError("Could not fine valid placement on model! Removing this branch...", true); + for (LayerIndex layer = layer_idx; layer <= first_elem->next_height; layer++) + { + move_bounds[layer].erase(checked[layer - layer_idx]); + delete checked[layer - layer_idx]->area; + delete checked[layer - layer_idx]; + } + } + else + { + logWarning("No valid placement found for to model gracious element on layer %lld\n", layer_idx); + TreeSupport::showError("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches.", true); + first_elem->to_model_gracious = false; + return setToModelContact(move_bounds, first_elem, layer_idx); + } + } + + for (LayerIndex layer = layer_idx + 1; layer < last_successfull_layer - 1; layer++) + { + move_bounds[layer].erase(checked[layer - layer_idx]); + delete checked[layer - layer_idx]->area; + delete checked[layer - layer_idx]; + } + + // Guess a point inside the influence area, in which the branch will be placed in. + Point best = checked[last_successfull_layer - layer_idx]->next_position; + if (!checked[last_successfull_layer - layer_idx]->area->inside(best, true)) + { + PolygonUtils::moveInside(*checked[last_successfull_layer - layer_idx]->area, best); + } + checked[last_successfull_layer - layer_idx]->result_on_layer = best; + + logDebug("Added gracious Support On Model Point (%lld,%lld). The current layer is %lld\n", best.X, best.Y, last_successfull_layer); + + return last_successfull_layer != layer_idx; + } + else // can not add graceful => just place it here and hope for the best + { + Point best = first_elem->next_position; + if (!first_elem->area->inside(best, true)) + { + PolygonUtils::moveInside(*first_elem->area, best); + } + first_elem->result_on_layer = best; + first_elem->to_model_gracious = false; + logDebug("Added NON gracious Support On Model Point (%lld,%lld). The current layer is %lld\n", best.X, best.Y, layer_idx); + return false; + } +} + + +void TreeSupport::createNodesFromArea(std::vector>& move_bounds) +{ + // Initialize points on layer 0, with a "random" point in the influence area. Point is chosen based on an inaccurate estimate where the branches will split into two, but every point inside the influence area would produce a valid result. + for (SupportElement* init : move_bounds[0]) + { + Point p = init->next_position; + if (!(init->area->inside(p, true))) + { + PolygonUtils::moveInside(*init->area, p, 0); + } + init->result_on_layer = p; + + setPointsOnAreas(init); // also set the parent nodes, as these will be required for the first iteration of the loop below + } + + + for (LayerIndex layer_idx = 1; layer_idx < LayerIndex(move_bounds.size()); layer_idx++) + { + std::unordered_set remove; + for (SupportElement* elem : move_bounds[layer_idx]) + { + bool removed = false; + if (elem->result_on_layer == Point(-1, -1)) // check if the resulting center point is not yet set + { + if (elem->to_buildplate || (!elem->to_buildplate && elem->distance_to_top < config.min_dtt_to_model && !elem->supports_roof)) + { + if (elem->to_buildplate) + { + logError("Uninitialized Influence area targeting (%lld,%lld) at target_height: %lld layer: %lld\n", elem->target_position.X, elem->target_position.Y, elem->target_height, layer_idx); + TreeSupport::showError("Uninitialized support element! A branch could be missing or exist partially.", true); + } + remove.emplace(elem); // we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set + removed = true; + for (SupportElement* parent : elem->parents) + { + parent->result_on_layer = Point(-1, -1); // When the roof was not able to generate downwards enough, the top elements may have not moved, and have result_on_layer already set. As this branch needs to be removed => all parents result_on_layer have to be invalidated. + } + continue; + } + else + { + // set the point where the branch will be placed on the model + removed = setToModelContact(move_bounds, elem, layer_idx); + if (removed) + { + remove.emplace(elem); + } + } + } + + if (!removed) + { + setPointsOnAreas(elem); // element is valid now setting points in the layer above + } + } + + // delete all not needed support elements + for (SupportElement* del : remove) + { + move_bounds[layer_idx].erase(del); + delete del->area; + delete del; + } + remove.clear(); + } +} + +void TreeSupport::generateBranchAreas(std::vector>& linear_data, std::vector>& layer_tree_polygons, const std::map& inverse_tree_order) +{ + 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(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; + + 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 = 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 (!elem->skip_ovalisation) + { + 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; + std::function generateArea = [&](coord_t offset) + { + Polygons poly; + + for (std::pair movement : movement_directions) + { + max_speed = std::max(max_speed, vSize(movement.first)); + + // Visualization: https://jsfiddle.net/0zvcq39L/2/ + // Ovalizes the circle to an ellipse, that contains both old center and new target position. + double used_scale = (movement.second + offset) / (1.0 * config.branch_radius); + Point center_position = elem->result_on_layer + movement.first / 2; + const double moveX = movement.first.X / (used_scale * config.branch_radius); + const double moveY = movement.first.Y / (used_scale * config.branch_radius); + const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); + + double matrix[] = { + used_scale * (1 + moveX * moveX * vsize_inv), + used_scale * (0 + moveX * moveY * vsize_inv), + used_scale * (0 + moveX * moveY * vsize_inv), + used_scale * (1 + moveY * moveY * vsize_inv), + }; + Polygon circle; + for (Point vertex : branch_circle) + { + vertex = Point(matrix[0] * vertex.X + matrix[1] * vertex.Y, matrix[2] * vertex.X + matrix[3] * vertex.Y); + circle.add(center_position + vertex); + } + poly.add(circle.offset(0)); + } + + 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; + }; + + + bool fast_relative_movement = max_speed > 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[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[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[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) + { + Polygons polygons_with_correct_center; + for (PolygonsPart part : nozzle_path.splitIntoParts(false)) + { + if (part.inside(elem->result_on_layer, true)) + { + polygons_with_correct_center = polygons_with_correct_center.unionPolygons(part); + } + else + { + // try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... + Point from = elem->result_on_layer; + PolygonUtils::moveInside(part, from, 0); + if (vSize(elem->result_on_layer - from) < 25) + { + polygons_with_correct_center = polygons_with_correct_center.unionPolygons(part); + } + } + } + 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 (idx % progress_inserts_check_interval == 0) + { + { + 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++) + { + layer_tree_polygons[linear_data[i].first].emplace(linear_data[i].second, linear_inserts[i]); + } +} + +void TreeSupport::smoothBranchAreas(std::vector>& layer_tree_polygons) +{ + 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 + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()) - 1; layer_idx++) + { + 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 + 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; + bool do_something = false; + for (SupportElement* parent : data_pair.first->parents) + { + if (config.getRadius(*parent) != config.getCollisionRadius(*parent)) + { + do_something = true; + max_outer_wall_distance = std::max(max_outer_wall_distance, vSize(data_pair.first->result_on_layer - parent->result_on_layer) - (config.getRadius(*data_pair.first) - config.getRadius(*parent))); + } + } + max_outer_wall_distance += max_radius_change_per_layer; // As this change is a bit larger than what usually appears, lost radius can be slowly reclaimed over the layers. + if (do_something) + { + Polygons max_allowed_area = data_pair.second.offset(max_outer_wall_distance); + for (SupportElement* parent : data_pair.first->parents) + { + if (config.getRadius(*parent) != config.getCollisionRadius(*parent)) + { + update_next[processing_idx].emplace_back(std::pair(parent, layer_tree_polygons[layer_idx + 1][parent].intersection(max_allowed_area))); + } + } + } + }); + + for (std::vector> data_vector : update_next) + { + for (std::pair data_pair : data_vector) + { + layer_tree_polygons[layer_idx + 1][data_pair.first] = data_pair.second; + } + } + } + + progress_total += TREE_PROGRESS_SMOOTH_BRANCH_AREAS / 2; + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); // It is just assumed that both smoothing loops together are one third of the time spent in this function. This was guessed. As the whole function is only 10%, and the smoothing is hard to predict a progress report in the loop may be not useful. + + // smooth downwards + std::unordered_set updated_last_iteration; + for (LayerIndex layer_idx = layer_tree_polygons.size() - 2; layer_idx >= 0; layer_idx--) + { + 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 + + 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; + for (size_t idx = 0; idx < data_pair.first->parents.size(); idx++) + { + SupportElement* parent = data_pair.first->parents[idx]; + coord_t max_outer_line_increase = max_radius_change_per_layer; + Polygons result = layer_tree_polygons[layer_idx + 1][parent].offset(max_outer_line_increase); + Point direction = data_pair.first->result_on_layer - parent->result_on_layer; + // move the polygons object + for (auto& outer : result) + { + for (Point& p : outer) + { + p += direction; + } + } + max_allowed_area.add(result); + do_something = do_something || updated_last_iteration.count(parent) || config.getCollisionRadius(*parent) != config.getRadius(*parent); + } + + if (do_something) + { + Polygons result = max_allowed_area.unionPolygons().intersection(data_pair.second); + if (result.area() < data_pair.second.area()) + { + update_next[processing_idx] = std::pair(data_pair.first, result); + } + } + }); + + updated_last_iteration.clear(); + for (std::pair data_pair : update_next) + { + if (data_pair.first != nullptr) + { + updated_last_iteration.emplace(data_pair.first); + layer_tree_polygons[layer_idx][data_pair.first] = data_pair.second; + } + } + } + + progress_total += TREE_PROGRESS_SMOOTH_BRANCH_AREAS / 2; + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); +} + + +void TreeSupport::dropNonGraciousAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, const std::map& inverse_tree_order) +{ + 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 && !inverse_tree_order.count(elem); // if a element has no child, it connects to whatever is below as no support further down for it will exist. + + if (non_gracious_model_contact) + { + Polygons rest_support = layer_tree_polygons[linear_data[idx].first][elem]; + LayerIndex counter = 1; + while (rest_support.area() > 1 && counter < linear_data[idx].first) + { + rest_support = rest_support.difference(volumes_.getCollision(0, linear_data[idx].first - counter)); + dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support); + counter++; + } + } + }); +} + + +void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& support_layer_storage, std::vector& support_roof_storage, SliceDataStorage& storage) +{ + 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. + 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 + storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(support_roof_storage[layer_idx]); + if (!storage.support.supportLayers[layer_idx].support_roof.empty() && support_layer_storage[layer_idx].intersection(storage.support.supportLayers[layer_idx].support_roof).area() > 1) + { + switch (interface_pref) + { + 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 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 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 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)); + storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.difference(tree_lines); // do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. + } + + break; + case InterfacePreference::NOTHING: + break; + } + } + + // Subtract support floors from the support area and add them to the support floor instead. + if (config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) + { + Polygons floor_layer = storage.support.supportLayers[layer_idx].support_bottom; + Polygons layer_outset = support_layer_storage[layer_idx].offset(config.support_bottom_offset).difference(volumes_.getCollision(0, layer_idx, false)); + size_t layers_below = 0; + while (layers_below <= config.support_bottom_layers) + { + // one sample at 0 layers below, another at config.support_bottom_layers. In-between samples at config.performance_interface_skip_layers distance from each other. + const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(config.z_distance_bottom_layers))); + constexpr bool no_support = false; + constexpr bool no_prime_tower = false; + floor_layer.add(layer_outset.intersection(storage.getLayerOutlines(sample_layer, no_support, no_prime_tower))); + if (layers_below < config.support_bottom_layers) + { + layers_below = std::min(layers_below + config.performance_interface_skip_layers, config.support_bottom_layers); + } + else + { + break; + } + } + floor_layer = floor_layer.unionPolygons(); + storage.support.supportLayers[layer_idx].support_bottom = storage.support.supportLayers[layer_idx].support_bottom.unionPolygons(floor_layer); + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(floor_layer.offset(10)); // Subtract the support floor from the normal support. + } + + for (PolygonsPart part : support_layer_storage[layer_idx].splitIntoParts(true)) // Convert every part into a PolygonsPart for the support. + { + storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(part, config.support_line_width, config.support_wall_count); + } + + { + 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); + } + + { + 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) +{ + std::vector support_layer_storage(move_bounds.size()); + std::vector support_roof_storage(move_bounds.size()); + std::map inverese_tree_order; // in the tree structure only the parents can be accessed. Inverse this to be able to access the children. + std::vector> linear_data; // All SupportElements are put into a layer independent storage to improve parallelization. Was added at a point in time where this function had performance issues. + // These were fixed by creating less initial points, but i do not see a good reason to remove a working performance optimization. + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); layer_idx++) + { + for (SupportElement* elem : move_bounds[layer_idx]) + { + if ((layer_idx > 0 && ((!inverese_tree_order.count(elem) && elem->target_height == layer_idx) || (inverese_tree_order.count(elem) && inverese_tree_order[elem]->result_on_layer == Point(-1, -1))))) // we either come from nowhere at the final layer or we had invalid parents 2. should never happen but just to be sure + { + continue; + } + + for (SupportElement* par : elem->parents) + { + if (par->result_on_layer == Point(-1, -1)) + { + continue; + } + inverese_tree_order.emplace(par, elem); + } + linear_data.emplace_back(layer_idx, elem); + } + } + + std::vector> layer_tree_polygons(move_bounds.size()); // reorder the processed data by layers again. The map also could be a vector>. + auto t_start = std::chrono::high_resolution_clock::now(); + // Generate the circles that will be the branches. + generateBranchAreas(linear_data, layer_tree_polygons, inverese_tree_order); + auto t_generate = std::chrono::high_resolution_clock::now(); + // In some edgecases a branch may go though a hole, where the regular radius does not fit. This can result in an apparent jump in branch radius. As such this cases need to be caught and smoothed out. + smoothBranchAreas(layer_tree_polygons); + auto t_smooth = std::chrono::high_resolution_clock::now(); + // drop down all trees that connect non gracefully with the model + std::vector>> dropped_down_areas(linear_data.size()); + dropNonGraciousAreas(layer_tree_polygons, linear_data, dropped_down_areas, inverese_tree_order); + auto t_drop = std::chrono::high_resolution_clock::now(); + // single threaded combining all dropped down support areas to the right layers. ONLY COPYS DATA! + for (coord_t i = 0; i < static_cast(dropped_down_areas.size()); i++) + { + for (std::pair pair : dropped_down_areas[i]) + { + support_layer_storage[pair.first].add(pair.second); + } + } + + // single threaded combining all support areas to the right layers. ONLY COPYS DATA! + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()); layer_idx++) + { + for (std::pair data_pair : layer_tree_polygons[layer_idx]) + { + if (data_pair.first->missing_roof_layers > data_pair.first->distance_to_top) + { + support_roof_storage[layer_idx].add(data_pair.second); + } + else + { + support_layer_storage[layer_idx].add(data_pair.second); + } + } + } + + finalizeInterfaceAndSupportAreas(support_layer_storage, support_roof_storage, storage); + auto t_end = std::chrono::high_resolution_clock::now(); + + auto dur_gen_tips = 0.001 * std::chrono::duration_cast(t_generate - t_start).count(); + auto dur_smooth = 0.001 * std::chrono::duration_cast(t_smooth - t_generate).count(); + auto dur_drop = 0.001 * std::chrono::duration_cast(t_drop - t_smooth).count(); + auto dur_finalize = 0.001 * std::chrono::duration_cast(t_end - t_drop).count(); + + log("Time used for drawing subfuctions: generateBranchAreas: %.3lf ms smoothBranchAreas: %.3lf ms dropNonGraciousAreas: %.3lf ms finalizeInterfaceAndSupportAreas %.3lf ms\n", dur_gen_tips, dur_smooth, dur_drop, dur_finalize); +} + + + +} // namespace cura diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp new file mode 100644 index 000000000..edc25d305 --- /dev/null +++ b/src/libslic3r/TreeSupport.hpp @@ -0,0 +1,1029 @@ +// Copyright (c) 2017 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#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" + + +#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 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 + +#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 +{ + + +/*! + * \brief Generates a tree structure to support your models. + */ + +class TreeSupport +{ + public: + using AvoidanceType = TreeModelVolumes::AvoidanceType; + enum class InterfacePreference + { + INTERFACE_AREA_OVERWRITES_SUPPORT, + SUPPORT_AREA_OVERWRITES_INTERFACE, + INTERFACE_LINES_OVERWRITE_SUPPORT, + SUPPORT_LINES_OVERWRITE_INTERFACE, + NOTHING + }; + + /*! + * \brief Creates an instance of the tree support generator. + * + * \param storage The data storage to get global settings from. + */ + TreeSupport(const SliceDataStorage& storage); + + /*! + * \brief Create the areas that need support. + * + * These areas are stored inside the given SliceDataStorage object. + * \param storage The data storage where the mesh data is gotten from and + * where the resulting support areas are stored. + */ + void generateSupportAreas(SliceDataStorage& storage); + + + //todo Remove! Only relevant for public BETA! + static bool inline showed_critical=false; + static bool inline showed_performance=false; + static void showError(std::string message,bool critical); + + struct TreeSupportSettings; // forward declaration as we need some config values in the merge case + + struct AreaIncreaseSettings + { + AreaIncreaseSettings() : type(AvoidanceType::FAST), increase_speed(0), increase_radius(false), no_error(false), use_min_distance(false), move(false) + { + } + + AreaIncreaseSettings(AvoidanceType type, coord_t increase_speed, bool increase_radius, bool simplify, bool use_min_distance, bool move) : type(type), increase_speed(increase_speed), increase_radius(increase_radius), no_error(simplify), use_min_distance(use_min_distance), move(move) + { + } + + AvoidanceType type; + coord_t increase_speed; + bool increase_radius; + bool no_error; + bool use_min_distance; + bool move; + bool operator==(const AreaIncreaseSettings& other) const + { + return increase_radius == other.increase_radius && increase_speed == other.increase_speed && type == other.type && no_error == other.no_error && use_min_distance == other.use_min_distance && move == other.move; + } + }; + + struct SupportElement + { + 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) + { + } + + + SupportElement(const SupportElement& elem, Polygons* newArea = nullptr) + : // copy constructor with possibility to set a new area + target_height(elem.target_height), + target_position(elem.target_position), + next_position(elem.next_position), + next_height(elem.next_height), + effective_radius_height(elem.effective_radius_height), + to_buildplate(elem.to_buildplate), + distance_to_top(elem.distance_to_top), + area(newArea != nullptr ? newArea : elem.area), + result_on_layer(elem.result_on_layer), + increased_to_model_radius(elem.increased_to_model_radius), + to_model_gracious(elem.to_model_gracious), + elephant_foot_increases(elem.elephant_foot_increases), + use_min_xy_dist(elem.use_min_xy_dist), + supports_roof(elem.supports_roof), + dont_move_until(elem.dont_move_until), + can_use_safe_radius(elem.can_use_safe_radius), + last_area_increase(elem.last_area_increase), + missing_roof_layers(elem.missing_roof_layers), + skip_ovalisation(elem.skip_ovalisation) + + { + parents.insert(parents.begin(), elem.parents.begin(), elem.parents.end()); + } + + /*! + * \brief Create a new Element for one layer below the element of the pointer supplied. + */ + + SupportElement(SupportElement* element_above) + : target_height(element_above->target_height), + target_position(element_above->target_position), + next_position(element_above->next_position), + next_height(element_above->next_height), + effective_radius_height(element_above->effective_radius_height), + to_buildplate(element_above->to_buildplate), + distance_to_top(element_above->distance_to_top + 1), + area(element_above->area), + result_on_layer(Point(-1, -1)), // set to invalid as we are a new node on a new layer + increased_to_model_radius(element_above->increased_to_model_radius), + to_model_gracious(element_above->to_model_gracious), + elephant_foot_increases(element_above->elephant_foot_increases), + use_min_xy_dist(element_above->use_min_xy_dist), + supports_roof(element_above->supports_roof), + dont_move_until(element_above->dont_move_until), + can_use_safe_radius(element_above->can_use_safe_radius), + last_area_increase(element_above->last_area_increase), + missing_roof_layers(element_above->missing_roof_layers), + skip_ovalisation(false) + { + parents = { element_above }; + } + + // ONLY to be called in merge as it assumes a few assurances made by it. + 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) + { + target_height = first.target_height; + target_position = first.target_position; + } + else + { + target_height = second.target_height; + target_position = second.target_position; + } + effective_radius_height = std::max(first.effective_radius_height, second.effective_radius_height); + distance_to_top = std::max(first.distance_to_top, second.distance_to_top); + + to_buildplate = first.to_buildplate && second.to_buildplate; + to_model_gracious = first.to_model_gracious && second.to_model_gracious; // valid as we do not merge non-gracious with gracious + + AddParents(first.parents); + AddParents(second.parents); + + elephant_foot_increases = 0; + if (config.diameter_scale_bp_radius > 0) + { + coord_t foot_increase_radius = std::abs(std::max(config.getCollisionRadius(second), config.getCollisionRadius(first)) - config.getCollisionRadius(*this)); + elephant_foot_increases = foot_increase_radius / (config.branch_radius * (config.diameter_scale_bp_radius - config.diameter_angle_scale_factor)); // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. + } + + + // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. + last_area_increase = AreaIncreaseSettings(std::min(first.last_area_increase.type, second.last_area_increase.type), std::min(first.last_area_increase.increase_speed, second.last_area_increase.increase_speed), first.last_area_increase.increase_radius || second.last_area_increase.increase_radius, first.last_area_increase.no_error || second.last_area_increase.no_error, first.last_area_increase.use_min_distance && second.last_area_increase.use_min_distance, first.last_area_increase.move || second.last_area_increase.move); + } + + /*! + * \brief The layer this support elements wants reach + */ + LayerIndex target_height; + + /*! + * \brief The position this support elements wants to support on layer=target_height + */ + Point target_position; + + /*! + * \brief The next position this support elements wants to reach. NOTE: This is mainly a suggestion regarding direction inside the influence area. + */ + Point next_position; + + + /*! + * \brief The next height this support elements wants to reach + */ + LayerIndex next_height; + + /*! + * \brief The Effective distance to top of this element regarding radius increases and collision calculations. + */ + + size_t effective_radius_height; + + /*! + * \brief The element trys to reach the buildplate + */ + + bool to_buildplate; + + /*! + * \brief All elements in the layer above the current one that are supported by this element + */ + std::vector parents; + + /*! + * \brief The amount of layers this element is below the topmost layer of this branch. + */ + size_t distance_to_top; + + /*! + * \brief The resulting influence area. + * Will only be set in the results of createLayerPathing, and will be nullptr inside! + */ + Polygons* area; + + /*! + * \brief The resulting center point around which a circle will be drawn later. + * Will be set by setPointsOnAreas + */ + Point result_on_layer = Point(-1, -1); + /*! + * \brief The amount of extra radius we got from merging branches that could have reached the buildplate, but merged with ones that can not. + */ + coord_t increased_to_model_radius; // how much to model we increased only relevant for merging + /*! + * \brief Will the branch be able to rest completely on a flat surface, be it buildplate or model ? + */ + bool to_model_gracious; + + /*! + * \brief Counter about the times the elephant foot was increased. Can be fractions for merge reasons. + */ + double elephant_foot_increases; + + /*! + * \brief Whether the min_xy_distance can be used to get avoidance or similar. Will only be true if support_xy_overrides_z=Z overrides X/Y. + */ + bool use_min_xy_dist; + + /*! + * \brief True if this Element or any parent provides support to a support roof. + */ + bool supports_roof; + + /*! + * \brief The element trys not to move until this dtt is reached, is set to 0 if the element had to move. + */ + size_t dont_move_until; + + /*! + * \brief An influence area is considered safe when it can use the holefree avoidance <=> It will not have to encounter holes on its way downward. + */ + bool can_use_safe_radius; + + /*! + * \brief Settings used to increase the influence area to its current state. + */ + AreaIncreaseSettings last_area_increase; + + /*! + * \brief Amount of roof layers that were not yet added, because the branch needed to move. + */ + size_t missing_roof_layers; + + /*! + * \brief Skip the ovalisation to parent and children when generating the final circles. + */ + bool skip_ovalisation; + + bool operator==(const SupportElement& other) const + { + return target_position == other.target_position && target_height == other.target_height; + } + + bool operator<(const SupportElement& other) const // true if me < other + { + return !(*this == other) && !(*this > other); + } + bool operator>(const SupportElement& other) const + { + // Doesn't really have to make sense, only required for ordering in maps to ensure deterministic behavior. + if (*this == other) + return false; + if (other.target_height != target_height) + { + return other.target_height < target_height; + } + + return other.target_position.X == target_position.X ? other.target_position.Y < target_position.Y : other.target_position.X < target_position.X; + } + + void AddParents(const std::vector& adding) + { + for (SupportElement* ptr : adding) + { + parents.emplace_back(ptr); + } + } + }; + + /*! + * \brief This struct contains settings used in the tree support. Thanks to this most functions do not need to know of meshes etc. Also makes the code shorter. + */ + struct TreeSupportSettings + { + TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupport class. + + TreeSupportSettings(const Settings& mesh_group_settings) + : angle(mesh_group_settings.get("support_tree_angle")), + angle_slow(mesh_group_settings.get("support_tree_angle_slow")), + support_line_width(mesh_group_settings.get("support_line_width")), + layer_height(mesh_group_settings.get("layer_height")), + branch_radius(mesh_group_settings.get("support_tree_branch_diameter") / 2), + min_radius(mesh_group_settings.get("support_tree_tip_diameter") / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance + maximum_move_distance((angle < 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)), // 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)), + increase_radius_until_radius(mesh_group_settings.get("support_tree_branch_diameter") / 2), + increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / (branch_radius * diameter_angle_scale_factor)), + support_rests_on_model(mesh_group_settings.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. + 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)), + z_distance_bottom_layers(round_up_divide(mesh_group_settings.get("support_bottom_distance"), layer_height)), + performance_interface_skip_layers(round_up_divide(mesh_group_settings.get("support_interface_skip_height"), layer_height)), + support_infill_angles(mesh_group_settings.get>("support_infill_angles")), + support_roof_angles(mesh_group_settings.get>("support_roof_angles")), + roof_pattern(mesh_group_settings.get("support_roof_pattern")), + support_pattern(mesh_group_settings.get("support_pattern")), + 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")), + 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_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")), + 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 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. + if (has_to_rely_on_min_xy_dist_only) + { + 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()) + { + if (pattern == EFillMethod::CONCENTRIC) + { + angles.push_back(0); // Concentric has no rotation. + } + else if (pattern == EFillMethod::TRIANGLES) + { + angles.push_back(90); // Triangular support interface shouldn't alternate every layer. + } + else + { + if (TreeSupportSettings::some_model_contains_thick_roof) + { + // Some roofs are quite thick. + // Alternate between the two kinds of diagonal: / and \ . + angles.push_back(45); + angles.push_back(135); + } + if (angles.empty()) + { + angles.push_back(90); // Perpendicular to support lines. + } + } + } + }; + + getInterfaceAngles(support_infill_angles, support_pattern); + getInterfaceAngles(support_roof_angles, roof_pattern); + 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: + double angle; + double angle_slow; + std::vector known_z; + + public: + // some static variables dependent on other meshes that are not currently processed. + // Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy. + inline static bool 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. + */ + coord_t support_line_width; + /*! + * \brief Height of a single layer + */ + coord_t layer_height; + /*! + * \brief Radius of a branch when it has left the tip. + */ + coord_t branch_radius; + /*! + * \brief smallest allowed radius, required to ensure that even at DTT 0 every circle will still be printed + */ + coord_t min_radius; + /*! + * \brief How far an influence area may move outward every layer at most. + */ + coord_t maximum_move_distance; + /*! + * \brief How far every influence area will move outward every layer if possible. + */ + coord_t maximum_move_distance_slow; + /*! + * \brief Amount of bottom layers. 0 if disabled. + */ + size_t support_bottom_layers; + /*! + * \brief Amount of effectiveDTT increases are required to reach branch radius. + */ + size_t tip_layers; + /*! + * \brief Factor by which to increase the branch radius. + */ + double diameter_angle_scale_factor; + /*! + * \brief How much a branch resting on the model may grow in radius by merging with branches that can reach the buildplate. + */ + coord_t max_to_model_radius_increase; + /*! + * \brief If smaller (in layers) than that, all branches to model will be deleted + */ + size_t min_dtt_to_model; + /*! + * \brief Increase radius in the resulting drawn branches, even if the avoidance does not allow it. Will be cut later to still fit. + */ + coord_t increase_radius_until_radius; + /*! + * \brief Same as increase_radius_until_radius, but contains the DTT at which the radius will be reached. + */ + size_t increase_radius_until_layer; + /*! + * \brief True if the branches may connect to the model. + */ + bool support_rests_on_model; + /*! + * \brief How far should support be from the model. + */ + coord_t xy_distance; + /*! + * \brief Radius a branch should have when reaching the buildplate. + */ + coord_t bp_radius; + /*! + * \brief The layer index at which an increase in radius may be required to reach the bp_radius. + */ + coord_t layer_start_bp_radius; + /*! + * \brief Factor by which to increase the branch radius to reach the required bp_radius at layer 0. Note that this radius increase will not happen in the tip, to ensure the tip is structurally sound. + */ + double diameter_scale_bp_radius; + /*! + * \brief Should Z distance override X/Y distance, or the other way around. + */ + SupportDistPriority support_overrides; + /*! + * \brief minimum xy_distance. Only relevant when Z overrides XY, otherwise equal to xy_distance- + */ + coord_t xy_min_distance; + /*! + * \brief Amount of layers distance required the top of the support to the model + */ + size_t z_distance_top_layers; + /*! + * \brief Amount of layers distance required from the top of the model to the bottom of a support structure. + */ + size_t z_distance_bottom_layers; + /*! + * \brief used for performance optimization at the support floor. Should have no impact on the resulting tree. + */ + size_t performance_interface_skip_layers; + /*! + * \brief User specified angles for the support infill. + */ + std::vector support_infill_angles; + /*! + * \brief User specified angles for the support roof infill. + */ + std::vector support_roof_angles; + /*! + * \brief Pattern used in the support roof. May contain non relevant data if support roof is disabled. + */ + EFillMethod roof_pattern; + /*! + * \brief Pattern used in the support infill. + */ + EFillMethod support_pattern; + /*! + * \brief Line width of the support roof. + */ + coord_t support_roof_line_width; + /*! + * \brief Distance between support infill lines. + */ + coord_t support_line_distance; + /*! + * \brief Offset applied to the support floor area. + */ + coord_t support_bottom_offset; + /* + * \brief Amount of walls the support area will have. + */ + int support_wall_count; + /* + * \brief Whether support infill lines will be connected. Only required to calculate infill patterns. + */ + bool zig_zaggify_support; + /* + * \brief Maximum allowed deviation when simplifying. + */ + coord_t maximum_deviation; + /* + * \brief Maximum allowed resolution (length of a line segment) when simplifying. The resolution is higher when this variable is smaller => Minimum size a line segment may have. + */ + coord_t maximum_resolution; + /* + * \brief Distance between the lines of the roof. + */ + coord_t support_roof_line_distance; + /* + * \brief Only relevant for zigzag pattern. Only required to calculate infill patterns. + */ + bool skip_some_zags; + /* + * \brief Only relevant for zigzag pattern. Only required to calculate infill patterns. + */ + size_t zag_skip_count; + /* + * \brief Only relevant for zigzag pattern. Only required to calculate infill patterns. + */ + bool connect_zigzags; + /* + * \brief How overlaps of an interface area with a support area should be handled. + */ + InterfacePreference interface_preference; + + /* + * \brief The infill class wants a settings object. This one will be the correct one for all settings it uses. + */ + 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 && 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_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"))); + } + + + /*! + * \brief Get the Distance to top regarding the real radius this part will have. This is different from distance_to_top, which is can be used to calculate the top most layer of the branch. + * \param elem[in] The SupportElement one wants to know the effectiveDTT + * \return The Effective DTT. + */ + [[nodiscard]] inline size_t getEffectiveDTT(const TreeSupport::SupportElement& elem) const + { + return elem.effective_radius_height < increase_radius_until_layer ? (elem.distance_to_top < increase_radius_until_layer ? elem.distance_to_top : increase_radius_until_layer) : elem.effective_radius_height; + } + + /*! + * \brief Get the Radius part will have based on numeric values. + * \param distance_to_top[in] The effective distance_to_top of the element + * \param elephant_foot_increases[in] The elephant_foot_increases of the element. + * \return The radius an element with these attributes would have. + */ + [[nodiscard]] inline coord_t getRadius(size_t distance_to_top, const double elephant_foot_increases = 0) const + { + return (distance_to_top <= tip_layers ? min_radius + (branch_radius - min_radius) * distance_to_top / tip_layers : // tip + branch_radius + // base + branch_radius * (distance_to_top - tip_layers) * diameter_angle_scale_factor) + + // gradual increase + branch_radius * elephant_foot_increases * (std::max(diameter_scale_bp_radius - diameter_angle_scale_factor, 0.0)); + } + + /*! + * \brief Get the Radius, that this element will have. + * \param elem[in] The Element. + * \return The radius the element has. + */ + [[nodiscard]] inline coord_t getRadius(const TreeSupport::SupportElement& elem) const + { + return getRadius(getEffectiveDTT(elem), elem.elephant_foot_increases); + } + + /*! + * \brief Get the collision Radius of this Element. This can be smaller then the actual radius, as the drawAreas will cut off areas that may collide with the model. + * \param elem[in] The Element. + * \return The collision radius the element has. + */ + [[nodiscard]] inline coord_t getCollisionRadius(const TreeSupport::SupportElement& elem) const + { + return getRadius(elem.effective_radius_height, elem.elephant_foot_increases); + } + + /*! + * \brief Get the Radius an element should at least have at a given layer. + * \param layer_idx[in] The layer. + * \return The radius every element should aim to achieve. + */ + [[nodiscard]] inline coord_t recommendedMinRadius(LayerIndex layer_idx) const + { + double scale = (layer_start_bp_radius - layer_idx) * diameter_scale_bp_radius; + return scale > 0 ? branch_radius + branch_radius * scale : 0; + } + + /*! + * \brief Return on which z in microns the layer will be printed. Used only for support infill line generation. + * \param layer_idx[in] The layer. + * \return The radius every element should aim to achieve. + */ + [[nodiscard]] inline coord_t getActualZ(LayerIndex layer_idx) + { + return layer_idx < coord_t(known_z.size()) ? known_z[layer_idx] : (layer_idx - known_z.size()) * layer_height + known_z.size() ? known_z.back() : 0; + } + + /*! + * \brief Set the z every Layer is printed at. Required for getActualZ to work + * \param z[in] The z every LayerIndex is printed. Vector is used as a map with the index of each element being the corresponding LayerIndex + * \return The radius every element should aim to achieve. + */ + void setActualZ(std::vector& z) + { + known_z = z; + } + }; + + private: + enum class LineStatus + { + INVALID, + TO_MODEL, + TO_MODEL_GRACIOUS, + TO_MODEL_GRACIOUS_SAFE, + TO_BP, + TO_BP_SAFE + }; + + using LineInformation = std::vector>; + + + /*! + * \brief Precalculates all avoidances, that could be required. + * + * \param storage[in] Background storage to access meshes. + * \param currently_processing_meshes[in] Indexes of all meshes that are processed in this iteration + */ + void precalculate(const SliceDataStorage& storage, std::vector currently_processing_meshes); + /*! + * \brief Converts a Polygons object representing a line into the internal format. + * + * \param polylines[in] The Polyline that will be converted. + * \param layer_idx[in] The current layer. + * \return All lines of the \p polylines object, with information for each point regarding in which avoidance it is currently valid in. + */ + std::vector convertLinesToInternal(Polygons polylines, LayerIndex layer_idx); + /*! + * \brief Converts lines in internal format into a Polygons object representing these lines. + * + * \param lines[in] The lines that will be converted. + * \return All lines of the \p lines object as a Polygons object. + */ + Polygons convertInternalToLines(std::vector lines); + /*! + * \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. + */ + std::function)> getEvaluatePointForNextLayerFunction(size_t current_layer); + /*! + * \brief Evaluates which points of some lines are not valid one layer below and which are. Assumes all points are valid on the current layer. Validity is evaluated using supplied lambda. + * + * \param lines[in] The lines that have to be evaluated. + * \param evaluatePoint[in] The function used to evaluate the points. + * \return A pair with which points are still valid in the first slot and which are not in the second slot. + */ + 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. + * + * \param poly[in] The Polygons object, of which its lines should be extended. + * \return A Polygons object with implicit line from the last vertex of a Polygon to the first one added. + */ + Polygons toPolylines(const Polygons& poly) const; + + + /*! + * \brief Returns Polylines representing the (infill) lines that will result in slicing the given area + * + * \param area[in] The area that has to be filled with infill. + * \param roof[in] Whether the roofing or regular support settings should be used. + * \param layer_idx[in] The current layer index. + * \param support_infill_distance[in] The distance that should be between the infill lines. + * \param cross_fill_provider[in] A SierpinskiFillProvider required for cross infill. + * + * \return A Polygons object that represents the resulting infill lines. + */ + Polygons generateSupportInfillLines(const Polygons& area, bool roof, LayerIndex layer_idx, coord_t support_infill_distance, SierpinskiFillProvider* cross_fill_provider = nullptr, bool include_walls = false); + + /*! + * \brief Unions two Polygons. Ensures that if the input is non empty that the output also will be non empty. + * \param first[in] The first Polygon. + * \param second[in] The second Polygon. + * \return The union of both Polygons + */ + [[nodiscard]] Polygons safeUnion(const Polygons first, const Polygons second = Polygons()) const; + + /*! + * \brief Creates a valid CrossInfillProvider + * Based on AreaSupport::precomputeCrossInfillTree, but calculates for each mesh separately + * \param mesh[in] The mesh that is currently processed. + * \param line_distance[in] The distance between the infill lines of the resulting infill + * \param line_width[in] What is the width of a line used in the infill. + * \return A valid CrossInfillProvider. Has to be freed manually to avoid a memory leak. + */ + SierpinskiFillProvider* generateCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width); + + + /*! + * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. + * + * Generates Points where the Model should be supported and creates the areas where these points have to be placed. + * + * \param mesh[in] The mesh that is currently processed. + * \param move_bounds[out] Storage for the influence areas. + * \param storage[in] Background storage, required for adding roofs. + */ + void generateInitialAreas(const 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_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. + */ + [[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; + + + /*! + * \brief Merges Influence Areas if possible. + * + * Branches which do overlap have to be merged. This helper merges all elements in input with the elements into reduced_new_layer. + * Elements in input_aabb are merged together if possible, while elements reduced_new_layer_aabb are not checked against each other. + * + * \param reduced_aabb[in,out] The already processed elements. + * \param input_aabb[in] Not yet processed elements + * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. Value is the influence area where the center of a circle of support may be placed. + * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. + * Value is the influence area where the center of a circle of support may be placed. + * \param influence_areas[in] The influence areas without avoidance removed. + * \param insert_bp_areas[out] Elements to be inserted into the main dictionary after the Helper terminates. + * \param insert_model_areas[out] Elements to be inserted into the secondary dictionary after the Helper terminates. + * \param insert_influence[out] Elements to be inserted into the dictionary containing the largest possibly valid influence area (ignoring if the area may not be there because of avoidance) + * \param erase[out] Elements that should be deleted from the above dictionaries. + * \param layer_idx[in] The Index of the current Layer. + */ + void mergeHelper(std::map& reduced_aabb, std::map& input_aabb, const std::unordered_map& to_bp_areas, const std::unordered_map& to_model_areas, const std::map& influence_areas, std::unordered_map& insert_bp_areas, std::unordered_map& insert_model_areas, std::unordered_map& insert_influence, std::vector& erase, const LayerIndex layer_idx); + /*! + * \brief Merges Influence Areas if possible. + * + * Branches which do overlap have to be merged. This manages the helper and uses a divide and conquer approach to parallelize this problem. This parallelization can at most accelerate the merging by a factor of 2. + * + * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. + * Value is the influence area where the center of a circle of support may be placed. + * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. + * Value is the influence area where the center of a circle of support may be placed. + * \param influence_areas[in] The Elements of the current Layer without avoidances removed. This is the largest possible influence area for this layer. + * Value is the influence area where the center of a circle of support may be placed. + * \param layer_idx[in] The current layer. + */ + void mergeInfluenceAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, LayerIndex layer_idx); + + /*! + * \brief Checks if an influence area contains a valid subsection and returns the corresponding metadata and the new Influence area. + * + * Calculates an influence areas of the layer below, based on the influence area of one element on the current layer. + * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in we would change our gracious or to_buildplate status the influence areas are instead increased by maximum_move_distance_slow. + * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. + * + * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. + * + * \param settings[in] Which settings have to be used to check validity. + * \param layer_idx[in] Number of the current layer. + * \param parent[in] The metadata of the parents influence area. + * \param relevant_offset[in] The maximal possible influence area. No guarantee regarding validity with current layer collision required, as it is ensured in-function! + * \param to_bp_data[out] The part of the Influence area that can reach the buildplate. + * \param to_model_data[out] The part of the Influence area that do not have to reach the buildplate. This has overlap with new_layer_data. + * \param increased[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. + * \param overspeed[in] How much should the already offset area be offset again. Usually this is 0. + * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. + * \return A valid support element for the next layer regarding the calculated influence areas. Empty if no influence are can be created using the supplied influence area and settings. + */ + std::optional increaseSingleArea(AreaIncreaseSettings settings, LayerIndex layer_idx, SupportElement* parent, const Polygons& relevant_offset, Polygons& to_bp_data, Polygons& to_model_data, Polygons& increased, const coord_t overspeed, const bool mergelayer); + /*! + * \brief Increases influence areas as far as required. + * + * Calculates influence areas of the layer below, based on the influence areas of the current layer. + * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in it would change the gracious or to_buildplate status, the influence areas are instead increased by maximum_move_distance. + * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. + * + * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. + * + * \param to_bp_areas[out] Influence areas that can reach the buildplate + * \param to_model_areas[out] Influence areas that do not have to reach the buildplate. This has overlap with new_layer_data, as areas that can reach the buildplate are also considered valid areas to the model. + * This redundancy is required if a to_buildplate influence area is allowed to merge with a to model influence area. + * \param influence_areas[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. + * \param bypass_merge_areas[out] Influence areas ready to be added to the layer below that do not need merging. + * \param last_layer[in] Influence areas of the current layer. + * \param layer_idx[in] Number of the current layer. + * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. + */ + void increaseAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, std::vector& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, const bool mergelayer); + + /*! + * \brief Propagates influence downwards, and merges overlapping ones. + * + * \param move_bounds[in,out] All currently existing influence areas + */ + void createLayerPathing(std::vector>& move_bounds); + + + /*! + * \brief Sets the result_on_layer for all parents based on the SupportElement supplied. + * + * \param elem[in] The SupportElements, which parent's position should be determined. + */ + void setPointsOnAreas(const SupportElement* elem); + /*! + * \brief Get the best point to connect to the model and set the result_on_layer of the relevant SupportElement accordingly. + * + * \param move_bounds[in,out] All currently existing influence areas + * \param first_elem[in,out] SupportElement that did not have its result_on_layer set meaning that it does not have a child element. + * \param layer_idx[in] The current layer. + * \return Should elem be deleted. + */ + bool setToModelContact(std::vector>& move_bounds, SupportElement* first_elem, const LayerIndex layer_idx); + + /*! + * \brief Set the result_on_layer point for all influence areas + * + * \param move_bounds[in,out] All currently existing influence areas + */ + void createNodesFromArea(std::vector>& move_bounds); + + /*! + * \brief Draws circles around result_on_layer points of the influence areas + * + * \param linear_data[in] All currently existing influence areas with the layer they are on + * \param layer_tree_polygons[out] Resulting branch areas with the layerindex they appear on. layer_tree_polygons.size() has to be at least linear_data.size() as each Influence area in linear_data will save have at least one (that's why it's a vector) corresponding branch area in layer_tree_polygons. + * \param inverse_tree_order[in] A mapping that returns the child of every influence area. + */ + void generateBranchAreas(std::vector>& linear_data, std::vector>& layer_tree_polygons, const std::map& inverse_tree_order); + + /*! + * \brief Applies some smoothing to the outer wall, intended to smooth out sudden jumps as they can happen when a branch moves though a hole. + * + * \param layer_tree_polygons[in,out] Resulting branch areas with the layerindex they appear on. + */ + void smoothBranchAreas(std::vector>& layer_tree_polygons); + + /*! + * \brief Drop down areas that do rest non-gracefully on the model to ensure the branch actually rests on something. + * + * \param layer_tree_polygons[in] Resulting branch areas with the layerindex they appear on. + * \param linear_data[in] All currently existing influence areas with the layer they are on + * \param dropped_down_areas[out] Areas that have to be added to support all non-graceful areas. + * \param inverse_tree_order[in] A mapping that returns the child of every influence area. + */ + void dropNonGraciousAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, const std::map& inverse_tree_order); + + + /*! + * \brief Generates Support Floor, ensures Support Roof can not cut of branches, and saves the branches as support to storage + * + * \param support_layer_storage[in] Areas where support should be generated. + * \param support_roof_storage[in] Areas where support was replaced with roof. + * \param storage[in,out] The storage where the support should be stored. + */ + void finalizeInterfaceAndSupportAreas(std::vector& support_layer_storage, std::vector& support_roof_storage, SliceDataStorage& storage); + + /*! + * \brief Draws circles around result_on_layer points of the influence areas and applies some post processing. + * + * \param move_bounds[in] All currently existing influence areas + * \param storage[in,out] The storage where the support should be stored. + */ + void drawAreas(std::vector>& move_bounds, SliceDataStorage& storage); + + /*! + * \brief Settings with the indexes of meshes that use these settings. + * + */ + std::vector>> grouped_meshes; + + /*! + * \brief Generator for model collision, avoidance and internal guide volumes. + * + */ + TreeModelVolumes volumes_; + + /*! + * \brief Contains config settings to avoid loading them in every function. This was done to improve readability of the code. + */ + TreeSupportSettings config; + + /*! + * \brief The progress multiplier of all values added progress bar. + * Required for the progress bar the behave as expected when areas have to be calculated multiple times + */ + double progress_multiplier = 1; + + /*! + * \brief The progress offset added to all values communicated to the progress bar. + * Required for the progress bar the behave as expected when areas have to be calculated multiple times + */ + double progress_offset = 0; +}; + + +} // namespace cura + +namespace std +{ +template <> +struct hash +{ + size_t operator()(const cura::TreeSupport::SupportElement& node) const + { + size_t hash_node = hash()(node.target_position); + boost::hash_combine(hash_node, size_t(node.target_height)); + return hash_node; + } +}; +} // namespace std + +#endif /* TREESUPPORT_H */ From 4c41311df32231dd636cb164410c1e56e97e8db2 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 19 Jul 2022 10:59:10 +0200 Subject: [PATCH 02/29] WIP Tree Supports: Updated copyright --- src/libslic3r/CMakeLists.txt | 4 ++++ src/libslic3r/TreeModelVolumes.cpp | 9 +++++++-- src/libslic3r/TreeModelVolumes.hpp | 9 +++++++-- src/libslic3r/TreeSupport.cpp | 7 ++++++- src/libslic3r/TreeSupport.hpp | 7 ++++++- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index e637b1402..a40b4845b 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -254,6 +254,10 @@ set(SLIC3R_SOURCES Technologies.hpp Tesselate.cpp Tesselate.hpp + TreeSupport.cpp + TreeSupport.hpp + TreeModelVolumes.cpp + TreeModelVolumes.hpp TriangleMesh.cpp TriangleMesh.hpp TriangleMeshSlicer.cpp diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 591b2c540..0537e93db 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -1,5 +1,10 @@ -//Copyright (c) 2021 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Tree supports by Thomas Rahm, losely based on Tree Supports by CuraEngine. +// Original source of Thomas Rahm's tree supports: +// https://github.com/ThomasRahm/CuraEngine +// +// Original CuraEngine copyright: +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #include "TreeModelVolumes.h" #include "TreeSupport.h" diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 4c8b5fbdd..e027e0c26 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -1,5 +1,10 @@ -//Copyright (c) 2021 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Tree supports by Thomas Rahm, losely based on Tree Supports by CuraEngine. +// Original source of Thomas Rahm's tree supports: +// https://github.com/ThomasRahm/CuraEngine +// +// Original CuraEngine copyright: +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef TREEMODELVOLUMES_H #define TREEMODELVOLUMES_H diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 5f579f114..3325ca379 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -1,4 +1,9 @@ -// Copyright (c) 2019 Ultimaker B.V. +// Tree supports by Thomas Rahm, losely based on Tree Supports by CuraEngine. +// Original source of Thomas Rahm's tree supports: +// https://github.com/ThomasRahm/CuraEngine +// +// Original CuraEngine copyright: +// Copyright (c) 2021 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher. #include "TreeSupport.h" diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index edc25d305..9fad42e14 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -1,4 +1,9 @@ -// Copyright (c) 2017 Ultimaker B.V. +// Tree supports by Thomas Rahm, losely based on Tree Supports by CuraEngine. +// Original source of Thomas Rahm's tree supports: +// https://github.com/ThomasRahm/CuraEngine +// +// Original CuraEngine copyright: +// Copyright (c) 2021 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef TREESUPPORT_H From f6ae93366a639f4d5a880e6fa5b4038f9a12f92c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 19 Jul 2022 11:10:17 +0200 Subject: [PATCH 03/29] WIP TreeSupports: Replaced cura namespace with Slic3r, removed Cura includes. --- src/libslic3r/TreeModelVolumes.cpp | 11 ++++------- src/libslic3r/TreeModelVolumes.hpp | 14 ++++---------- src/libslic3r/TreeSupport.cpp | 20 +++++--------------- src/libslic3r/TreeSupport.hpp | 24 ++++++++++-------------- 4 files changed, 23 insertions(+), 46 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 0537e93db..07917ff93 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -6,13 +6,10 @@ // Copyright (c) 2021 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher. -#include "TreeModelVolumes.h" -#include "TreeSupport.h" -#include "progress/Progress.h" -#include "sliceDataStorage.h" -#include "utils/algorithm.h" -#include "utils/logoutput.h" -namespace cura +#include "TreeModelVolumes.hpp" +#include "TreeSupport.hpp" + +namespace Slic3r { 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 diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index e027e0c26..44edf6941 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -6,20 +6,14 @@ // Copyright (c) 2021 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher. -#ifndef TREEMODELVOLUMES_H -#define TREEMODELVOLUMES_H +#ifndef slic3r_TreeModelVolumes_hpp +#define slic3r_TreeModelVolumes_hpp -#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 +namespace Slic3r { class TreeModelVolumes @@ -432,4 +426,4 @@ class TreeModelVolumes } -#endif //TREEMODELVOLUMES_H \ No newline at end of file +#endif //slic3r_TreeModelVolumes_hpp \ No newline at end of file diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 3325ca379..b8b3a9760 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -6,26 +6,16 @@ // Copyright (c) 2021 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher. -#include "TreeSupport.h" -#include "Application.h" //To get settings. -#include "infill.h" -#include "infill/SierpinskiFillProvider.h" -#include "progress/Progress.h" -#include "settings/EnumSettings.h" -#include "support.h" //For precomputeCrossInfillTree -#include "utils/logoutput.h" -#include "utils/math.h" //For round_up_divide and PI. -#include "utils/polygonUtils.h" //For moveInside. +#include "TreeSupport.hpp" + #include #include #include #include #include -#include -#include #include //todo Remove! ONLY FOR PUBLIC BETA!! -namespace cura +namespace Slic3r { TreeSupport::TreeSupport(const SliceDataStorage& storage) @@ -2279,7 +2269,7 @@ void TreeSupport::smoothBranchAreas(std::vector data_pair : update_next) + for (std::pair data_pair : update_next) { if (data_pair.first != nullptr) { @@ -2486,4 +2476,4 @@ void TreeSupport::drawAreas(std::vector>& move_bounds, -} // namespace cura +} // namespace Slic3r diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 9fad42e14..697fbe8f7 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -6,15 +6,11 @@ // Copyright (c) 2021 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher. -#ifndef TREESUPPORT_H -#define TREESUPPORT_H +#ifndef slic3r_TreeSupport_hpp +#define slic3r_TreeSupport_hpp -#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 "TreeModelVolumes.hpp" +#include // For combining hashes #define SUPPORT_TREE_CIRCLE_RESOLUTION 25 // The number of vertices in each circle. @@ -42,7 +38,7 @@ #define SUPPORT_TREE_MAX_DEVIATION 0 -namespace cura +namespace Slic3r { @@ -1015,20 +1011,20 @@ class TreeSupport }; -} // namespace cura +} // namespace Slic3r namespace std { template <> -struct hash +struct hash { - size_t operator()(const cura::TreeSupport::SupportElement& node) const + size_t operator()(const Slic3r::TreeSupport::SupportElement& node) const { - size_t hash_node = hash()(node.target_position); + size_t hash_node = hash()(node.target_position); boost::hash_combine(hash_node, size_t(node.target_height)); return hash_node; } }; } // namespace std -#endif /* TREESUPPORT_H */ +#endif /* slic3r_TreeSupport_hpp */ From 6e1e4fcca2ea144d36e1393ca6afdacc56b21b85 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 19 Jul 2022 17:14:07 +0200 Subject: [PATCH 04/29] WIP Tree Supports: Ported parallelization from cura homebrew parallel_for to thread building blocks tbb::parallel_for. --- src/libslic3r/TreeModelVolumes.cpp | 716 ++++++++------- src/libslic3r/TreeModelVolumes.hpp | 15 +- src/libslic3r/TreeSupport.cpp | 1379 ++++++++++++++-------------- 3 files changed, 1066 insertions(+), 1044 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 07917ff93..c8cd87c40 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -9,6 +9,9 @@ #include "TreeModelVolumes.hpp" #include "TreeSupport.hpp" +#include +#include + namespace Slic3r { @@ -71,40 +74,47 @@ TreeModelVolumes::TreeModelVolumes(const SliceDataStorage& storage, const coord_ { 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 + tbb::parallel_for(tbb::blocked_range(0, layer_outlines_[mesh_to_layeroutline_idx[mesh_idx]].second.size()), + [&](const tbb::blocked_range &range) { + for (const size_t layer_idx = range.begin(); layer_idx < range.end(); ++ 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); } - 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); - } + tbb::parallel_for(tbb::blocked_range(0, anti_overhang_.size()), + [&](const tbb::blocked_range &range) { + for (const size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + if (layer_idx < coord_t(additional_excluded_areas.size())) + { + anti_overhang_[layer_idx].add(additional_excluded_areas[layer_idx]); + } - if (storage.primeTower.enabled) - { - anti_overhang_[layer_idx].add(layer_idx == 0 ? storage.primeTower.outer_poly_first_layer : storage.primeTower.outer_poly); + 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(); } - 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(); }); + tbb::parallel_for(tbb::blocked_range(0, anti_overhang_.size()), + [&](const tbb::blocked_range &range) { + for (const size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) + layer_outlines_[idx].second[layer_idx] = layer_outlines_[idx].second[layer_idx].unionPolygons(); + }); } radius_0 = config.getRadius(0); } @@ -199,21 +209,15 @@ void TreeModelVolumes::precalculate(coord_t max_layer) // ### Calculate the relevant avoidances in parallel as far as possible { - std::future placeable_waiter; + tbb::task_group task_group; + task_group.run([this, relevant_avoidance_radiis]{ calculateAvoidance(relevant_avoidance_radiis); }); + task_group.run([this, relevant_avoidance_radiis]{ calculateWallRestrictions(relevant_avoidance_radiis); }); 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(); + task_group.run([this, relevant_avoidance_radiis_to_model]{ + calculatePlaceables(relevant_avoidance_radiis_to_model); + calculateAvoidanceToModel(relevant_avoidance_radiis_to_model); + }); + task_group.wait(); } auto t_end = std::chrono::high_resolution_clock::now(); auto dur_col = 0.001 * std::chrono::duration_cast(t_coll - t_start).count(); @@ -549,137 +553,138 @@ LayerIndex TreeModelVolumes::getMaxCalculatedLayer(coord_t radius, const std::un 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++) - { - std::unordered_map data; - std::unordered_map data_placeable; + tbb::parallel_for(tbb::blocked_range(0, keys.size()), + [&](const tbb::blocked_range &range) { + for (const size_t i = range.begin(); i < range.end(); ++ 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; - { - 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()) + 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; { - collision_areas.add(layer_outlines_[outline_idx].second[layer_idx]); + std::lock_guard critical_section(*critical_collision_cache_); + min_layer_bottom = getMaxCalculatedLayer(radius, collision_cache_) - z_distance_bottom_layers; } - 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++) + if (min_layer_bottom < 0) { - data[key].add(data[RadiusLayerPair(radius, layer_idx - layer_offset)]); + min_layer_bottom = 0; } - if (support_rests_on_this_model && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) + for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; layer_idx++) { - data[key] = data[key].unionPolygons(); - Polygons above = data[RadiusLayerPair(radius, layer_idx + 1)]; - if (max_anti_overhang_layer >= layer_idx + 1) + key.second = layer_idx; + Polygons collision_areas = machine_border_; + if (size_t(layer_idx) < layer_outlines_[outline_idx].second.size()) { - above = above.unionPolygons(anti_overhang_[layer_idx]); + 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 { - above = above.unionPolygons(); // just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly. + 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); } - 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++) + std::lock_guard critical_section(*critical_progress); + + if (precalculated && precalculation_progress < TREE_PROGRESS_PRECALC_COLL) { - 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(); + precalculation_progress += TREE_PROGRESS_PRECALC_COLL / keys.size(); + Progress::messageProgress(Progress::Stage::SUPPORT, precalculation_progress * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); } } - 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); + std::lock_guard critical_section(*critical_collision_cache_); + collision_cache_.insert(data_outer.begin(), data_outer.end()); } 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); + std::lock_guard critical_section(*critical_placeable_areas_cache_); + placeable_areas_cache_.insert(data_placeable_outer.begin(), data_placeable_outer.end()); } } } - - { - 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) - { - { - std::lock_guard critical_section(*critical_placeable_areas_cache_); - placeable_areas_cache_.insert(data_placeable_outer.begin(), data_placeable_outer.end()); - } - } }); } void TreeModelVolumes::calculateCollisionHolefree(std::deque keys) @@ -690,23 +695,24 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke 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; - } + tbb::parallel_for(tbb::blocked_range(0, max_layer + 1), + [&](const tbb::blocked_range &range) { + for (const size_t layer_idx = range.begin(); layer_idx < range.end(); ++ 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()); + { + std::lock_guard critical_section(*critical_collision_cache_holefree_); + collision_cache_holefree_.insert(data.begin(), data.end()); + } } }); } @@ -728,20 +734,156 @@ Polygons TreeModelVolumes::safeOffset(const Polygons& me, coord_t distance, Clip return ret.unionPolygons(collision); } -std::future TreeModelVolumes::calculateAvoidance(std::deque keys) +void TreeModelVolumes::calculateAvoidance(std::deque keys) { // For every RadiusLayer pair there are 3 avoidances that have to be calculate, calculated in the same paralell_for loop for better paralellisation. const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; - 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; - { + tbb::parallel_for(tbb::blocked_range(0, keys.size() * 3), + [&, keys, all_types](const tbb::blocked_range &range) { + for (const size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { + size_t key_idx = iter_idx / 3; + { + size_t type_idx = iter_idx % all_types.size(); + AvoidanceType type = all_types[type_idx]; + const bool slow = type == AvoidanceType::SLOW; + const bool holefree = type == AvoidanceType::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; +} + +void TreeModelVolumes::calculatePlaceables(std::deque keys) +{ + tbb::parallel_for(tbb::blocked_range(0, keys.size()), + [&, keys](const tbb::blocked_range &range) { + for (const size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { + const coord_t radius = keys[key_idx].first; + const LayerIndex max_required_layer = keys[key_idx].second; + std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); + RadiusLayerPair key(radius, 0); + + LayerIndex start_layer; + { + 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; +} + + +void TreeModelVolumes::calculateAvoidanceToModel(std::deque keys) +{ + // For every RadiusLayer pair there are 3 avoidances that have to be calculated, calculated in the same parallel_for loop for better parallelization. + const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; + tbb::parallel_for(tbb::blocked_range(0, keys.size() * 3), + [&, keys, all_types](const tbb::blocked_range &range) { + for (const size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { + size_t key_idx = iter_idx / 3; size_t type_idx = iter_idx % all_types.size(); AvoidanceType type = all_types[type_idx]; - const bool slow = type == AvoidanceType::SLOW; - const bool holefree = type == AvoidanceType::FAST_SAFE; - + 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; @@ -750,32 +892,33 @@ std::future TreeModelVolumes::calculateAvoidance(std::deque> 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_slow_ : holefree ? critical_avoidance_cache_holefree_ : critical_avoidance_cache_)); - start_layer = 1 + getMaxCalculatedLayer(radius, slow ? avoidance_cache_slow_ : holefree ? avoidance_cache_hole_ : avoidance_cache_); + 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)); // 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. + 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; + Polygons col = getCollision(radius, layer, true); + if ((slow && radius < increase_until_radius + current_min_xy_dist_delta) || holefree) { col = getCollisionHolefree(radius, layer, true); @@ -784,7 +927,9 @@ std::future TreeModelVolumes::calculateAvoidance(std::deque(key, latest_avoidance); } @@ -794,159 +939,23 @@ std::future TreeModelVolumes::calculateAvoidance(std::deque 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()); + 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::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) +void TreeModelVolumes::calculateWallRestrictions(std::deque keys) { // Wall restrictions are mainly important when they represent actual walls that are printed, and not "just" the configured z_distance, because technically valid placement is no excuse for moving through a wall. // 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 @@ -983,47 +992,48 @@ std::future TreeModelVolumes::calculateWallRestrictions(std::deque 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; + tbb::parallel_for(tbb::blocked_range(0, keys.size()), + [&, keys](const tbb::blocked_range &range) { + for (const size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { + coord_t radius = keys[key_idx].first; + RadiusLayerPair key(radius, 0); + coord_t min_layer_bottom; + 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_); + min_layer_bottom = getMaxCalculatedLayer(radius, wall_restrictions_cache_); } - } - { - std::lock_guard critical_section(*critical_wall_restrictions_cache_); - wall_restrictions_cache_.insert(data.begin(), data.end()); - } + 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_min_); - wall_restrictions_cache_min_.insert(data_min.begin(), data_min.end()); + { + 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; diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 44edf6941..551dfdddc 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -196,9 +196,8 @@ class TreeModelVolumes * 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 */ - [[nodiscard]] std::future calculateAvoidance(std::deque keys); + void calculateAvoidance(std::deque keys); /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model. @@ -226,10 +225,8 @@ class TreeModelVolumes * \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 */ - [[nodiscard]] std::future calculatePlaceables(std::deque keys); + 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. @@ -237,10 +234,8 @@ class TreeModelVolumes * 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 */ - [[nodiscard]] std::future calculateAvoidanceToModel(std::deque keys); + 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. @@ -259,10 +254,8 @@ class TreeModelVolumes * 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. - * - * \return A future that has to be waited on */ - [[nodiscard]] std::future calculateWallRestrictions(std::deque keys); + void calculateWallRestrictions(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). diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index b8b3a9760..d8a0d7983 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -15,6 +15,8 @@ #include #include //todo Remove! ONLY FOR PUBLIC BETA!! +#include + namespace Slic3r { @@ -140,17 +142,18 @@ 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 - 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); - for (auto part : storage.support.supportLayers[layer_idx].support_infill_parts) - { - exlude_at_layer.add(part.outline); + tbb::parallel_for(tbb::blocked_range(0, storage.support.supportLayers.size()), + [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ 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); + for (auto part : storage.support.supportLayers[layer_idx].support_infill_parts) + { + exlude_at_layer.add(part.outline); + } + exclude[layer_idx] = exlude_at_layer.unionPolygons(); } - exclude[layer_idx] = exlude_at_layer.unionPolygons(); }); 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()); @@ -701,290 +704,292 @@ void TreeSupport::generateInitialAreas(const SliceMeshStorage& mesh, std::vector 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()) - { - return; // This is a continue if imagined in a loop context - } + tbb::parallel_for(tbb::blocked_range(1, mesh.overhang_areas.size() - z_distance_delta), + [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - 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) - { - 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); - }; + if (mesh.overhang_areas[layer_idx + z_distance_delta].empty()) + { + return; // This is a continue if imagined in a loop context + } - 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; - bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - if (!mesh_config.support_rests_on_model && !to_bp) + 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) { - logWarning("Tried to add an invalid support point\n"); - TreeSupport::showError("Unable to add tip. Some overhang may not be supported correctly.", true); - return; - } - Polygon circle; - for (Point corner : base_circle) + 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, 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) { - circle.add(p.first + corner); - } - Polygons area = circle.offset(0); - { - std::lock_guard critical_section_movebounds(critical_sections); - if (!already_inserted[insert_layer].count(p.first / ((mesh_config.min_radius + 1) / 10))) + bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; + bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; + bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; + if (!mesh_config.support_rests_on_model && !to_bp) { - // 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, skip_ovalisation); - elem->area = new Polygons(area); - move_bounds[insert_layer].emplace(elem); + logWarning("Tried to add an invalid support point\n"); + TreeSupport::showError("Unable to add tip. Some overhang may not be supported correctly.", true); + return; } - } - }; - - - 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_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) + Polygon circle; + for (Point corner : base_circle) { - Polygon roof_circle; - for (Point corner : base_circle) - { - roof_circle.add(p.first + corner * mesh_config.min_radius); - } - Polygons area = roof_circle.offset(0); - return !generateLines(area, true, insert_layer_idx - dtt_roof_tip).empty(); - }; - - 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 + circle.add(p.first + corner); + } + Polygons area = circle.offset(0); { - for (std::pair point_data : line) + std::lock_guard critical_section_movebounds(critical_sections); + if (!already_inserted[insert_layer].count(p.first / ((mesh_config.min_radius + 1) / 10))) { - addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); + // 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, skip_ovalisation); + elem->area = new Polygons(area); + move_bounds[insert_layer].emplace(elem); } } + }; - // not all roofs are guaranteed to actually generate lines, so filter these out and add them as points - split = splitLines(split.first, evaluateRoofWillGenerate); - lines = split.first; - for (LineInformation line : split.second) + 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_tip; + for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; dtt_roof_tip++) { - for (std::pair point_data : line) - { - addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); - } - } - - // add all tips as roof to the roof storage - Polygons added_roofs; - for (LineInformation line : lines) - { - for (std::pair p : line) + std::function)> evaluateRoofWillGenerate = [&](std::pair p) { Polygon roof_circle; for (Point corner : base_circle) { - roof_circle.add(p.first + corner * mesh_config.min_radius / base_radius); + roof_circle.add(p.first + corner * mesh_config.min_radius); } - added_roofs.add(roof_circle); - } - } - added_roofs = added_roofs.unionPolygons(); - { - std::lock_guard critical_section_storage(critical_sections); + Polygons area = roof_circle.offset(0); + return !generateLines(area, true, insert_layer_idx - dtt_roof_tip).empty(); + }; - storage.support.supportLayers[insert_layer_idx - dtt_roof_tip].support_roof.add(added_roofs); - } - } + 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 : lines) - { - bool disable_ovalistation = mesh_config.min_radius < 3 * mesh_config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > 5; // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width - for (auto point_data : line) - { - addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, dtt_roof_tip != 0 || supports_roof, disable_ovalistation); - } - } - }; - - 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 * 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; - - // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. - while (extra_total_offset_acc + mesh_config.support_line_width / 8 < extra_outset) //+mesh_config.support_line_width / 80 to avoid calculating very small (useless) offsets because of rounding errors. - { - 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, 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, 0, 1); - overhang_regular = overhang_regular.unionPolygons(next_overhang.difference(relevant_forbidden)); - } - - // If the xy distance overrides the z distance, some support needs to be inserted further down. - //=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the the setting description, it would not produce reasonable results. ) - if (xy_overrides) - { - std::vector overhang_lines; - Polygons polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang, false, layer_idx), mesh_config.min_radius, 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 consistent behavior as some infill patterns generate each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that the area that is valid a layer below is to small for support roof. - if (polylines.pointCount() <= 3) - { - // add the outer wall to ensure it is correct supported instead - polylines = ensureMaximumDistancePolyline(toPolylines(remaining_overhang), connect_length, 3); - } - - for (auto line : polylines) - { - LineInformation res_line; - for (Point p : line) - { - res_line.emplace_back(p, LineStatus::INVALID); - } - overhang_lines.emplace_back(res_line); - } - - for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) - { - // get least restricted avoidance for layer_idx-lag_ctr - Polygons relevant_forbidden_below = (mesh_config.support_rests_on_model ? (only_gracious ? volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, true, !xy_overrides) : volumes_.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, !xy_overrides)) : volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, false, !xy_overrides)); - // it is not required to offset the forbidden area here as the points wont change: If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. - std::function)> evaluatePoint = [&](std::pair p) { return relevant_forbidden_below.inside(p.first, true); }; - - std::pair, std::vector> split = splitLines(overhang_lines, evaluatePoint); // keep all lines that are invalid - 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, 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, 0, 1); - overhang_roofs.removeSmallAreas(minimum_roof_area); - overhang_regular = overhang_regular.difference(overhang_roofs); - for (Polygons roof_part : overhang_roofs.splitIntoParts(true)) - { - overhang_processing.emplace_back(roof_part, true); - } - } - overhang_regular.removeSmallAreas(minimum_support_area); - - for (Polygons support_part : overhang_regular.splitIntoParts(true)) - { - overhang_processing.emplace_back(support_part, false); - } - - for (std::pair overhang_pair : overhang_processing) - { - const bool roof_allowed_for_this_part = overhang_pair.second; - Polygons overhang_outset = overhang_pair.first; - const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), overhang_outset.polygonLength() / connect_length)); - std::vector overhang_lines; - Polygons last_overhang = overhang_outset; - size_t dtt_roof = 0; - std::vector added_roofs(support_roof_layers); // sometimes roofs could be empty as the pattern does not generate lines if the area is narrow enough (i am looking at you, concentric infill). To catch these cases the added roofs are saved to be evaluated later. - - // Assumption is that roof will support roof further up to avoid a lot of unnecessary branches. Each layer down it is checked whether the roof area is still large enough to be a roof and aborted as soon as it is not. - // This part was already reworked a few times, and there could be an argument made to change it again if there are actual issues encountered regarding supporting roofs. - // Main problem is that some patterns change each layer, so just calculating points and checking if they are still valid an layer below is not useful, as the pattern may be different one layer below. - // Same with calculating which points are now no longer being generated as result from a decreasing roof, as there is no guarantee that a line will be above these points. - // Implementing a separate roof support behavior for each pattern harms maintainability as it very well could be >100 LOC - if (roof_allowed_for_this_part) - { - for (dtt_roof = 0; dtt_roof < support_roof_layers && layer_idx - dtt_roof >= 1; dtt_roof++) - { - // here the roof is handled. If roof can not be added the branches will try to not move instead - Polygons forbidden_next = (mesh_config.support_rests_on_model ? (only_gracious ? volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, true, !xy_overrides) : volumes_.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides)) : volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides)); - forbidden_next = forbidden_next.offset(5); // prevent rounding errors down the line - Polygons overhang_outset_next = overhang_outset.difference(forbidden_next); - if (overhang_outset_next.area() / (1000 * 1000) < minimum_roof_area) // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter + for (LineInformation line : split.second) // add all points that would not be valid { - size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; - if (dtt_roof != 0) + for (std::pair point_data : line) { - overhang_lines = convertLinesToInternal(ensureMaximumDistancePolyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); - overhang_lines = splitLines(overhang_lines, getEvaluatePointForNextLayerFunction(layer_idx - dtt_before)).first; + addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); } - - break; } - added_roofs[dtt_roof] = overhang_outset; - last_overhang = overhang_outset; - overhang_outset = overhang_outset_next; - } - } - size_t layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; // 1 inside max and -1 outside to avoid underflow. layer_generation_dtt=dtt_roof-1 if dtt_roof!=0; - if (overhang_lines.empty() && dtt_roof != 0 && generateLines(overhang_outset, true, layer_idx - layer_generation_dtt).empty()) // if the roof should be valid, check that the area does generate lines. This is NOT guaranteed. - { - for (size_t idx = 0; idx < dtt_roof; idx++) - { - // check for every roof area that it has resulting lines. Remember idx 1 means the 2. layer of roof => higher idx == lower layer - if (generateLines(added_roofs[idx], true, layer_idx - idx).empty()) + // not all roofs are guaranteed to actually generate lines, so filter these out and add them as points + split = splitLines(split.first, evaluateRoofWillGenerate); + lines = split.first; + + for (LineInformation line : split.second) { - dtt_roof = idx; - layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; - break; + for (std::pair point_data : line) + { + addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); + } } - } - } - { - std::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 - } - } - - 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. - if (!reduced_overhang_outset.empty() && overhang_outset.difference(reduced_overhang_outset.offset(std::max(mesh_config.support_line_width, connect_length))).area() < 1) + // add all tips as roof to the roof storage + Polygons added_roofs; + for (LineInformation line : lines) { - polylines = ensureMaximumDistancePolyline(toPolylines(reduced_overhang_outset), connect_length, min_support_points); + for (std::pair p : line) + { + Polygon roof_circle; + for (Point corner : base_circle) + { + roof_circle.add(p.first + corner * mesh_config.min_radius / base_radius); + } + added_roofs.add(roof_circle); + } } - else + added_roofs = added_roofs.unionPolygons(); { - polylines = ensureMaximumDistancePolyline(toPolylines(overhang_outset), connect_length, min_support_points); + std::lock_guard critical_section_storage(critical_sections); + + storage.support.supportLayers[insert_layer_idx - dtt_roof_tip].support_roof.add(added_roofs); } } - LayerIndex last_insert_layer = layer_idx - dtt_roof; - overhang_lines = convertLinesToInternal(polylines, last_insert_layer); + + for (LineInformation line : lines) + { + bool disable_ovalistation = mesh_config.min_radius < 3 * mesh_config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > 5; // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width + for (auto point_data : line) + { + addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, dtt_roof_tip != 0 || supports_roof, disable_ovalistation); + } + } + }; + + 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 * 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; + + // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. + while (extra_total_offset_acc + mesh_config.support_line_width / 8 < extra_outset) //+mesh_config.support_line_width / 80 to avoid calculating very small (useless) offsets because of rounding errors. + { + 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, 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, 0, 1); + overhang_regular = overhang_regular.unionPolygons(next_overhang.difference(relevant_forbidden)); } - if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part) // reached buildplate + // If the xy distance overrides the z distance, some support needs to be inserted further down. + //=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the the setting description, it would not produce reasonable results. ) + if (xy_overrides) { + std::vector overhang_lines; + Polygons polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang, false, layer_idx), mesh_config.min_radius, 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 consistent behavior as some infill patterns generate each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that the area that is valid a layer below is to small for support roof. + if (polylines.pointCount() <= 3) + { + // add the outer wall to ensure it is correct supported instead + polylines = ensureMaximumDistancePolyline(toPolylines(remaining_overhang), connect_length, 3); + } + + for (auto line : polylines) + { + LineInformation res_line; + for (Point p : line) + { + res_line.emplace_back(p, LineStatus::INVALID); + } + overhang_lines.emplace_back(res_line); + } + + for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) + { + // get least restricted avoidance for layer_idx-lag_ctr + Polygons relevant_forbidden_below = (mesh_config.support_rests_on_model ? (only_gracious ? volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, true, !xy_overrides) : volumes_.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, !xy_overrides)) : volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, false, !xy_overrides)); + // it is not required to offset the forbidden area here as the points wont change: If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. + std::function)> evaluatePoint = [&](std::pair p) { return relevant_forbidden_below.inside(p.first, true); }; + + std::pair, std::vector> split = splitLines(overhang_lines, evaluatePoint); // keep all lines that are invalid + 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, 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, 0, 1); + overhang_roofs.removeSmallAreas(minimum_roof_area); + overhang_regular = overhang_regular.difference(overhang_roofs); + for (Polygons roof_part : overhang_roofs.splitIntoParts(true)) + { + overhang_processing.emplace_back(roof_part, true); + } + } + overhang_regular.removeSmallAreas(minimum_support_area); + + for (Polygons support_part : overhang_regular.splitIntoParts(true)) + { + overhang_processing.emplace_back(support_part, false); + } + + for (std::pair overhang_pair : overhang_processing) + { + const bool roof_allowed_for_this_part = overhang_pair.second; + Polygons overhang_outset = overhang_pair.first; + const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), overhang_outset.polygonLength() / connect_length)); + std::vector overhang_lines; + Polygons last_overhang = overhang_outset; + size_t dtt_roof = 0; + std::vector added_roofs(support_roof_layers); // sometimes roofs could be empty as the pattern does not generate lines if the area is narrow enough (i am looking at you, concentric infill). To catch these cases the added roofs are saved to be evaluated later. + + // Assumption is that roof will support roof further up to avoid a lot of unnecessary branches. Each layer down it is checked whether the roof area is still large enough to be a roof and aborted as soon as it is not. + // This part was already reworked a few times, and there could be an argument made to change it again if there are actual issues encountered regarding supporting roofs. + // Main problem is that some patterns change each layer, so just calculating points and checking if they are still valid an layer below is not useful, as the pattern may be different one layer below. + // Same with calculating which points are now no longer being generated as result from a decreasing roof, as there is no guarantee that a line will be above these points. + // Implementing a separate roof support behavior for each pattern harms maintainability as it very well could be >100 LOC + if (roof_allowed_for_this_part) + { + for (dtt_roof = 0; dtt_roof < support_roof_layers && layer_idx - dtt_roof >= 1; dtt_roof++) + { + // here the roof is handled. If roof can not be added the branches will try to not move instead + Polygons forbidden_next = (mesh_config.support_rests_on_model ? (only_gracious ? volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, true, !xy_overrides) : volumes_.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides)) : volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides)); + forbidden_next = forbidden_next.offset(5); // prevent rounding errors down the line + Polygons overhang_outset_next = overhang_outset.difference(forbidden_next); + if (overhang_outset_next.area() / (1000 * 1000) < minimum_roof_area) // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter + { + size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; + if (dtt_roof != 0) + { + overhang_lines = convertLinesToInternal(ensureMaximumDistancePolyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); + overhang_lines = splitLines(overhang_lines, getEvaluatePointForNextLayerFunction(layer_idx - dtt_before)).first; + } + + break; + } + added_roofs[dtt_roof] = overhang_outset; + last_overhang = overhang_outset; + overhang_outset = overhang_outset_next; + } + } + + size_t layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; // 1 inside max and -1 outside to avoid underflow. layer_generation_dtt=dtt_roof-1 if dtt_roof!=0; + if (overhang_lines.empty() && dtt_roof != 0 && generateLines(overhang_outset, true, layer_idx - layer_generation_dtt).empty()) // if the roof should be valid, check that the area does generate lines. This is NOT guaranteed. + { + for (size_t idx = 0; idx < dtt_roof; idx++) + { + // check for every roof area that it has resulting lines. Remember idx 1 means the 2. layer of roof => higher idx == lower layer + if (generateLines(added_roofs[idx], true, layer_idx - idx).empty()) + { + dtt_roof = idx; + layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; + break; + } + } + } + { std::lock_guard critical_section_storage(critical_sections); - storage.support.supportLayers[0].support_roof.add(overhang_outset); + 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 + } + } + + 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. + 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); + } + else + { + polylines = ensureMaximumDistancePolyline(toPolylines(overhang_outset), connect_length, min_support_points); + } + } + LayerIndex last_insert_layer = layer_idx - dtt_roof; + overhang_lines = convertLinesToInternal(polylines, last_insert_layer); + } + + if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part) // reached buildplate + { + { + 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, dtt_roof > 0, roof_enabled ? support_roof_layers - dtt_roof : 0); } - } - else // normal trees have to be generated - { - addLinesAsInfluenceAreas(overhang_lines, force_tip_to_roof ? support_roof_layers - dtt_roof : 0, layer_idx - dtt_roof, dtt_roof > 0, roof_enabled ? support_roof_layers - dtt_roof : 0); } } }); @@ -1285,14 +1290,18 @@ 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 + tbb::parallel_for(tbb::blocked_range(0, bucket_count), + [&](const tbb::blocked_range &range) { + for (size_t idx = range.begin(); idx < range.end(); ++ idx) { { - 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); + // +=2 as in the beginning only uneven buckets will be filled + size_t bucket_idx = 2 * idx + 1; + for (const std::pair& input_pair : buckets_area[bucket_idx]) + { + AABB outer_support_wall_aabb = AABB(input_pair.second); + outer_support_wall_aabb.expand(config.getRadius(input_pair.first)); + buckets_aabb[bucket_idx].emplace(input_pair.first, outer_support_wall_aabb); + } } }); @@ -1305,13 +1314,15 @@ void TreeSupport::mergeInfluenceAreas(std::unordered_map> erase(buckets_area.size() / 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[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(); + tbb::parallel_for(tbb::blocked_range(0, buckets_area.size() / 2), + [&](const tbb::blocked_range &range) { + for (size_t idx = range.begin(); idx < range.end(); ++ idx) { + const size_t bucket_pair_idx = idx * 2; + // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets + mergeHelper(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) @@ -1505,252 +1516,253 @@ 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) { std::mutex critical_sections; - cura::parallel_for(0, last_layer.size(), 1, - [&](const size_t idx) - { - SupportElement* parent = last_layer[idx]; + tbb::parallel_for(tbb::blocked_range(0, last_layer.size()), + [&](const tbb::blocked_range &range) { + for (const size_t idx = range.begin(); idx < range.end(); ++ idx) { + SupportElement* parent = last_layer[idx]; - SupportElement elem(parent); // also increases dtt + SupportElement elem(parent); // also increases dtt - 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 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); + Polygons to_bp_data, to_model_data; + coord_t radius = config.getCollisionRadius(elem); - // When the radius increases, the outer "support wall" of the branch will have been moved farther away from the center (as this is the definition of radius). - // As it is not specified that the support_tree_angle has to be one of the center of the branch, it is here seen as the smaller angle of the outer wall of the branch, to the outer wall of the same branch one layer above. - // As the branch may have become larger the distance between these 2 walls is smaller than the distance of the center points. - // These extra distance is added to the movement distance possible for this layer. + // When the radius increases, the outer "support wall" of the branch will have been moved farther away from the center (as this is the definition of radius). + // As it is not specified that the support_tree_angle has to be one of the center of the branch, it is here seen as the smaller angle of the outer wall of the branch, to the outer wall of the same branch one layer above. + // As the branch may have become larger the distance between these 2 walls is smaller than the distance of the center points. + // These extra distance is added to the movement distance possible for this layer. - coord_t extra_speed = 5; // The extra speed is added to both movement distances. Also move 5 microns faster than allowed to avoid rounding errors, this may cause issues at VERY VERY small layer heights. - coord_t extra_slow_speed = 0; // Only added to the slow movement distance. - const coord_t ceiled_parent_radius = 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); + coord_t extra_speed = 5; // The extra speed is added to both movement distances. Also move 5 microns faster than allowed to avoid rounding errors, this may cause issues at VERY VERY small layer heights. + coord_t extra_slow_speed = 0; // Only added to the slow movement distance. + const coord_t ceiled_parent_radius = 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 - extra_speed += projected_radius_delta; - } - else - { - // if a guaranteed radius increase is not possible, only increase the slow speed - extra_slow_speed += std::min(projected_radius_delta, (config.maximum_move_distance + extra_speed) - (config.maximum_move_distance_slow + extra_slow_speed)); // Ensure that the slow movement distance can not become larger than the fast one. - } - - if (config.layer_start_bp_radius > layer_idx && config.recommendedMinRadius(layer_idx - 1) < config.getRadius(elem.effective_radius_height + 1, elem.elephant_foot_increases)) - { - // can guarantee elephant foot radius increase - if (ceiled_parent_radius == volumes_.ceilRadius(config.getRadius(parent->effective_radius_height + 1, parent->elephant_foot_increases + 1), parent->use_min_xy_dist)) + // 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) { - extra_speed += config.branch_radius * config.diameter_scale_bp_radius; + // If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of the slower moving wall + extra_speed += projected_radius_delta; } else { - extra_slow_speed += std::min(coord_t(config.branch_radius * config.diameter_scale_bp_radius), config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed)); + // if a guaranteed radius increase is not possible, only increase the slow speed + extra_slow_speed += std::min(projected_radius_delta, (config.maximum_move_distance + extra_speed) - (config.maximum_move_distance_slow + extra_slow_speed)); // Ensure that the slow movement distance can not become larger than the fast one. } - } - const coord_t fast_speed = config.maximum_move_distance + extra_speed; - const coord_t slow_speed = config.maximum_move_distance_slow + extra_speed + extra_slow_speed; - - Polygons offset_slow, offset_fast; - - bool add = false; - bool bypass_merge = false; - constexpr bool increase_radius = true, no_error = true, use_min_radius = true, move = true; // aliases for better readability - - // Determine in which order configurations are checked if they result in a valid influence area. Check will stop if a valid area is found - std::deque order; - std::function insertSetting = [&](AreaIncreaseSettings settings, bool back) - { - if (std::find(order.begin(), order.end(), settings) == order.end()) + if (config.layer_start_bp_radius > layer_idx && config.recommendedMinRadius(layer_idx - 1) < config.getRadius(elem.effective_radius_height + 1, elem.elephant_foot_increases)) { - if (back) + // can guarantee elephant foot radius increase + if (ceiled_parent_radius == volumes_.ceilRadius(config.getRadius(parent->effective_radius_height + 1, parent->elephant_foot_increases + 1), parent->use_min_xy_dist)) { - order.emplace_back(settings); + extra_speed += config.branch_radius * config.diameter_scale_bp_radius; } else { - order.emplace_front(settings); + extra_slow_speed += std::min(coord_t(config.branch_radius * config.diameter_scale_bp_radius), config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed)); } } - }; - const bool parent_moved_slow = elem.last_area_increase.increase_speed < config.maximum_move_distance; - const bool avoidance_speed_mismatch = parent_moved_slow && elem.last_area_increase.type != AvoidanceType::SLOW; - if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && !mergelayer && !avoidance_speed_mismatch && (elem.distance_to_top >= config.tip_layers || parent_moved_slow)) - { - // assume that the avoidance type that was best for the parent is best for me. Makes this function about 7% faster. - insertSetting(AreaIncreaseSettings(elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move), true); - insertSetting(AreaIncreaseSettings(elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, !increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move), true); - } - // branch may still go though a hole, so a check has to be done whether the hole was already passed, and the regular avoidance can be used. - if (!elem.can_use_safe_radius) - { - // if the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. - // order.emplace_back(AvoidanceType::SLOW,!increase_radius,no_error,!use_min_radius,move); - insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, !use_min_radius, !move), true); // did we go through the hole - // in many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. This CAN cause a branch to go though a hole it otherwise may have avoided. - if (elem.distance_to_top < round_up_divide(config.tip_layers, 2)) - { - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, slow_speed, increase_radius, no_error, !use_min_radius, !move), true); - } - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, !move), true); // did we manage to avoid the hole - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); - } - else - { - 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 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) - { - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, slow_speed, increase_radius, no_error, !use_min_radius, move), true); - } - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, move), true); // b - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); - } + const coord_t fast_speed = config.maximum_move_distance + extra_speed; + const coord_t slow_speed = config.maximum_move_distance_slow + extra_speed + extra_slow_speed; - if (elem.use_min_xy_dist) - { - std::deque new_order; - // if the branch currently has to use min_xy_dist check if the configuration would also be valid with the regular xy_distance before checking with use_min_radius (Only happens when Support Distance priority is z overrides xy ) - for (AreaIncreaseSettings settings : order) - { - new_order.emplace_back(settings); - new_order.emplace_back(settings.type, settings.increase_speed, settings.increase_radius, settings.no_error, use_min_radius, settings.move); - } - order = new_order; - } - if (elem.to_buildplate || (elem.to_model_gracious && (parent->area->intersection(volumes_.getPlaceableAreas(radius, layer_idx)).empty()))) // error case - { - // it is normal that we wont be able to find a new area at some point in time if we wont be able to reach layer 0 aka have to connect with the model - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move), true); - } - if (elem.distance_to_top < elem.dont_move_until && elem.can_use_safe_radius) // only do not move when holes would be avoided in every case. - { - insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, 0, increase_radius, no_error, !use_min_radius, !move), false); // Only do not move when already in a no hole avoidance with the regular xy distance. - } + Polygons offset_slow, offset_fast; - 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) - { - if (settings.move) + bool add = false; + bool bypass_merge = false; + constexpr bool increase_radius = true, no_error = true, use_min_radius = true, move = true; // aliases for better readability + + // Determine in which order configurations are checked if they result in a valid influence area. Check will stop if a valid area is found + std::deque order; + std::function insertSetting = [&](AreaIncreaseSettings settings, bool back) { - if (offset_slow.empty() && (settings.increase_speed == slow_speed || !offset_independant_faster)) + if (std::find(order.begin(), order.end(), settings) == order.end()) { - 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()) - { - if (offset_independant_faster) + if (back) { - 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(); + order.emplace_back(settings); } 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(); + order.emplace_front(settings); } } - } - std::optional result; - if (!settings.no_error) // ERROR CASE + }; + + const bool parent_moved_slow = elem.last_area_increase.increase_speed < config.maximum_move_distance; + const bool avoidance_speed_mismatch = parent_moved_slow && elem.last_area_increase.type != AvoidanceType::SLOW; + if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && !mergelayer && !avoidance_speed_mismatch && (elem.distance_to_top >= config.tip_layers || parent_moved_slow)) { - // 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 = 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); - showError("Potentially lost branch!", true); + // assume that the avoidance type that was best for the parent is best for me. Makes this function about 7% faster. + insertSetting(AreaIncreaseSettings(elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move), true); + insertSetting(AreaIncreaseSettings(elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, !increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move), true); + } + // branch may still go though a hole, so a check has to be done whether the hole was already passed, and the regular avoidance can be used. + if (!elem.can_use_safe_radius) + { + // if the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. + // order.emplace_back(AvoidanceType::SLOW,!increase_radius,no_error,!use_min_radius,move); + insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, !use_min_radius, !move), true); // did we go through the hole + // in many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. This CAN cause a branch to go though a hole it otherwise may have avoided. + if (elem.distance_to_top < round_up_divide(config.tip_layers, 2)) + { + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, slow_speed, increase_radius, no_error, !use_min_radius, !move), true); + } + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, !move), true); // did we manage to avoid the hole + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); } else { - result = increaseSingleArea(settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); + 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 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) + { + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, slow_speed, increase_radius, no_error, !use_min_radius, move), true); + } + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, move), true); // b + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); } - if (result) + if (elem.use_min_xy_dist) + { + std::deque new_order; + // if the branch currently has to use min_xy_dist check if the configuration would also be valid with the regular xy_distance before checking with use_min_radius (Only happens when Support Distance priority is z overrides xy ) + for (AreaIncreaseSettings settings : order) + { + new_order.emplace_back(settings); + new_order.emplace_back(settings.type, settings.increase_speed, settings.increase_radius, settings.no_error, use_min_radius, settings.move); + } + order = new_order; + } + if (elem.to_buildplate || (elem.to_model_gracious && (parent->area->intersection(volumes_.getPlaceableAreas(radius, layer_idx)).empty()))) // error case + { + // it is normal that we wont be able to find a new area at some point in time if we wont be able to reach layer 0 aka have to connect with the model + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move), true); + } + if (elem.distance_to_top < elem.dont_move_until && elem.can_use_safe_radius) // only do not move when holes would be avoided in every case. + { + insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, 0, increase_radius, no_error, !use_min_radius, !move), false); // Only do not move when already in a no hole avoidance with the regular xy distance. + } + + 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) { - elem = result.value(); - radius = config.getCollisionRadius(elem); - elem.last_area_increase = settings; - add = true; - bypass_merge = !settings.move || (settings.use_min_distance && elem.distance_to_top < config.tip_layers); // do not merge if the branch should not move or the priority has to be to get farther away from the model. if (settings.move) { - elem.dont_move_until = 0; + 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()) + { + 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 = 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); + showError("Potentially lost branch!", true); } else { - elem.result_on_layer = parent->result_on_layer; + 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); } - elem.can_use_safe_radius = settings.type != AvoidanceType::FAST; - - if (!settings.use_min_distance) + if (result) { - elem.use_min_xy_dist = false; - } - if (!settings.no_error) - { - logError("Trying to keep area by moving faster than intended: Success \n"); - } - break; - } - else if (!settings.no_error) - { - logError("Trying to keep area by moving faster than intended: FAILURE! WRONG BRANCHES LIKLY! \n"); - } - } - - if (add) - { - 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 - - { - std::lock_guard critical_section_newLayer(critical_sections); - if (bypass_merge) - { - Polygons* new_area = new Polygons(max_influence_area); - SupportElement* next = new SupportElement(elem, new_area); - bypass_merge_areas.emplace_back(next); - } - else - { - influence_areas.emplace(elem, max_influence_area); - if (elem.to_buildplate) + elem = result.value(); + radius = config.getCollisionRadius(elem); + elem.last_area_increase = settings; + add = true; + bypass_merge = !settings.move || (settings.use_min_distance && elem.distance_to_top < config.tip_layers); // do not merge if the branch should not move or the priority has to be to get farther away from the model. + if (settings.move) { - to_bp_areas.emplace(elem, to_bp_data); + elem.dont_move_until = 0; } - if (config.support_rests_on_model) + else { - to_model_areas.emplace(elem, to_model_data); + elem.result_on_layer = parent->result_on_layer; + } + + elem.can_use_safe_radius = settings.type != AvoidanceType::FAST; + + if (!settings.use_min_distance) + { + elem.use_min_xy_dist = false; + } + if (!settings.no_error) + { + logError("Trying to keep area by moving faster than intended: Success \n"); + } + break; + } + else if (!settings.no_error) + { + logError("Trying to keep area by moving faster than intended: FAILURE! WRONG BRANCHES LIKLY! \n"); + } + } + + if (add) + { + 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 + + { + std::lock_guard critical_section_newLayer(critical_sections); + if (bypass_merge) + { + Polygons* new_area = new Polygons(max_influence_area); + SupportElement* next = new SupportElement(elem, new_area); + bypass_merge_areas.emplace_back(next); + } + else + { + influence_areas.emplace(elem, max_influence_area); + if (elem.to_buildplate) + { + to_bp_areas.emplace(elem, to_bp_data); + } + if (config.support_rests_on_model) + { + to_model_areas.emplace(elem, to_model_data); + } } } } - } - else - { - parent->result_on_layer = Point(-1, -1); // If the bottom most point of a branch is set, later functions will assume that the position is valid, and ignore it. But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. A point can be set on the top most tip layer (maybe more if it should not move for a few layers). + else + { + 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). + } } }); } @@ -2052,117 +2064,119 @@ void TreeSupport::generateBranchAreas(std::vector(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 = inverse_tree_order.count(elem) ? inverse_tree_order.at(elem) : nullptr; + tbb::parallel_for(tbb::blocked_range(0, linear_data.size()), + [&](const tbb::blocked_range &range) { + for (const size_t idx = range.begin(); idx < range.end(); ++ idx) { - // Calculate multiple ovalized circles, to connect with every parent and child. Also generate regular circle for the current layer. Merge all these into one area. - std::vector> movement_directions{ std::pair(Point(0, 0), radius) }; - if (!elem->skip_ovalisation) - { - if (child_elem != nullptr) + SupportElement* elem = linear_data[idx].second; + coord_t radius = config.getRadius(*elem); + bool parent_uses_min = false; + 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 (!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; - } - } - - coord_t max_speed = 0; - std::function generateArea = [&](coord_t offset) - { - Polygons poly; - - for (std::pair movement : movement_directions) - { - max_speed = std::max(max_speed, vSize(movement.first)); - - // Visualization: https://jsfiddle.net/0zvcq39L/2/ - // Ovalizes the circle to an ellipse, that contains both old center and new target position. - double used_scale = (movement.second + offset) / (1.0 * config.branch_radius); - Point center_position = elem->result_on_layer + movement.first / 2; - const double moveX = movement.first.X / (used_scale * config.branch_radius); - const double moveY = movement.first.Y / (used_scale * config.branch_radius); - const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); - - double matrix[] = { - used_scale * (1 + moveX * moveX * vsize_inv), - used_scale * (0 + moveX * moveY * vsize_inv), - used_scale * (0 + moveX * moveY * vsize_inv), - used_scale * (1 + moveY * moveY * vsize_inv), - }; - Polygon circle; - for (Point vertex : branch_circle) + if (child_elem != nullptr) { - vertex = Point(matrix[0] * vertex.X + matrix[1] * vertex.Y, matrix[2] * vertex.X + matrix[3] * vertex.Y); - circle.add(center_position + vertex); + 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; } - poly.add(circle.offset(0)); } - 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; - }; - - - bool fast_relative_movement = max_speed > 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[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[idx].offset(-config.support_line_width / 2); - if (nozzle_path.splitIntoParts(false).size() > 1) + coord_t max_speed = 0; + std::function generateArea = [&](coord_t offset) { - // Just try to make the area a tiny bit larger. - linear_inserts[idx] = generateArea(config.support_line_width / 2); - nozzle_path = linear_inserts[idx].offset(-config.support_line_width / 2); + Polygons poly; - // 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 + for (std::pair movement : movement_directions) + { + max_speed = std::max(max_speed, vSize(movement.first)); + + // Visualization: https://jsfiddle.net/0zvcq39L/2/ + // Ovalizes the circle to an ellipse, that contains both old center and new target position. + double used_scale = (movement.second + offset) / (1.0 * config.branch_radius); + Point center_position = elem->result_on_layer + movement.first / 2; + const double moveX = movement.first.X / (used_scale * config.branch_radius); + const double moveY = movement.first.Y / (used_scale * config.branch_radius); + const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); + + double matrix[] = { + used_scale * (1 + moveX * moveX * vsize_inv), + used_scale * (0 + moveX * moveY * vsize_inv), + used_scale * (0 + moveX * moveY * vsize_inv), + used_scale * (1 + moveY * moveY * vsize_inv), + }; + Polygon circle; + for (Point vertex : branch_circle) + { + vertex = Point(matrix[0] * vertex.X + matrix[1] * vertex.Y, matrix[2] * vertex.X + matrix[3] * vertex.Y); + circle.add(center_position + vertex); + } + poly.add(circle.offset(0)); + } + + 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; + }; + + + bool fast_relative_movement = max_speed > 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[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[idx].offset(-config.support_line_width / 2); if (nozzle_path.splitIntoParts(false).size() > 1) { - Polygons polygons_with_correct_center; - for (PolygonsPart part : nozzle_path.splitIntoParts(false)) + // Just try to make the area a tiny bit larger. + 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) { - if (part.inside(elem->result_on_layer, true)) + Polygons polygons_with_correct_center; + for (PolygonsPart part : nozzle_path.splitIntoParts(false)) { - polygons_with_correct_center = polygons_with_correct_center.unionPolygons(part); - } - else - { - // try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... - Point from = elem->result_on_layer; - PolygonUtils::moveInside(part, from, 0); - if (vSize(elem->result_on_layer - from) < 25) + if (part.inside(elem->result_on_layer, true)) { polygons_with_correct_center = polygons_with_correct_center.unionPolygons(part); } + else + { + // try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... + Point from = elem->result_on_layer; + PolygonUtils::moveInside(part, from, 0); + if (vSize(elem->result_on_layer - from) < 25) + { + polygons_with_correct_center = polygons_with_correct_center.unionPolygons(part); + } + } } + 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(); } - 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 (idx % progress_inserts_check_interval == 0) - { + if (idx % progress_inserts_check_interval == 0) { - 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); + { + 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); + } } } }); @@ -2185,30 +2199,32 @@ 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 - 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; - bool do_something = false; - for (SupportElement* parent : data_pair.first->parents) - { - if (config.getRadius(*parent) != config.getCollisionRadius(*parent)) - { - do_something = true; - max_outer_wall_distance = std::max(max_outer_wall_distance, vSize(data_pair.first->result_on_layer - parent->result_on_layer) - (config.getRadius(*data_pair.first) - config.getRadius(*parent))); - } - } - max_outer_wall_distance += max_radius_change_per_layer; // As this change is a bit larger than what usually appears, lost radius can be slowly reclaimed over the layers. - if (do_something) - { - Polygons max_allowed_area = data_pair.second.offset(max_outer_wall_distance); + tbb::parallel_for(tbb::blocked_range(0, processing.size()), + [&](const tbb::blocked_range &range) { + for (const size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { + std::pair data_pair = processing[processing_idx]; + + coord_t max_outer_wall_distance = 0; + bool do_something = false; for (SupportElement* parent : data_pair.first->parents) { if (config.getRadius(*parent) != config.getCollisionRadius(*parent)) { - update_next[processing_idx].emplace_back(std::pair(parent, layer_tree_polygons[layer_idx + 1][parent].intersection(max_allowed_area))); + do_something = true; + max_outer_wall_distance = std::max(max_outer_wall_distance, vSize(data_pair.first->result_on_layer - parent->result_on_layer) - (config.getRadius(*data_pair.first) - config.getRadius(*parent))); + } + } + max_outer_wall_distance += max_radius_change_per_layer; // As this change is a bit larger than what usually appears, lost radius can be slowly reclaimed over the layers. + if (do_something) + { + Polygons max_allowed_area = data_pair.second.offset(max_outer_wall_distance); + for (SupportElement* parent : data_pair.first->parents) + { + if (config.getRadius(*parent) != config.getCollisionRadius(*parent)) + { + update_next[processing_idx].emplace_back(std::pair(parent, layer_tree_polygons[layer_idx + 1][parent].intersection(max_allowed_area))); + } } } } @@ -2234,36 +2250,37 @@ void TreeSupport::smoothBranchAreas(std::vector> update_next(processing.size(), std::pair(nullptr, Polygons())); // with this a lock can be avoided - 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; - for (size_t idx = 0; idx < data_pair.first->parents.size(); idx++) - { - SupportElement* parent = data_pair.first->parents[idx]; - coord_t max_outer_line_increase = max_radius_change_per_layer; - Polygons result = layer_tree_polygons[layer_idx + 1][parent].offset(max_outer_line_increase); - Point direction = data_pair.first->result_on_layer - parent->result_on_layer; - // move the polygons object - for (auto& outer : result) + tbb::parallel_for(tbb::blocked_range(0, processing.size()), + [&](const tbb::blocked_range &range) { + for (const size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { + std::pair data_pair = processing[processing_idx]; + bool do_something = false; + Polygons max_allowed_area; + for (size_t idx = 0; idx < data_pair.first->parents.size(); idx++) { - for (Point& p : outer) + SupportElement* parent = data_pair.first->parents[idx]; + coord_t max_outer_line_increase = max_radius_change_per_layer; + Polygons result = layer_tree_polygons[layer_idx + 1][parent].offset(max_outer_line_increase); + Point direction = data_pair.first->result_on_layer - parent->result_on_layer; + // move the polygons object + for (auto& outer : result) { - p += direction; + for (Point& p : outer) + { + p += direction; + } } + max_allowed_area.add(result); + do_something = do_something || updated_last_iteration.count(parent) || config.getCollisionRadius(*parent) != config.getRadius(*parent); } - max_allowed_area.add(result); - do_something = do_something || updated_last_iteration.count(parent) || config.getCollisionRadius(*parent) != config.getRadius(*parent); - } - if (do_something) - { - Polygons result = max_allowed_area.unionPolygons().intersection(data_pair.second); - if (result.area() < data_pair.second.area()) + if (do_something) { - update_next[processing_idx] = std::pair(data_pair.first, result); + Polygons result = max_allowed_area.unionPolygons().intersection(data_pair.second); + if (result.area() < data_pair.second.area()) + { + update_next[processing_idx] = std::pair(data_pair.first, result); + } } } }); @@ -2286,21 +2303,22 @@ void TreeSupport::smoothBranchAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, const std::map& inverse_tree_order) { - 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 && !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. + tbb::parallel_for(tbb::blocked_range(0, linear_data.size()), + [&](const tbb::blocked_range &range) { + for (const size_t idx = range.begin(); idx < range.end(); ++ idx) { + SupportElement* elem = linear_data[idx].second; + bool non_gracious_model_contact = !elem->to_model_gracious && !inverse_tree_order.count(elem); // if a element has no child, it connects to whatever is below as no support further down for it will exist. - if (non_gracious_model_contact) - { - Polygons rest_support = layer_tree_polygons[linear_data[idx].first][elem]; - LayerIndex counter = 1; - while (rest_support.area() > 1 && counter < linear_data[idx].first) + if (non_gracious_model_contact) { - rest_support = rest_support.difference(volumes_.getCollision(0, linear_data[idx].first - counter)); - dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support); - counter++; + Polygons rest_support = layer_tree_polygons[linear_data[idx].first][elem]; + LayerIndex counter = 1; + while (rest_support.area() > 1 && counter < linear_data[idx].first) + { + rest_support = rest_support.difference(volumes_.getCollision(0, linear_data[idx].first - counter)); + dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support); + counter++; + } } } }); @@ -2314,85 +2332,86 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& suppor // Iterate over the generated circles in parallel and clean them up. Also add support floor. 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 - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(support_roof_storage[layer_idx]); - if (!storage.support.supportLayers[layer_idx].support_roof.empty() && support_layer_storage[layer_idx].intersection(storage.support.supportLayers[layer_idx].support_roof).area() > 1) - { - switch (interface_pref) + tbb::parallel_for(tbb::blocked_range(0, support_layer_storage.size()), + [&](const tbb::blocked_range &range) { + for (const size_t layer_idx = range.begin(); layer_idx < range.end(); ++ 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 + storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(support_roof_storage[layer_idx]); + if (!storage.support.supportLayers[layer_idx].support_roof.empty() && support_layer_storage[layer_idx].intersection(storage.support.supportLayers[layer_idx].support_roof).area() > 1) { - 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 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 InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT: + switch (interface_pref) { - 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 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)); - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.difference(tree_lines); // do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. - } + 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 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 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 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)); + storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.difference(tree_lines); // do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. + } - break; - case InterfacePreference::NOTHING: - break; - } - } - - // Subtract support floors from the support area and add them to the support floor instead. - if (config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) - { - Polygons floor_layer = storage.support.supportLayers[layer_idx].support_bottom; - Polygons layer_outset = support_layer_storage[layer_idx].offset(config.support_bottom_offset).difference(volumes_.getCollision(0, layer_idx, false)); - size_t layers_below = 0; - while (layers_below <= config.support_bottom_layers) - { - // one sample at 0 layers below, another at config.support_bottom_layers. In-between samples at config.performance_interface_skip_layers distance from each other. - const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(config.z_distance_bottom_layers))); - constexpr bool no_support = false; - constexpr bool no_prime_tower = false; - floor_layer.add(layer_outset.intersection(storage.getLayerOutlines(sample_layer, no_support, no_prime_tower))); - if (layers_below < config.support_bottom_layers) - { - layers_below = std::min(layers_below + config.performance_interface_skip_layers, config.support_bottom_layers); - } - else - { break; + case InterfacePreference::NOTHING: + break; } } - floor_layer = floor_layer.unionPolygons(); - storage.support.supportLayers[layer_idx].support_bottom = storage.support.supportLayers[layer_idx].support_bottom.unionPolygons(floor_layer); - support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(floor_layer.offset(10)); // Subtract the support floor from the normal support. - } - for (PolygonsPart part : support_layer_storage[layer_idx].splitIntoParts(true)) // Convert every part into a PolygonsPart for the support. - { - storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(part, config.support_line_width, config.support_wall_count); - } - - { - 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); - } - - { - 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()) + // Subtract support floors from the support area and add them to the support floor instead. + if (config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) { - storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, static_cast(layer_idx)); + Polygons floor_layer = storage.support.supportLayers[layer_idx].support_bottom; + Polygons layer_outset = support_layer_storage[layer_idx].offset(config.support_bottom_offset).difference(volumes_.getCollision(0, layer_idx, false)); + size_t layers_below = 0; + while (layers_below <= config.support_bottom_layers) + { + // one sample at 0 layers below, another at config.support_bottom_layers. In-between samples at config.performance_interface_skip_layers distance from each other. + const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(config.z_distance_bottom_layers))); + constexpr bool no_support = false; + constexpr bool no_prime_tower = false; + floor_layer.add(layer_outset.intersection(storage.getLayerOutlines(sample_layer, no_support, no_prime_tower))); + if (layers_below < config.support_bottom_layers) + { + layers_below = std::min(layers_below + config.performance_interface_skip_layers, config.support_bottom_layers); + } + else + { + break; + } + } + floor_layer = floor_layer.unionPolygons(); + storage.support.supportLayers[layer_idx].support_bottom = storage.support.supportLayers[layer_idx].support_bottom.unionPolygons(floor_layer); + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(floor_layer.offset(10)); // Subtract the support floor from the normal support. + } + + for (PolygonsPart part : support_layer_storage[layer_idx].splitIntoParts(true)) // Convert every part into a PolygonsPart for the support. + { + storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(part, config.support_line_width, config.support_wall_count); + } + + { + 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); + } + + { + 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)); + } } } }); From 23099c83dc9abf2d569a47e7cf702c2500b2fa26 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 20 Jul 2022 11:21:53 +0200 Subject: [PATCH 05/29] WIP Tree Supports: Extracted Cura configs into a structure, replaced Cura AABB with BoundingBox, headers should be compilable now. --- src/libslic3r/Point.hpp | 7 ++ src/libslic3r/TreeModelVolumes.cpp | 8 +- src/libslic3r/TreeModelVolumes.hpp | 61 +++++++++-- src/libslic3r/TreeSupport.cpp | 55 +++++----- src/libslic3r/TreeSupport.hpp | 159 +++++++++++++++-------------- 5 files changed, 176 insertions(+), 114 deletions(-) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 06cf34503..c6dfb1223 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -511,6 +511,13 @@ inline Point align_to_grid(Point coord, Point spacing, Point base) } // namespace Slic3r +namespace std { + template <> struct hash { + size_t operator()(const Slic3r::Point &p) const + { return (89 * 31 + p.x()) * 31 + p.y(); } + }; +} + // start Boost #include #include diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index c8cd87c40..4f445a307 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -52,7 +52,7 @@ TreeModelVolumes::TreeModelVolumes(const SliceDataStorage& storage, const coord_ 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) + if (! config.support_xy_overrides_z) { current_min_xy_dist = config.xy_min_distance; @@ -76,7 +76,7 @@ TreeModelVolumes::TreeModelVolumes(const SliceDataStorage& storage, const coord_ tbb::parallel_for(tbb::blocked_range(0, layer_outlines_[mesh_to_layeroutline_idx[mesh_idx]].second.size()), [&](const tbb::blocked_range &range) { - for (const size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ 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 @@ -89,7 +89,7 @@ TreeModelVolumes::TreeModelVolumes(const SliceDataStorage& storage, const coord_ tbb::parallel_for(tbb::blocked_range(0, anti_overhang_.size()), [&](const tbb::blocked_range &range) { - for (const size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { if (layer_idx < coord_t(additional_excluded_areas.size())) { anti_overhang_[layer_idx].add(additional_excluded_areas[layer_idx]); @@ -501,7 +501,7 @@ coord_t TreeModelVolumes::getRadiusNextCeil(coord_t radius, bool min_xy_dist) co return ceiled_radius; } -bool TreeModelVolumes::checkSettingsEquality(const Settings& me, const Settings& other) const +bool TreeModelVolumes::checkSettingsEquality(const TreeSupportMeshGroupSettings& me, const TreeSupportMeshGroupSettings& other) const { return TreeSupport::TreeSupportSettings(me) == TreeSupport::TreeSupportSettings(other); } diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 551dfdddc..684dd1fe9 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -13,9 +13,58 @@ #include #include +#include "Polygon.hpp" +#include "PrintConfig.hpp" + namespace Slic3r { +using LayerIndex = size_t; +using AngleRadians = double; + +//FIXME +class SliceDataStorage; +class SliceMeshStorage; + +struct TreeSupportMeshGroupSettings { + AngleRadians support_tree_angle; + AngleRadians support_tree_angle_slow; + AngleRadians support_tree_branch_diameter_angle; + coord_t support_tree_bp_diameter; + coord_t support_tree_max_diameter_increase_by_merges_when_support_to_model; + coord_t support_tree_min_height_to_model; + coord_t support_line_width; + coord_t layer_height; + coord_t support_tree_branch_diameter; + coord_t support_tree_tip_diameter; + bool support_bottom_enable; + coord_t support_bottom_height; + bool support_material_buildplate_only; + bool support_xy_overrides_z; + coord_t support_xy_distance; + coord_t support_xy_distance_overhang; + coord_t support_top_distance; + coord_t support_bottom_distance; + coord_t support_interface_skip_height; + std::vector support_infill_angles; + std::vector support_roof_angles; + SupportMaterialInterfacePattern support_roof_pattern; + SupportMaterialPattern support_pattern; + coord_t support_roof_line_width; + coord_t support_line_distance; + coord_t support_bottom_offset; + int support_wall_count; + coord_t meshfix_maximum_deviation; + coord_t meshfix_maximum_resolution; + coord_t support_roof_line_distance; + coord_t min_feature_size; +}; + +inline coord_t round_up_divide(const coord_t dividend, const coord_t divisor) //!< Return dividend divided by divisor rounded to the nearest integer +{ + return (dividend + divisor - 1) / divisor; +} + class TreeModelVolumes { public: @@ -208,7 +257,7 @@ class TreeModelVolumes */ void calculateAvoidance(RadiusLayerPair key) { - calculateAvoidance(std::deque{ RadiusLayerPair(key) }).wait(); + calculateAvoidance(std::deque{ RadiusLayerPair(key) }); } /*! @@ -218,7 +267,7 @@ class TreeModelVolumes */ void calculatePlaceables(RadiusLayerPair key) { - calculatePlaceables(std::deque{ key }).wait(); + calculatePlaceables(std::deque{ key }); } /*! @@ -246,7 +295,7 @@ class TreeModelVolumes */ void calculateAvoidanceToModel(RadiusLayerPair key) { - calculateAvoidanceToModel(std::deque{ RadiusLayerPair(key) }).wait(); + 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). @@ -264,7 +313,7 @@ class TreeModelVolumes */ void calculateWallRestrictions(RadiusLayerPair key) { - calculateWallRestrictions(std::deque{ RadiusLayerPair(key) }).wait(); + calculateWallRestrictions(std::deque{ RadiusLayerPair(key) }); } /*! @@ -274,7 +323,7 @@ class TreeModelVolumes */ template const std::optional> getArea(const std::unordered_map& cache, const KEY key) const; - bool checkSettingsEquality(const Settings& me, const Settings& other) const; + bool checkSettingsEquality(const TreeSupportMeshGroupSettings& me, const TreeSupportMeshGroupSettings& 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. @@ -349,7 +398,7 @@ class TreeModelVolumes /*! * \brief Storage for layer outlines and the corresponding settings of the meshes grouped by meshes with identical setting. */ - std::vector>> layer_outlines_; + std::vector>> layer_outlines_; /*! * \brief Storage for areas that should be avoided, like support blocker or previous generated trees. */ diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index d8a0d7983..1ed7d2810 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -97,7 +97,7 @@ std::string getPolygonAsString(const Polygons& poly) { if (ret != "") ret += ","; - ret += "(" + std::to_string(p.X) + "," + std::to_string(p.Y) + ")"; + ret += "(" + std::to_string(p.x()) + "," + std::to_string(p.y()) + ")"; } } return ret; @@ -248,7 +248,7 @@ void TreeSupport::precalculate(const SliceDataStorage& storage, std::vector TreeSupport::convertLinesToInternal(Polygons polylines, LayerIndex layer_idx) { - const bool xy_overrides = config.support_overrides == SupportDistPriority::XY_OVERRIDES_Z; + const bool xy_overrides = config.support_xy_overrides_z; std::vector result; // Also checks if the position is valid, if it is NOT, it deletes that point @@ -315,7 +315,7 @@ Polygons TreeSupport::convertInternalToLines(std::vector)> TreeSupport::getEvaluatePointForNextLayerFunction(size_t current_layer) { - const bool xy_overrides = config.support_overrides == SupportDistPriority::XY_OVERRIDES_Z; + const bool xy_overrides = config.support_xy_overrides_z; std::function)> evaluatePoint = [=](std::pair p) { if (!volumes_.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, false, !xy_overrides).inside(p.first, true)) @@ -563,7 +563,7 @@ Polygons TreeSupport::generateSupportInfillLines(const Polygons& area, bool roof const EFillMethod pattern = roof ? config.roof_pattern : config.support_pattern; - const bool zig_zaggify_infill = roof ? pattern == EFillMethod::ZIG_ZAG : config.zig_zaggify_support; +// const bool zig_zaggify_infill = roof ? pattern == EFillMethod::ZIG_ZAG : config.zig_zaggify_support; const bool connect_polygons = false; constexpr coord_t support_roof_overlap = 0; constexpr size_t infill_multiplier = 1; @@ -574,17 +574,16 @@ Polygons TreeSupport::generateSupportInfillLines(const Polygons& area, bool roof constexpr Polygons* perimeter_gaps = nullptr; constexpr bool use_endpieces = true; const bool connected_zigzags = roof ? false : config.connect_zigzags; - const bool skip_some_zags = roof ? false : config.skip_some_zags; const size_t zag_skip_count = roof ? 0 : config.zag_skip_count; constexpr coord_t pocket_size = 0; - std::vector angles = roof ? config.support_roof_angles : config.support_infill_angles; + 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, 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); + const AngleRadians fill_angle = angles[index]; + Infill roof_computation(pattern, true /* 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, false /* skip_some_zags */, zag_skip_count, pocket_size); Polygons areas; Polygons lines; roof_computation.generate(toolpaths, areas, lines, config.settings, cross_fill_provider); @@ -670,18 +669,18 @@ void TreeSupport::generateInitialAreas(const SliceMeshStorage& mesh, std::vector 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; + const AngleRadians angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); base_circle.emplace_back(coord_t(cos(angle) * base_radius), coord_t(sin(angle) * base_radius)); } TreeSupportSettings mesh_config(mesh.settings); const size_t z_distance_delta = mesh_config.z_distance_top_layers + 1; // To ensure z_distance_top_layers are left empty between the overhang (zeroth empty layer), the support has to be added z_distance_top_layers+1 layers below - const bool xy_overrides = mesh_config.support_overrides == SupportDistPriority::XY_OVERRIDES_Z; + const bool xy_overrides = mesh_config.support_xy_overrides_z; const coord_t support_roof_line_distance = mesh.settings.get("support_roof_line_distance"); const double minimum_roof_area = mesh.settings.get("minimum_roof_area"); const double minimum_support_area = mesh.settings.get("minimum_support_area"); - const size_t support_roof_layers = mesh.settings.get("support_roof_enable") ? round_divide(mesh.settings.get("support_roof_height"), mesh_config.layer_height) : 0; + const size_t support_roof_layers = mesh.settings.get("support_roof_enable") ? (mesh.settings.get("support_roof_height") + mesh_config.layer_height / 2) / mesh_config.layer_height : 0; const bool roof_enabled = support_roof_layers != 0; const bool only_gracious = SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL; const EFillMethod support_pattern = mesh.settings.get("support_pattern"); @@ -693,7 +692,7 @@ void TreeSupport::generateInitialAreas(const SliceMeshStorage& mesh, std::vector 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. const bool force_tip_to_roof = (mesh_config.min_radius * mesh_config.min_radius * M_PI > minimum_roof_area * (1000 * 1000)) && roof_enabled; const double support_overhang_angle = mesh.settings.get("support_angle"); - const coord_t max_overhang_speed = (support_overhang_angle < TAU / 4) ? (coord_t)(tan(support_overhang_angle) * mesh_config.layer_height) : std::numeric_limits::max(); + const coord_t max_overhang_speed = (support_overhang_angle < (2.0 * M_PI) / 4) ? (coord_t)(tan(support_overhang_angle) * mesh_config.layer_height) : std::numeric_limits::max(); const size_t max_overhang_insert_lag = std::max((size_t)round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers); // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. The 2*z_distance_delta is only a catch for when the support angle is very high. SierpinskiFillProvider* cross_fill_provider = generateCrossFillProvider(mesh, support_tree_branch_distance, mesh_config.support_line_width); if (mesh.overhang_areas.size() <= z_distance_delta) @@ -1062,18 +1061,18 @@ Polygons TreeSupport::safeOffsetInc(const Polygons& me, coord_t distance, const } -void TreeSupport::mergeHelper(std::map& reduced_aabb, std::map& input_aabb, const std::unordered_map& to_bp_areas, const std::unordered_map& to_model_areas, const std::map& influence_areas, std::unordered_map& insert_bp_areas, std::unordered_map& insert_model_areas, std::unordered_map& insert_influence, std::vector& erase, const LayerIndex layer_idx) +void TreeSupport::mergeHelper(std::map& reduced_aabb, std::map& input_aabb, const std::unordered_map& to_bp_areas, const std::unordered_map& to_model_areas, const std::map& influence_areas, std::unordered_map& insert_bp_areas, std::unordered_map& insert_model_areas, std::unordered_map& insert_influence, std::vector& erase, const LayerIndex layer_idx) { const bool first_merge_iteration = reduced_aabb.empty(); // If this is the first iteration, all elements in input have to be merged with each other - for (std::map::iterator influence_iter = input_aabb.begin(); influence_iter != input_aabb.end(); influence_iter++) + for (std::map::iterator influence_iter = input_aabb.begin(); influence_iter != input_aabb.end(); influence_iter++) { bool merged = false; - AABB influence_aabb = influence_iter->second; - for (std::map::iterator reduced_check_iter = reduced_aabb.begin(); reduced_check_iter != reduced_aabb.end(); reduced_check_iter++) + BoundingBox influence_aabb = influence_iter->second; + for (std::map::iterator reduced_check_iter = reduced_aabb.begin(); reduced_check_iter != reduced_aabb.end(); reduced_check_iter++) { // As every area has to be checked for overlaps with other areas, some fast heuristic is needed to abort early if clearly possible // This is so performance critical that using a map lookup instead of the direct access of the cached AABBs can have a surprisingly large performance impact - AABB aabb = reduced_check_iter->second; + BoundingBox aabb = reduced_check_iter->second; if (aabb.hit(influence_aabb)) { if (!first_merge_iteration && input_aabb.count(reduced_check_iter->first)) @@ -1223,7 +1222,7 @@ void TreeSupport::mergeHelper(std::map& reduced_aabb, std: Polygons merge = intersect.unionPolygons(intersect_sec).offset(config.getRadius(key), ClipperLib::jtRound).difference(volumes_.getCollision(0, layer_idx - 1)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. reduced_aabb.erase(reduced_check_iter->first); // this invalidates reduced_check_iter - reduced_aabb.emplace(key, AABB(merge)); + reduced_aabb.emplace(key, BoundingBox(merge)); merged = true; break; @@ -1268,7 +1267,7 @@ void TreeSupport::mergeInfluenceAreas(std::unordered_map> buckets_area(2 * bucket_count); - std::vector> buckets_aabb(2 * bucket_count); + std::vector> buckets_aabb(2 * bucket_count); size_t position = 0, counter = 0; @@ -1298,7 +1297,7 @@ void TreeSupport::mergeInfluenceAreas(std::unordered_map& input_pair : buckets_area[bucket_idx]) { - AABB outer_support_wall_aabb = AABB(input_pair.second); + BoundingBox outer_support_wall_aabb = BoundingBox(input_pair.second); outer_support_wall_aabb.expand(config.getRadius(input_pair.first)); buckets_aabb[bucket_idx].emplace(input_pair.first, outer_support_wall_aabb); } @@ -1352,7 +1351,7 @@ void TreeSupport::mergeInfluenceAreas(std::unordered_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(); }); + 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()); } } @@ -1963,7 +1962,7 @@ bool TreeSupport::setToModelContact(std::vector>& move } checked[last_successfull_layer - layer_idx]->result_on_layer = best; - logDebug("Added gracious Support On Model Point (%lld,%lld). The current layer is %lld\n", best.X, best.Y, last_successfull_layer); + logDebug("Added gracious Support On Model Point (%lld,%lld). The current layer is %lld\n", best.x(), best.y(), last_successfull_layer); return last_successfull_layer != layer_idx; } @@ -1976,7 +1975,7 @@ bool TreeSupport::setToModelContact(std::vector>& move } first_elem->result_on_layer = best; first_elem->to_model_gracious = false; - logDebug("Added NON gracious Support On Model Point (%lld,%lld). The current layer is %lld\n", best.X, best.Y, layer_idx); + logDebug("Added NON gracious Support On Model Point (%lld,%lld). The current layer is %lld\n", best.x(), best.y(), layer_idx); return false; } } @@ -2010,7 +2009,7 @@ void TreeSupport::createNodesFromArea(std::vector>& mo { if (elem->to_buildplate) { - logError("Uninitialized Influence area targeting (%lld,%lld) at target_height: %lld layer: %lld\n", elem->target_position.X, elem->target_position.Y, elem->target_height, layer_idx); + logError("Uninitialized Influence area targeting (%lld,%lld) at target_height: %lld layer: %lld\n", elem->target_position.x(), elem->target_position.y(), elem->target_height, layer_idx); TreeSupport::showError("Uninitialized support element! A branch could be missing or exist partially.", true); } remove.emplace(elem); // we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set @@ -2056,7 +2055,7 @@ void TreeSupport::generateBranchAreas(std::vector(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * TAU; + const AngleRadians angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); branch_circle.emplace_back(coord_t(cos(angle) * config.branch_radius), coord_t(sin(angle) * config.branch_radius)); } @@ -2103,8 +2102,8 @@ void TreeSupport::generateBranchAreas(std::vectorresult_on_layer + movement.first / 2; - const double moveX = movement.first.X / (used_scale * config.branch_radius); - const double moveY = movement.first.Y / (used_scale * config.branch_radius); + const double moveX = movement.first.x() / (used_scale * config.branch_radius); + const double moveY = movement.first.y() / (used_scale * config.branch_radius); const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); double matrix[] = { @@ -2116,7 +2115,7 @@ void TreeSupport::generateBranchAreas(std::vector // For combining hashes +#include "BoundingBox.hpp" #define SUPPORT_TREE_CIRCLE_RESOLUTION 25 // The number of vertices in each circle. @@ -41,6 +43,11 @@ namespace Slic3r { +using LayerIndex = size_t; + +//FIXME +class SierpinskiFillProvider; + /*! * \brief Generates a tree structure to support your models. @@ -323,7 +330,7 @@ class TreeSupport return other.target_height < target_height; } - return other.target_position.X == target_position.X ? other.target_position.Y < target_position.Y : other.target_position.X < target_position.X; + return other.target_position.x() == target_position.x() ? other.target_position.y() < target_position.y() : other.target_position.x() < target_position.x(); } void AddParents(const std::vector& adding) @@ -342,48 +349,44 @@ class TreeSupport { TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupport class. - TreeSupportSettings(const Settings& mesh_group_settings) - : angle(mesh_group_settings.get("support_tree_angle")), - angle_slow(mesh_group_settings.get("support_tree_angle_slow")), - support_line_width(mesh_group_settings.get("support_line_width")), - layer_height(mesh_group_settings.get("layer_height")), - branch_radius(mesh_group_settings.get("support_tree_branch_diameter") / 2), - min_radius(mesh_group_settings.get("support_tree_tip_diameter") / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance - maximum_move_distance((angle < 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), + TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings) + : angle(mesh_group_settings.support_tree_angle), + angle_slow(mesh_group_settings.support_tree_angle_slow), + support_line_width(mesh_group_settings.support_line_width), + layer_height(mesh_group_settings.layer_height), + branch_radius(mesh_group_settings.support_tree_branch_diameter / 2), + min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance + maximum_move_distance((angle < M_PI / 2.) ? (coord_t)(tan(angle) * layer_height) : std::numeric_limits::max()), + maximum_move_distance_slow((angle_slow < M_PI / 2.) ? (coord_t)(tan(angle_slow) * layer_height) : std::numeric_limits::max()), + support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0), tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // Ensure lines always stack nicely even if layer height is large - diameter_angle_scale_factor(sin(mesh_group_settings.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)), - increase_radius_until_radius(mesh_group_settings.get("support_tree_branch_diameter") / 2), + diameter_angle_scale_factor(sin(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height / branch_radius), + max_to_model_radius_increase(mesh_group_settings.support_tree_max_diameter_increase_by_merges_when_support_to_model / 2), + min_dtt_to_model(round_up_divide(mesh_group_settings.support_tree_min_height_to_model, layer_height)), + increase_radius_until_radius(mesh_group_settings.support_tree_branch_diameter / 2), increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / (branch_radius * diameter_angle_scale_factor)), - support_rests_on_model(mesh_group_settings.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), + support_rests_on_model(! mesh_group_settings.support_material_buildplate_only), + xy_distance(mesh_group_settings.support_xy_distance), + bp_radius(mesh_group_settings.support_tree_bp_diameter / 2), diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. - 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)), - z_distance_bottom_layers(round_up_divide(mesh_group_settings.get("support_bottom_distance"), layer_height)), - performance_interface_skip_layers(round_up_divide(mesh_group_settings.get("support_interface_skip_height"), layer_height)), - support_infill_angles(mesh_group_settings.get>("support_infill_angles")), - support_roof_angles(mesh_group_settings.get>("support_roof_angles")), - roof_pattern(mesh_group_settings.get("support_roof_pattern")), - support_pattern(mesh_group_settings.get("support_pattern")), - 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")), - 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_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")), + support_xy_overrides_z(mesh_group_settings.support_xy_overrides_z), + xy_min_distance(support_xy_overrides_z ? xy_distance : mesh_group_settings.support_xy_distance_overhang), + z_distance_top_layers(round_up_divide(mesh_group_settings.support_top_distance, layer_height)), + z_distance_bottom_layers(round_up_divide(mesh_group_settings.support_bottom_distance, layer_height)), + performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)), + support_infill_angles(mesh_group_settings.support_infill_angles), + support_roof_angles(mesh_group_settings.support_roof_angles), + roof_pattern(mesh_group_settings.support_roof_pattern), + support_pattern(mesh_group_settings.support_pattern), + support_roof_line_width(mesh_group_settings.support_roof_line_width), + support_line_distance(mesh_group_settings.support_line_distance), + support_bottom_offset(mesh_group_settings.support_bottom_offset), + support_wall_count(mesh_group_settings.support_wall_count), + maximum_deviation(mesh_group_settings.meshfix_maximum_deviation), + maximum_resolution(mesh_group_settings.meshfix_maximum_resolution), + support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. settings(mesh_group_settings), - min_feature_size(mesh_group_settings.get("min_feature_size")) + min_feature_size(mesh_group_settings.min_feature_size) { @@ -399,38 +402,42 @@ class TreeSupport xy_distance = std::max(xy_distance, xy_min_distance); - std::function&, EFillMethod)> getInterfaceAngles = [&](std::vector& angles, EFillMethod pattern) { // (logic) from getInterfaceAngles in FFFGcodeWriter. + std::function&, SupportMaterialInterfacePattern)> getInterfaceAngles = [&](std::vector& angles, SupportMaterialInterfacePattern pattern) { // (logic) from getInterfaceAngles in FFFGcodeWriter. if (angles.empty()) { - if (pattern == EFillMethod::CONCENTRIC) + if (pattern == SupportMaterialInterfacePattern::smipConcentric) { angles.push_back(0); // Concentric has no rotation. } - else if (pattern == EFillMethod::TRIANGLES) + /* + else if (pattern == SupportMaterialInterfacePattern::TRIANGLES) { angles.push_back(90); // Triangular support interface shouldn't alternate every layer. } + */ else { if (TreeSupportSettings::some_model_contains_thick_roof) { // Some roofs are quite thick. // Alternate between the two kinds of diagonal: / and \ . - angles.push_back(45); - angles.push_back(135); + angles.push_back(M_PI / 4.); + angles.push_back(3. * M_PI / 4.); } if (angles.empty()) { - angles.push_back(90); // Perpendicular to support lines. + angles.push_back(M_PI / 2.); // Perpendicular to support lines. } } } }; - getInterfaceAngles(support_infill_angles, support_pattern); + //getInterfaceAngles(support_infill_angles, support_pattern); + support_infill_angles = { M_PI / 2. }; getInterfaceAngles(support_roof_angles, roof_pattern); - 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")); +// 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")); + interface_preference = InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; } private: @@ -518,7 +525,7 @@ class TreeSupport /*! * \brief Should Z distance override X/Y distance, or the other way around. */ - SupportDistPriority support_overrides; + bool support_xy_overrides_z; /*! * \brief minimum xy_distance. Only relevant when Z overrides XY, otherwise equal to xy_distance- */ @@ -538,19 +545,19 @@ class TreeSupport /*! * \brief User specified angles for the support infill. */ - std::vector support_infill_angles; + std::vector support_infill_angles; /*! * \brief User specified angles for the support roof infill. */ - std::vector support_roof_angles; + std::vector support_roof_angles; /*! * \brief Pattern used in the support roof. May contain non relevant data if support roof is disabled. */ - EFillMethod roof_pattern; + SupportMaterialInterfacePattern roof_pattern; /*! * \brief Pattern used in the support infill. */ - EFillMethod support_pattern; + SupportMaterialPattern support_pattern; /*! * \brief Line width of the support roof. */ @@ -567,10 +574,6 @@ class TreeSupport * \brief Amount of walls the support area will have. */ int support_wall_count; - /* - * \brief Whether support infill lines will be connected. Only required to calculate infill patterns. - */ - bool zig_zaggify_support; /* * \brief Maximum allowed deviation when simplifying. */ @@ -583,18 +586,6 @@ class TreeSupport * \brief Distance between the lines of the roof. */ coord_t support_roof_line_distance; - /* - * \brief Only relevant for zigzag pattern. Only required to calculate infill patterns. - */ - bool skip_some_zags; - /* - * \brief Only relevant for zigzag pattern. Only required to calculate infill patterns. - */ - size_t zag_skip_count; - /* - * \brief Only relevant for zigzag pattern. Only required to calculate infill patterns. - */ - bool connect_zigzags; /* * \brief How overlaps of an interface area with a support area should be handled. */ @@ -603,7 +594,7 @@ class TreeSupport /* * \brief The infill class wants a settings object. This one will be the correct one for all settings it uses. */ - Settings settings; + TreeSupportMeshGroupSettings settings; /* * \brief Minimum thickness of any model features. @@ -615,15 +606,31 @@ class TreeSupport { 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 && 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_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_xy_overrides_z == other.support_xy_overrides_z && 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_preference == other.interface_preference + maximum_resolution == other.maximum_resolution && support_roof_line_distance == other.support_roof_line_distance && interface_preference == other.interface_preference && min_feature_size == other.min_feature_size // interface_preference should be identical to ensure the tree will correctly interact with the roof. // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry - && (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"))); +#if 0 + && (interface_preference == InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT || interface_preference == InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE + // Perimeter generator parameters + || + (settings.get("fill_outline_gaps") == other.settings.get("fill_outline_gaps") && + settings.get("min_bead_width") == other.settings.get("min_bead_width") && + settings.get("wall_transition_angle") == other.settings.get("wall_transition_angle") && + settings.get("wall_transition_length") == other.settings.get("wall_transition_length") && + settings.get("wall_split_middle_threshold") == other.settings.get("wall_split_middle_threshold") && + settings.get("wall_add_middle_threshold") == other.settings.get("wall_add_middle_threshold") && + settings.get("wall_distribution_count") == other.settings.get("wall_distribution_count") && + settings.get("wall_transition_filter_distance") == other.settings.get("wall_transition_filter_distance") && + settings.get("wall_transition_filter_deviation") == other.settings.get("wall_transition_filter_deviation") && + settings.get("wall_line_width_x") == other.settings.get("wall_line_width_x") && + settings.get("meshfix_maximum_extrusion_area_deviation") == other.settings.get("meshfix_maximum_extrusion_area_deviation")) + ) +#endif + ; } @@ -848,7 +855,7 @@ class TreeSupport * \param erase[out] Elements that should be deleted from the above dictionaries. * \param layer_idx[in] The Index of the current Layer. */ - void mergeHelper(std::map& reduced_aabb, std::map& input_aabb, const std::unordered_map& to_bp_areas, const std::unordered_map& to_model_areas, const std::map& influence_areas, std::unordered_map& insert_bp_areas, std::unordered_map& insert_model_areas, std::unordered_map& insert_influence, std::vector& erase, const LayerIndex layer_idx); + void mergeHelper(std::map& reduced_aabb, std::map& input_aabb, const std::unordered_map& to_bp_areas, const std::unordered_map& to_model_areas, const std::map& influence_areas, std::unordered_map& insert_bp_areas, std::unordered_map& insert_model_areas, std::unordered_map& insert_influence, std::vector& erase, const LayerIndex layer_idx); /*! * \brief Merges Influence Areas if possible. * From 075bf675fa3cfe902e4b71fc27b8ffcd5ae6d4ad Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 27 Jul 2022 08:50:59 +0200 Subject: [PATCH 06/29] WIP Tree Supports: Refactored the classic FDM support generator for modularity, so that the rasterization of support layers is accessible from tree supports. --- src/libslic3r/SupportMaterial.cpp | 619 +++++++++++++++--------------- src/libslic3r/SupportMaterial.hpp | 345 +++++++++-------- 2 files changed, 490 insertions(+), 474 deletions(-) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index e2a1f0cf2..330cbaf13 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -62,18 +62,18 @@ namespace Slic3r { #define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. #ifdef SLIC3R_DEBUG -const char* support_surface_type_to_color_name(const PrintObjectSupportMaterial::SupporLayerType surface_type) +const char* support_surface_type_to_color_name(const SupporLayerType surface_type) { switch (surface_type) { - case PrintObjectSupportMaterial::sltTopContact: return "rgb(255,0,0)"; // "red"; - case PrintObjectSupportMaterial::sltTopInterface: return "rgb(0,255,0)"; // "green"; - case PrintObjectSupportMaterial::sltBase: return "rgb(0,0,255)"; // "blue"; - case PrintObjectSupportMaterial::sltBottomInterface:return "rgb(255,255,128)"; // yellow - case PrintObjectSupportMaterial::sltBottomContact: return "rgb(255,0,255)"; // magenta - case PrintObjectSupportMaterial::sltRaftInterface: return "rgb(0,255,255)"; - case PrintObjectSupportMaterial::sltRaftBase: return "rgb(128,128,128)"; - case PrintObjectSupportMaterial::sltUnknown: return "rgb(128,0,0)"; // maroon - default: return "rgb(64,64,64)"; + case SupporLayerType::TopContact: return "rgb(255,0,0)"; // "red"; + case SupporLayerType::TopInterface: return "rgb(0,255,0)"; // "green"; + case SupporLayerType::Base: return "rgb(0,0,255)"; // "blue"; + case SupporLayerType::BottomInterface:return "rgb(255,255,128)"; // yellow + case SupporLayerType::BottomContact: return "rgb(255,0,255)"; // magenta + case SupporLayerType::RaftInterface: return "rgb(0,255,255)"; + case SupporLayerType::RaftBase: return "rgb(128,128,128)"; + case SupporLayerType::Unknown: return "rgb(128,0,0)"; // maroon + default: return "rgb(64,64,64)"; }; } @@ -89,28 +89,28 @@ void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos) coord_t pos_x = pos_x0; coord_t pos_y = pos(1) + scale_(1.5); coord_t step_x = scale_(10.); - svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopContact)); + svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(SupporLayerType::TopContact)); pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopInterface)); + svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(SupporLayerType::TopInterface)); pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBase)); + svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(SupporLayerType::Base)); pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomInterface)); + svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(SupporLayerType::BottomInterface)); pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomContact)); + svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(SupporLayerType::BottomContact)); // 2nd row pos_x = pos_x0; pos_y = pos(1)+scale_(2.8); - svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftInterface)); + svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(SupporLayerType::RaftInterface)); pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftBase)); + svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(SupporLayerType::RaftBase)); pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltUnknown)); + svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(SupporLayerType::Unknown)); pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltIntermediate)); + svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(SupporLayerType::Intermediate)); } -void export_print_z_polygons_to_svg(const char *path, PrintObjectSupportMaterial::MyLayer ** const layers, size_t n_layers) +void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers) { BoundingBox bbox; for (int i = 0; i < n_layers; ++ i) @@ -130,7 +130,7 @@ void export_print_z_polygons_to_svg(const char *path, PrintObjectSupportMaterial void export_print_z_polygons_and_extrusions_to_svg( const char *path, - PrintObjectSupportMaterial::MyLayer ** const layers, + SupportGeneratorLayer ** const layers, size_t n_layers, SupportLayer &support_layer) { @@ -321,112 +321,120 @@ static Polygons contours_simplified(const Vec2i &grid_size, const double pixel_s } #endif // SUPPORT_USE_AGG_RASTERIZER -PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : - m_object (object), - m_print_config (&object->print()->config()), - m_object_config (&object->config()), - m_slicing_params (slicing_params) +SupportParameters::SupportParameters(const PrintObject &object) { - m_support_params.first_layer_flow = support_material_1st_layer_flow(object, float(slicing_params.first_print_layer_height)); - m_support_params.support_material_flow = support_material_flow(object, float(slicing_params.layer_height)); - m_support_params.support_material_interface_flow = support_material_interface_flow(object, float(slicing_params.layer_height)); - m_support_params.support_layer_height_min = 0.01; + const PrintConfig &print_config = object.print()->config(); + const PrintObjectConfig &object_config = object.config(); + const SlicingParameters &slicing_params = object.slicing_parameters(); + + this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height)); + this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height)); + this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height)); // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. - m_support_params.support_layer_height_min = 1000000.; - for (auto lh : m_print_config->min_layer_height.values) - m_support_params.support_layer_height_min = std::min(m_support_params.support_layer_height_min, std::max(0.01, lh)); - for (auto layer : m_object->layers()) - m_support_params.support_layer_height_min = std::min(m_support_params.support_layer_height_min, std::max(0.01, layer->height)); + this->support_layer_height_min = scaled(0.01); + for (auto lh : print_config.min_layer_height.values) + this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, lh)); + for (auto layer : object.layers()) + this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height)); - if (m_object_config->support_material_interface_layers.value == 0) { + if (object_config.support_material_interface_layers.value == 0) { // No interface layers allowed, print everything with the base support pattern. - m_support_params.support_material_interface_flow = m_support_params.support_material_flow; + this->support_material_interface_flow = this->support_material_flow; } // Evaluate the XY gap between the object outer perimeters and the support structures. // Evaluate the XY gap between the object outer perimeters and the support structures. coordf_t external_perimeter_width = 0.; coordf_t bridge_flow_ratio = 0; - for (size_t region_id = 0; region_id < object->num_printing_regions(); ++ region_id) { - const PrintRegion ®ion = object->printing_region(region_id); - external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width())); + for (size_t region_id = 0; region_id < object.num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = object.printing_region(region_id); + external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(object, frExternalPerimeter, slicing_params.layer_height).width())); bridge_flow_ratio += region.config().bridge_flow_ratio; } - m_support_params.gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width); - bridge_flow_ratio /= object->num_printing_regions(); + this->gap_xy = object_config.support_material_xy_spacing.get_abs_value(external_perimeter_width); + bridge_flow_ratio /= object.num_printing_regions(); - m_support_params.support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ? - m_support_params.support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : - Flow::bridging_flow(bridge_flow_ratio * m_support_params.support_material_interface_flow.nozzle_diameter(), m_support_params.support_material_interface_flow.nozzle_diameter()); + this->support_material_bottom_interface_flow = slicing_params.soluble_interface || ! object_config.thick_bridges ? + this->support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : + Flow::bridging_flow(bridge_flow_ratio * this->support_material_interface_flow.nozzle_diameter(), this->support_material_interface_flow.nozzle_diameter()); - m_support_params.can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value; - if (!m_support_params.can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) { + this->can_merge_support_regions = object_config.support_material_extruder.value == object_config.support_material_interface_extruder.value; + if (!this->can_merge_support_regions && (object_config.support_material_extruder.value == 0 || object_config.support_material_interface_extruder.value == 0)) { // One of the support extruders is of "don't care" type. - auto object_extruders = m_object->object_extruders(); + auto object_extruders = object.object_extruders(); if (object_extruders.size() == 1 && - *object_extruders.begin() == std::max(m_object_config->support_material_extruder.value, m_object_config->support_material_interface_extruder.value)) + *object_extruders.begin() == std::max(object_config.support_material_extruder.value, object_config.support_material_interface_extruder.value)) // Object is printed with the same extruder as the support. - m_support_params.can_merge_support_regions = true; + this->can_merge_support_regions = true; } - m_support_params.base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); - m_support_params.interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); - m_support_params.interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); - m_support_params.interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / m_support_params.interface_spacing); - m_support_params.support_spacing = m_object_config->support_material_spacing.value + m_support_params.support_material_flow.spacing(); - m_support_params.support_density = std::min(1., m_support_params.support_material_flow.spacing() / m_support_params.support_spacing); - if (m_object_config->support_material_interface_layers.value == 0) { + this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value)); + this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.)); + this->interface_spacing = object_config.support_material_interface_spacing.value + this->support_material_interface_flow.spacing(); + this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / this->interface_spacing); + this->support_spacing = object_config.support_material_spacing.value + this->support_material_flow.spacing(); + this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_spacing); + if (object_config.support_material_interface_layers.value == 0) { // No interface layers allowed, print everything with the base support pattern. - m_support_params.interface_spacing = m_support_params.support_spacing; - m_support_params.interface_density = m_support_params.support_density; + this->interface_spacing = this->support_spacing; + this->interface_density = this->support_density; } - SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; - m_support_params.with_sheath = m_object_config->support_material_with_sheath; - m_support_params.base_fill_pattern = + SupportMaterialPattern support_pattern = object_config.support_material_pattern; + this->with_sheath = object_config.support_material_with_sheath; + this->base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : - m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase; - m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); - m_support_params.contact_fill_pattern = - (m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || - m_object_config->support_material_interface_pattern == smipConcentric ? + this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; + this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); + this->contact_fill_pattern = + (object_config.support_material_interface_pattern == smipAuto && slicing_params.soluble_interface) || + object_config.support_material_interface_pattern == smipConcentric ? ipConcentric : - (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); + (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); +} + +PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : + m_object (object), + m_print_config (&object->print()->config()), + m_object_config (&object->config()), + m_slicing_params (slicing_params), + m_support_params (*object) +{ } // Using the std::deque as an allocator. -inline PrintObjectSupportMaterial::MyLayer& layer_allocate( - std::deque &layer_storage, - PrintObjectSupportMaterial::SupporLayerType layer_type) +inline SupportGeneratorLayer& layer_allocate( + std::deque &layer_storage, + SupporLayerType layer_type) { - layer_storage.push_back(PrintObjectSupportMaterial::MyLayer()); + layer_storage.push_back(SupportGeneratorLayer()); layer_storage.back().layer_type = layer_type; return layer_storage.back(); } -inline PrintObjectSupportMaterial::MyLayer& layer_allocate( - std::deque &layer_storage, +inline SupportGeneratorLayer& layer_allocate( + std::deque &layer_storage, tbb::spin_mutex &layer_storage_mutex, - PrintObjectSupportMaterial::SupporLayerType layer_type) + SupporLayerType layer_type) { layer_storage_mutex.lock(); - layer_storage.push_back(PrintObjectSupportMaterial::MyLayer()); - PrintObjectSupportMaterial::MyLayer *layer_new = &layer_storage.back(); + layer_storage.push_back(SupportGeneratorLayer()); + SupportGeneratorLayer *layer_new = &layer_storage.back(); layer_storage_mutex.unlock(); layer_new->layer_type = layer_type; return *layer_new; } -inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const PrintObjectSupportMaterial::MyLayersPtr &src) +inline void layers_append(SupportGeneratorLayersPtr &dst, const SupportGeneratorLayersPtr &src) { dst.insert(dst.end(), src.begin(), src.end()); } // Support layer that is covered by some form of dense interface. -static constexpr const std::initializer_list support_types_interface { - PrintObjectSupportMaterial::sltRaftInterface, PrintObjectSupportMaterial::sltBottomContact, PrintObjectSupportMaterial::sltBottomInterface, PrintObjectSupportMaterial::sltTopContact, PrintObjectSupportMaterial::sltTopInterface +static constexpr const std::initializer_list support_types_interface { + SupporLayerType::RaftInterface, SupporLayerType::BottomContact, SupporLayerType::BottomInterface, SupporLayerType::TopContact, SupporLayerType::TopInterface }; void PrintObjectSupportMaterial::generate(PrintObject &object) @@ -439,7 +447,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Layer instances will be allocated by std::deque and they will be kept until the end of this function call. // The layers will be referenced by various LayersPtr (of type std::vector) - MyLayerStorage layer_storage; + SupportGeneratorLayerStorage layer_storage; BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts"; @@ -452,7 +460,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // should the support material expose to the object in order to guarantee // that it will be effective, regardless of how it's built below. // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette without holes. - MyLayersPtr top_contacts = this->top_contact_layers(object, buildplate_covered, layer_storage); + SupportGeneratorLayersPtr top_contacts = this->top_contact_layers(object, buildplate_covered, layer_storage); if (top_contacts.empty()) // Nothing is supported, no supports are generated. return; @@ -460,7 +468,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) #ifdef SLIC3R_DEBUG static int iRun = 0; iRun ++; - for (const MyLayer *layer : top_contacts) + for (const SupportGeneratorLayer *layer : top_contacts) Slic3r::SVG::export_expolygons( debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z), union_ex(layer->polygons)); @@ -473,7 +481,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // layer_support_areas contains the per object layer support areas. These per object layer support areas // may get merged and trimmed by this->generate_base_layers() if the support layers are not synchronized with object layers. std::vector layer_support_areas; - MyLayersPtr bottom_contacts = this->bottom_contact_layers_and_layer_support_areas( + SupportGeneratorLayersPtr bottom_contacts = this->bottom_contact_layers_and_layer_support_areas( object, top_contacts, buildplate_covered, layer_storage, layer_support_areas); @@ -491,13 +499,13 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // The layers may or may not be synchronized with the object layers, depending on the configuration. // For example, a single nozzle multi material printing will need to generate a waste tower, which in turn // wastes less material, if there are as little tool changes as possible. - MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( + SupportGeneratorLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( object, bottom_contacts, top_contacts, layer_storage); this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy); #ifdef SLIC3R_DEBUG - for (const MyLayer *layer : top_contacts) + for (const SupportGeneratorLayer *layer : top_contacts) Slic3r::SVG::export_expolygons( debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z), union_ex(layer->polygons)); @@ -509,7 +517,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) this->generate_base_layers(object, bottom_contacts, top_contacts, intermediate_layers, layer_support_areas); #ifdef SLIC3R_DEBUG - for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it) + for (SupportGeneratorLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it) Slic3r::SVG::export_expolygons( debug_out_path("support-base-layers-%d-%lf.svg", iRun, (*it)->print_z), union_ex((*it)->polygons)); @@ -535,14 +543,14 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled. // There is also a 1st intermediate layer containing bases of support columns. // Inflate the bases of the support columns and create the raft base under the object. - MyLayersPtr raft_layers = this->generate_raft_base(object, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); + SupportGeneratorLayersPtr raft_layers = this->generate_raft_base(object, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); #ifdef SLIC3R_DEBUG - for (const MyLayer *l : interface_layers) + for (const SupportGeneratorLayer *l : interface_layers) Slic3r::SVG::export_expolygons( debug_out_path("support-interface-layers-%d-%lf.svg", iRun, l->print_z), union_ex(l->polygons)); - for (const MyLayer *l : base_interface_layers) + for (const SupportGeneratorLayer *l : base_interface_layers) Slic3r::SVG::export_expolygons( debug_out_path("support-base-interface-layers-%d-%lf.svg", iRun, l->print_z), union_ex(l->polygons)); @@ -567,7 +575,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Install support layers into the object. // A support layer installed on a PrintObject has a unique print_z. - MyLayersPtr layers_sorted; + SupportGeneratorLayersPtr layers_sorted; layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); layers_append(layers_sorted, raft_layers); layers_append(layers_sorted, bottom_contacts); @@ -595,11 +603,11 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) size_t num_top_contacts = 0; double top_contact_bottom_z = 0; for (size_t u = i; u < j; ++u) { - MyLayer &layer = *layers_sorted[u]; + SupportGeneratorLayer &layer = *layers_sorted[u]; if (! layer.polygons.empty()) { empty = false; num_interfaces += one_of(layer.layer_type, support_types_interface); - if (layer.layer_type == sltTopContact) { + if (layer.layer_type == SupporLayerType::TopContact) { ++ num_top_contacts; assert(num_top_contacts <= 1); // All top contact layers sharing this print_z shall also share bottom_z. @@ -661,7 +669,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) #endif /* SLIC3R_DEBUG */ // Generate the actual toolpaths and save them into each layer. - this->generate_toolpaths(object.support_layers(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); + generate_support_toolpaths(object.support_layers(), *m_object_config, m_support_params, m_slicing_params, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); #ifdef SLIC3R_DEBUG { @@ -1681,17 +1689,17 @@ static inline std::tuple detect_overhangs( // Allocate one, possibly two support contact layers. // For "thick" overhangs, one support layer will be generated to support normal extrusions, the other to support the "thick" extrusions. -static inline std::pair new_contact_layer( +static inline std::pair new_contact_layer( const PrintConfig &print_config, const PrintObjectConfig &object_config, const SlicingParameters &slicing_params, const coordf_t support_layer_height_min, const Layer &layer, - std::deque &layer_storage, + std::deque &layer_storage, tbb::spin_mutex &layer_storage_mutex) { double print_z, bottom_z, height; - PrintObjectSupportMaterial::MyLayer* bridging_layer = nullptr; + SupportGeneratorLayer* bridging_layer = nullptr; assert(layer.id() >= slicing_params.raft_layers()); size_t layer_id = layer.id() - slicing_params.raft_layers(); @@ -1717,7 +1725,7 @@ static inline std::pair(nullptr, nullptr); + return std::pair(nullptr, nullptr); } const bool has_raft = slicing_params.raft_layers() > 1; const coordf_t min_print_z = has_raft ? slicing_params.raft_contact_top_z : slicing_params.first_print_layer_height; @@ -1748,7 +1756,7 @@ static inline std::pairidx_object_layer_above = layer_id; bridging_layer->print_z = bridging_print_z; if (bridging_print_z == slicing_params.first_print_layer_height) { @@ -1764,7 +1772,7 @@ static inline std::pairprint_z < l2->print_z; }); + std::sort(layers.begin(), layers.end(), [](const SupportGeneratorLayer *l1, const SupportGeneratorLayer *l2) { return l1->print_z < l2->print_z; }); int i = 0; int k = 0; @@ -1938,7 +1946,7 @@ static void merge_contact_layers(const SlicingParameters &slicing_params, double for (; j < (int)layers.size() && layers[j]->print_z < slicing_params.first_print_layer_height + support_layer_height_min - EPSILON; ++ j); if (j > 0) { // Merge the layers layers (0) to (j - 1) into the layers[0]. - PrintObjectSupportMaterial::MyLayer &dst = *layers.front(); + SupportGeneratorLayer &dst = *layers.front(); for (int u = 1; u < j; ++ u) dst.merge(std::move(*layers[u])); // Snap the first layer to the 1st layer height. @@ -1956,7 +1964,7 @@ static void merge_contact_layers(const SlicingParameters &slicing_params, double for (; j < (int)layers.size() && layers[j]->print_z < zmax; ++ j) ; if (i + 1 < j) { // Merge the layers layers (i + 1) to (j - 1) into the layers[i]. - PrintObjectSupportMaterial::MyLayer &dst = *layers[i]; + SupportGeneratorLayer &dst = *layers[i]; for (int u = i + 1; u < j; ++ u) dst.merge(std::move(*layers[u])); } @@ -1971,8 +1979,8 @@ static void merge_contact_layers(const SlicingParameters &slicing_params, double // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers( - const PrintObject &object, const std::vector &buildplate_covered, MyLayerStorage &layer_storage) const +SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( + const PrintObject &object, const std::vector &buildplate_covered, SupportGeneratorLayerStorage &layer_storage) const { #ifdef SLIC3R_DEBUG static int iRun = 0; @@ -1984,7 +1992,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ SupportAnnotations annotations(object, buildplate_covered); // Output layers, sorted by top Z. - MyLayersPtr contact_out; + SupportGeneratorLayersPtr contact_out; BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - start"; // Determine top contact areas. @@ -2056,17 +2064,17 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ } // Find the bottom contact layers above the top surfaces of this layer. -static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( +static inline SupportGeneratorLayer* detect_bottom_contacts( const SlicingParameters &slicing_params, - const PrintObjectSupportMaterial::SupportParams &support_params, + const SupportParameters &support_params, const PrintObject &object, const Layer &layer, // Existing top contact layers, to which this newly created bottom contact layer will be snapped to guarantee a minimum layer height. - const PrintObjectSupportMaterial::MyLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &top_contacts, // First top contact layer index overlapping with this new bottom interface layer. size_t contact_idx, // To allocate a new layer from. - std::deque &layer_storage, + std::deque &layer_storage, // To trim the support areas above this bottom interface layer with this newly created bottom interface layer. std::vector &layer_support_areas, // Support areas projected from top to bottom, starting with top support interfaces. @@ -2101,7 +2109,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( size_t layer_id = layer.id() - slicing_params.raft_layers(); // Allocate a new bottom contact layer. - PrintObjectSupportMaterial::MyLayer &layer_new = layer_allocate(layer_storage, PrintObjectSupportMaterial::sltBottomContact); + SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::BottomContact); // Grow top surfaces so that interface and support generation are generated // with some spacing from object - it looks we don't need the actual // top shapes so this can be done here @@ -2263,12 +2271,12 @@ static inline std::pair project_support_to_grid(const Layer // Generate bottom contact layers supporting the top contact layers. // For a soluble interface material synchronize the layer heights with the object, // otherwise set the layer height to a bridging flow of a support interface nozzle. -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_layer_support_areas( - const PrintObject &object, const MyLayersPtr &top_contacts, std::vector &buildplate_covered, - MyLayerStorage &layer_storage, std::vector &layer_support_areas) const +SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_layer_support_areas( + const PrintObject &object, const SupportGeneratorLayersPtr &top_contacts, std::vector &buildplate_covered, + SupportGeneratorLayerStorage &layer_storage, std::vector &layer_support_areas) const { if (top_contacts.empty()) - return MyLayersPtr(); + return SupportGeneratorLayersPtr(); #ifdef SLIC3R_DEBUG static size_t s_iRun = 0; @@ -2285,7 +2293,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta // find object top surfaces // we'll use them to clip our support and detect where does it stick - MyLayersPtr bottom_contacts; + SupportGeneratorLayersPtr bottom_contacts; // There is some support to be built, if there are non-empty top surfaces detected. // Sum of unsupported contact areas above the current layer.print_z. @@ -2304,7 +2312,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta Polygons enforcers_new; #endif // SLIC3R_DEBUG for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { - MyLayer &top_contact = *top_contacts[contact_idx]; + SupportGeneratorLayer &top_contact = *top_contacts[contact_idx]; #ifndef SLIC3R_DEBUG Polygons polygons_new; Polygons enforcers_new; @@ -2346,7 +2354,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta #endif // SLIC3R_DEBUG ] { // Find the bottom contact layers above the top surfaces of this layer. - MyLayer *layer_new = detect_bottom_contacts( + SupportGeneratorLayer *layer_new = detect_bottom_contacts( m_slicing_params, m_support_params, object, layer, top_contacts, contact_idx, layer_storage, layer_support_areas, overhangs_for_bottom_contacts #ifdef SLIC3R_DEBUG , iRun, polygons_new @@ -2481,19 +2489,19 @@ int idx_lower_or_equal(const std::vector &vec, int idx, FN_LOWER_EQUAL fn_lo // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( - const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const + const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const { tbb::parallel_for(tbb::blocked_range(0, int(top_contacts.size())), [&bottom_contacts, &top_contacts](const tbb::blocked_range& range) { int idx_bottom_overlapping_first = -2; // For all top contact layers, counting downwards due to the way idx_higher_or_equal caches the last index to avoid repeated binary search. for (int idx_top = range.end() - 1; idx_top >= range.begin(); -- idx_top) { - MyLayer &layer_top = *top_contacts[idx_top]; + SupportGeneratorLayer &layer_top = *top_contacts[idx_top]; // Find the first bottom layer overlapping with layer_top. - idx_bottom_overlapping_first = idx_lower_or_equal(bottom_contacts, idx_bottom_overlapping_first, [&layer_top](const MyLayer *layer_bottom){ return layer_bottom->bottom_print_z() - EPSILON <= layer_top.bottom_z; }); + idx_bottom_overlapping_first = idx_lower_or_equal(bottom_contacts, idx_bottom_overlapping_first, [&layer_top](const SupportGeneratorLayer *layer_bottom){ return layer_bottom->bottom_print_z() - EPSILON <= layer_top.bottom_z; }); // For all top contact layers overlapping with the thick bottom contact layer: for (int idx_bottom_overlapping = idx_bottom_overlapping_first; idx_bottom_overlapping >= 0; -- idx_bottom_overlapping) { - const MyLayer &layer_bottom = *bottom_contacts[idx_bottom_overlapping]; + const SupportGeneratorLayer &layer_bottom = *bottom_contacts[idx_bottom_overlapping]; assert(layer_bottom.bottom_print_z() - EPSILON <= layer_top.bottom_z); if (layer_top.print_z < layer_bottom.print_z + EPSILON) { // Layers overlap. Trim layer_top with layer_bottom. @@ -2505,16 +2513,16 @@ void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( }); } -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_support_layers( +SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_support_layers( const PrintObject &object, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayerStorage &layer_storage) const + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayerStorage &layer_storage) const { - MyLayersPtr intermediate_layers; + SupportGeneratorLayersPtr intermediate_layers; // Collect and sort the extremes (bottoms of the top contacts and tops of the bottom contacts). - MyLayersPtr extremes; + SupportGeneratorLayersPtr extremes; extremes.reserve(top_contacts.size() + bottom_contacts.size()); for (size_t i = 0; i < top_contacts.size(); ++ i) // Bottoms of the top contact layers. In case of non-soluble supports, @@ -2526,18 +2534,18 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int if (extremes.empty()) return intermediate_layers; - auto layer_extreme_lower = [](const MyLayer *l1, const MyLayer *l2) { + auto layer_extreme_lower = [](const SupportGeneratorLayer *l1, const SupportGeneratorLayer *l2) { coordf_t z1 = l1->extreme_z(); coordf_t z2 = l2->extreme_z(); // If the layers are aligned, return the top contact surface first. - return z1 < z2 || (z1 == z2 && l1->layer_type == PrintObjectSupportMaterial::sltTopContact && l2->layer_type == PrintObjectSupportMaterial::sltBottomContact); + return z1 < z2 || (z1 == z2 && l1->layer_type == SupporLayerType::TopContact && l2->layer_type == SupporLayerType::BottomContact); }; std::sort(extremes.begin(), extremes.end(), layer_extreme_lower); assert(extremes.empty() || (extremes.front()->extreme_z() > m_slicing_params.raft_interface_top_z - EPSILON && (m_slicing_params.raft_layers() == 1 || // only raft contact layer - extremes.front()->layer_type == sltTopContact || // first extreme is a top contact layer + extremes.front()->layer_type == SupporLayerType::TopContact || // first extreme is a top contact layer extremes.front()->extreme_z() > m_slicing_params.first_print_layer_height - EPSILON))); bool synchronize = this->synchronize_layers(); @@ -2549,7 +2557,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > m_support_params.support_layer_height_min - EPSILON); assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > 0. || extremes[i]->layer_type == extremes[i-1]->layer_type || - (extremes[i]->layer_type == sltBottomContact && extremes[i - 1]->layer_type == sltTopContact)); + (extremes[i]->layer_type == SupporLayerType::BottomContact && extremes[i - 1]->layer_type == SupporLayerType::TopContact)); } #endif @@ -2562,19 +2570,19 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int if (! extremes.empty() && std::abs(extremes.front()->extreme_z() - m_slicing_params.raft_interface_top_z) < EPSILON) { // This is a raft contact layer, its height has been decided in this->top_contact_layers(). // Ignore this layer when calculating the intermediate support layers. - assert(extremes.front()->layer_type == sltTopContact); + assert(extremes.front()->layer_type == SupporLayerType::TopContact); ++ idx_extreme_first; } for (size_t idx_extreme = idx_extreme_first; idx_extreme < extremes.size(); ++ idx_extreme) { - MyLayer *extr2 = extremes[idx_extreme]; + SupportGeneratorLayer *extr2 = extremes[idx_extreme]; coordf_t extr2z = extr2->extreme_z(); if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) { // This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers(). - assert(extr2->layer_type == sltTopContact); + assert(extr2->layer_type == SupporLayerType::TopContact); assert(extr2->bottom_z == m_slicing_params.first_print_layer_height); assert(extr2->print_z >= m_slicing_params.first_print_layer_height + m_support_params.support_layer_height_min - EPSILON); if (intermediate_layers.empty() || intermediate_layers.back()->print_z < m_slicing_params.first_print_layer_height) { - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); layer_new.bottom_z = 0.; layer_new.print_z = m_slicing_params.first_print_layer_height; layer_new.height = m_slicing_params.first_print_layer_height; @@ -2584,11 +2592,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int } assert(extr2z >= m_slicing_params.raft_interface_top_z + EPSILON); assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); - MyLayer *extr1 = (idx_extreme == idx_extreme_first) ? nullptr : extremes[idx_extreme - 1]; + SupportGeneratorLayer *extr1 = (idx_extreme == idx_extreme_first) ? nullptr : extremes[idx_extreme - 1]; // Fuse a support layer firmly to the raft top interface (not to the raft contacts). coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z(); assert(extr2z >= extr1z); - assert(extr2z > extr1z || (extr1 != nullptr && extr2->layer_type == sltBottomContact)); + assert(extr2z > extr1z || (extr1 != nullptr && extr2->layer_type == SupporLayerType::BottomContact)); if (std::abs(extr1z) < EPSILON) { // This layer interval starts with the 1st layer. Print the 1st layer using the prescribed 1st layer thickness. // assert(! m_slicing_params.has_raft()); RaftingEdition: unclear where the issue is: assert fails with 1-layer raft & base supports @@ -2596,7 +2604,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int // At this point only layers above first_print_layer_heigth + EPSILON are expected as the other cases were captured earlier. assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); // Generate a new intermediate layer. - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); layer_new.bottom_z = 0.; layer_new.print_z = extr1z = m_slicing_params.first_print_layer_height; layer_new.height = extr1z; @@ -2616,7 +2624,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int ++ idx_layer_object; if (idx_layer_object == 0 && extr1z == m_slicing_params.raft_interface_top_z) { // Insert one base support layer below the object. - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); layer_new.print_z = m_slicing_params.object_print_z_min; layer_new.bottom_z = m_slicing_params.raft_interface_top_z; layer_new.height = layer_new.print_z - layer_new.bottom_z; @@ -2624,7 +2632,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int } // Emit all intermediate support layers synchronized with object layers up to extr2z. for (; idx_layer_object < object.layers().size() && object.layers()[idx_layer_object]->print_z < extr2z + EPSILON; ++ idx_layer_object) { - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); layer_new.print_z = object.layers()[idx_layer_object]->print_z; layer_new.height = object.layers()[idx_layer_object]->height; layer_new.bottom_z = (idx_layer_object > 0) ? object.layers()[idx_layer_object - 1]->print_z : (layer_new.print_z - layer_new.height); @@ -2636,13 +2644,13 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int size_t n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height)); assert(n_layers_extra > 0); coordf_t step = dist / coordf_t(n_layers_extra); - if (extr1 != nullptr && extr1->layer_type == sltTopContact && + if (extr1 != nullptr && extr1->layer_type == SupporLayerType::TopContact && extr1->print_z + m_support_params.support_layer_height_min > extr1->bottom_z + step) { // The bottom extreme is a bottom of a top surface. Ensure that the gap // between the 1st intermediate layer print_z and extr1->print_z is not too small. assert(extr1->bottom_z + m_support_params.support_layer_height_min < extr1->print_z + EPSILON); // Generate the first intermediate layer. - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); layer_new.bottom_z = extr1->bottom_z; layer_new.print_z = extr1z = extr1->print_z; layer_new.height = extr1->height; @@ -2654,7 +2662,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int // Continue printing the other layers up to extr2z. step = dist / coordf_t(n_layers_extra); } - if (! m_slicing_params.soluble_interface && extr2->layer_type == sltTopContact) { + if (! m_slicing_params.soluble_interface && extr2->layer_type == SupporLayerType::TopContact) { // This is a top interface layer, which does not have a height assigned yet. Do it now. assert(extr2->height == 0.); assert(extr1z > m_slicing_params.first_print_layer_height - EPSILON); @@ -2666,7 +2674,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int coordf_t extr2z_large_steps = extr2z; // Take the largest allowed step in the Z axis until extr2z_large_steps is reached. for (size_t i = 0; i < n_layers_extra; ++ i) { - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + SupportGeneratorLayer &layer_new = layer_allocate(layer_storage, SupporLayerType::Intermediate); if (i + 1 == n_layers_extra) { // Last intermediate layer added. Align the last entered layer with extr2z_large_steps exactly. layer_new.bottom_z = (i == 0) ? extr1z : intermediate_layers.back()->print_z; @@ -2697,9 +2705,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int // Also the bottom/top_contacts shall have a layer thickness assigned already. void PrintObjectSupportMaterial::generate_base_layers( const PrintObject &object, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &intermediate_layers, const std::vector &layer_support_areas) const { #ifdef SLIC3R_DEBUG @@ -2723,7 +2731,7 @@ void PrintObjectSupportMaterial::generate_base_layers( { BOOST_LOG_TRIVIAL(trace) << "Support generator - generate_base_layers - creating layer " << idx_intermediate << " of " << intermediate_layers.size(); - MyLayer &layer_intermediate = *intermediate_layers[idx_intermediate]; + SupportGeneratorLayer &layer_intermediate = *intermediate_layers[idx_intermediate]; // Layers must be sorted by print_z. assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z); @@ -2746,10 +2754,10 @@ void PrintObjectSupportMaterial::generate_base_layers( // 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen. // 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top. idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, - [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); + [&layer_intermediate](const SupportGeneratorLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); // Collect all the top_contact layer intersecting with this layer. for (int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { - MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; + SupportGeneratorLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON) break; // Base must not overlap with top.bottom_z. @@ -2766,7 +2774,7 @@ void PrintObjectSupportMaterial::generate_base_layers( polygons_new = layer_support_areas.front(); double first_layer_z = object.layers().front()->print_z; for (int i = idx_top_contact_above + 1; i < int(top_contacts.size()); ++ i) { - MyLayer &contacts = *top_contacts[i]; + SupportGeneratorLayer &contacts = *top_contacts[i]; if (contacts.print_z > first_layer_z + EPSILON) break; assert(contacts.bottom_z > layer_intermediate.print_z - EPSILON); @@ -2783,10 +2791,10 @@ void PrintObjectSupportMaterial::generate_base_layers( // 4) base.print_z > bottom.print_z && base.bottom_z >= bottom.print_z -> Base overlaps with bottom.print_z. This must not happen. // 5) base.print_z <= bottom.print_z && base.bottom_z >= bottom.bottom_z -> Base is fully inside top. Trim base by top. idx_bottom_contact_overlapping = idx_lower_or_equal(bottom_contacts, idx_bottom_contact_overlapping, - [&layer_intermediate](const MyLayer *layer){ return layer->bottom_print_z() <= layer_intermediate.print_z - EPSILON; }); + [&layer_intermediate](const SupportGeneratorLayer *layer){ return layer->bottom_print_z() <= layer_intermediate.print_z - EPSILON; }); // Collect all the bottom_contacts layer intersecting with this layer. for (int i = idx_bottom_contact_overlapping; i >= 0; -- i) { - MyLayer &layer_bottom_overlapping = *bottom_contacts[i]; + SupportGeneratorLayer &layer_bottom_overlapping = *bottom_contacts[i]; if (layer_bottom_overlapping.print_z < layer_intermediate.bottom_print_z() + EPSILON) break; // Base must not overlap with bottom.top_z. @@ -2816,7 +2824,7 @@ void PrintObjectSupportMaterial::generate_base_layers( polygons_new, polygons_trimming, ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons - layer_intermediate.layer_type = sltBase; + layer_intermediate.layer_type = SupporLayerType::Base; #if 0 // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); @@ -2838,7 +2846,7 @@ void PrintObjectSupportMaterial::generate_base_layers( BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_base_layers() in parallel - end"; #ifdef SLIC3R_DEBUG - for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++it) + for (SupportGeneratorLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++it) ::Slic3r::SVG::export_expolygons( debug_out_path("support-intermediate-layers-untrimmed-%d-%lf.svg", iRun, (*it)->print_z), union_ex((*it)->polygons)); @@ -2850,7 +2858,7 @@ void PrintObjectSupportMaterial::generate_base_layers( void PrintObjectSupportMaterial::trim_support_layers_by_object( const PrintObject &object, - MyLayersPtr &support_layers, + SupportGeneratorLayersPtr &support_layers, const coordf_t gap_extra_above, const coordf_t gap_extra_below, const coordf_t gap_xy) const @@ -2859,10 +2867,10 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( // Collect non-empty layers to be processed in parallel. // This is a good idea as pulling a thread from a thread pool for an empty task is expensive. - MyLayersPtr nonempty_layers; + SupportGeneratorLayersPtr nonempty_layers; nonempty_layers.reserve(support_layers.size()); for (size_t idx_layer = 0; idx_layer < support_layers.size(); ++ idx_layer) { - MyLayer *support_layer = support_layers[idx_layer]; + SupportGeneratorLayer *support_layer = support_layers[idx_layer]; if (! support_layer->polygons.empty() && support_layer->print_z >= m_slicing_params.raft_contact_top_z + EPSILON) // Non-empty support layer and not a raft layer. nonempty_layers.push_back(support_layer); @@ -2875,7 +2883,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( [this, &object, &nonempty_layers, gap_extra_above, gap_extra_below, gap_xy_scaled](const tbb::blocked_range& range) { size_t idx_object_layer_overlapping = size_t(-1); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { - MyLayer &support_layer = *nonempty_layers[idx_layer]; + SupportGeneratorLayer &support_layer = *nonempty_layers[idx_layer]; // BOOST_LOG_TRIVIAL(trace) << "Support generator - trim_support_layers_by_object - trimmming non-empty layer " << idx_layer << " of " << nonempty_layers.size(); assert(! support_layer.polygons.empty() && support_layer.print_z >= m_slicing_params.raft_contact_top_z + EPSILON); // Find the overlapping object layers including the extra above / below gap. @@ -2922,13 +2930,13 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; } -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raft_base( +SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base( const PrintObject &object, - const MyLayersPtr &top_contacts, - const MyLayersPtr &interface_layers, - const MyLayersPtr &base_interface_layers, - const MyLayersPtr &base_layers, - MyLayerStorage &layer_storage) const + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers, + const SupportGeneratorLayersPtr &base_layers, + SupportGeneratorLayerStorage &layer_storage) const { // If there is brim to be generated, calculate the trimming regions. Polygons brim; @@ -2962,10 +2970,10 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. const float inflate_factor_fine = float(scale_((m_slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); - MyLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); - MyLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); - MyLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); - MyLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); + SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); + SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); + SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); + SupportGeneratorLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); if (contacts != nullptr && contacts->print_z > std::max(m_slicing_params.first_print_layer_height, m_slicing_params.raft_contact_top_z) + EPSILON) // This is not the raft contact layer. contacts = nullptr; @@ -2988,7 +2996,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Output vector. - MyLayersPtr raft_layers; + SupportGeneratorLayersPtr raft_layers; if (m_slicing_params.raft_layers() > 1) { Polygons base; @@ -3007,7 +3015,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf // Do not add the raft contact layer, only add the raft layers below the contact layer. // Insert the 1st layer. { - MyLayer &new_layer = layer_allocate(layer_storage, (m_slicing_params.base_raft_layers > 0) ? sltRaftBase : sltRaftInterface); + SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, (m_slicing_params.base_raft_layers > 0) ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface); raft_layers.push_back(&new_layer); new_layer.print_z = m_slicing_params.first_print_layer_height; new_layer.height = m_slicing_params.first_print_layer_height; @@ -3017,7 +3025,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf // Insert the base layers. for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) { coordf_t print_z = raft_layers.back()->print_z; - MyLayer &new_layer = layer_allocate(layer_storage, sltRaftBase); + SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, SupporLayerType::RaftBase); raft_layers.push_back(&new_layer); new_layer.print_z = print_z + m_slicing_params.base_raft_layer_height; new_layer.height = m_slicing_params.base_raft_layer_height; @@ -3027,7 +3035,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf // Insert the interface layers. for (size_t i = 1; i < m_slicing_params.interface_raft_layers; ++ i) { coordf_t print_z = raft_layers.back()->print_z; - MyLayer &new_layer = layer_allocate(layer_storage, sltRaftInterface); + SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, SupporLayerType::RaftInterface); raft_layers.push_back(&new_layer); new_layer.print_z = print_z + m_slicing_params.interface_raft_layer_height; new_layer.height = m_slicing_params.interface_raft_layer_height; @@ -3068,17 +3076,17 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf } // Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers. -std::pair PrintObjectSupportMaterial::generate_interface_layers( - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayersPtr &intermediate_layers, - MyLayerStorage &layer_storage) const +std::pair PrintObjectSupportMaterial::generate_interface_layers( + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage) const { // my $area_threshold = $self->interface_flow->scaled_spacing ** 2; - std::pair base_and_interface_layers; - MyLayersPtr &interface_layers = base_and_interface_layers.first; - MyLayersPtr &base_interface_layers = base_and_interface_layers.second; + std::pair base_and_interface_layers; + SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first; + SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second; // distinguish between interface and base interface layers // Contact layer is considered an interface layer, therefore run the following block only if support_material_interface_layers > 1. @@ -3115,7 +3123,7 @@ std::pair MyLayer* { + SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* { assert(! bottom.empty() || ! top.empty()); // Merge top into bottom, unite them with a safety offset. append(bottom, std::move(top)); @@ -3129,7 +3137,7 @@ std::pair::max() : intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only_top - 1)]->print_z; // Move idx_top_contact_first up until above the current print_z. - idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON + idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON // Collect the top contact areas above this intermediate layer, below top_z. for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { - const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; + const SupportGeneratorLayer &top_contact_layer = *top_contacts[idx_top_contact]; //FIXME maybe this adds one interface layer in excess? if (top_contact_layer.bottom_z - EPSILON > top_z) break; @@ -3199,26 +3207,26 @@ std::pair::max() : intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only_bottom)]->bottom_z; // Move idx_bottom_contact_first up until touching bottom_z. - idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); + idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const SupportGeneratorLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); // Collect the top contact areas above this intermediate layer, below top_z. for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { - const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; + const SupportGeneratorLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) break; polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); } } - MyLayer *interface_layer = nullptr; + SupportGeneratorLayer *interface_layer = nullptr; if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty()) { interface_layer = insert_layer( intermediate_layer, polygons_bottom_contact_projected_interface, std::move(polygons_top_contact_projected_interface), nullptr, - polygons_top_contact_projected_interface.empty() ? sltBottomInterface : sltTopInterface); + polygons_top_contact_projected_interface.empty() ? SupporLayerType::BottomInterface : SupporLayerType::TopInterface); interface_layers[idx_intermediate_layer] = interface_layer; } if (! polygons_bottom_contact_projected_base.empty() || ! polygons_top_contact_projected_base.empty()) base_interface_layers[idx_intermediate_layer] = insert_layer( intermediate_layer, polygons_bottom_contact_projected_base, std::move(polygons_top_contact_projected_base), - interface_layer ? &interface_layer->polygons : nullptr, sltBase); + interface_layer ? &interface_layer->polygons : nullptr, SupporLayerType::Base); } }); @@ -3333,9 +3341,9 @@ static inline void fill_expolygons_with_sheath_generate_paths( } // Support layers, partially processed. -struct MyLayerExtruded +struct SupportGeneratorLayerExtruded { - MyLayerExtruded& operator=(MyLayerExtruded &&rhs) { + SupportGeneratorLayerExtruded& operator=(SupportGeneratorLayerExtruded &&rhs) { this->layer = rhs.layer; this->extrusions = std::move(rhs.extrusions); m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); @@ -3356,14 +3364,14 @@ struct MyLayerExtruded Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } - bool could_merge(const MyLayerExtruded &other) const { + bool could_merge(const SupportGeneratorLayerExtruded &other) const { return ! this->empty() && ! other.empty() && std::abs(this->layer->height - other.layer->height) < EPSILON && this->layer->bridging == other.layer->bridging; } // Merge regions, perform boolean union over the merged polygons. - void merge(MyLayerExtruded &&other) { + void merge(SupportGeneratorLayerExtruded &&other) { assert(this->could_merge(other)); // 1) Merge the rest polygons to extrude, if there are any. if (other.m_polygons_to_extrude != nullptr) { @@ -3397,7 +3405,7 @@ struct MyLayerExtruded } // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). - PrintObjectSupportMaterial::MyLayer *layer { nullptr }; + SupportGeneratorLayer *layer { nullptr }; // Collect extrusions. They will be exported sorted by the bottom height. ExtrusionEntitiesPtr extrusions; @@ -3407,7 +3415,7 @@ private: std::unique_ptr m_polygons_to_extrude; }; -typedef std::vector MyLayerExtrudedPtrs; +typedef std::vector SupportGeneratorLayerExtrudedPtrs; struct LoopInterfaceProcessor { @@ -3426,7 +3434,7 @@ struct LoopInterfaceProcessor // Generate loop contacts at the top_contact_layer, // trim the top_contact_layer->polygons with the areas covered by the loops. - void generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; + void generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; int n_contact_loops; coordf_t circle_radius; @@ -3434,7 +3442,7 @@ struct LoopInterfaceProcessor Polygon circle; }; -void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const +void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const { if (n_contact_loops == 0 || top_contact_layer.empty()) return; @@ -3665,9 +3673,9 @@ static std::string dbg_index_to_color(int idx) void modulate_extrusion_by_overlapping_layers( // Extrusions generated for this_layer. ExtrusionEntitiesPtr &extrusions_in_out, - const PrintObjectSupportMaterial::MyLayer &this_layer, + const SupportGeneratorLayer &this_layer, // Multiple layers overlapping with this_layer, sorted bottom up. - const PrintObjectSupportMaterial::MyLayersPtr &overlapping_layers) + const SupportGeneratorLayersPtr &overlapping_layers) { size_t n_overlapping_layers = overlapping_layers.size(); if (n_overlapping_layers == 0 || extrusions_in_out.empty()) @@ -3704,7 +3712,7 @@ void modulate_extrusion_by_overlapping_layers( ++ iRun; BoundingBox bbox; for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; bbox.merge(get_extents(overlapping_layer.polygons)); } for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { @@ -3717,13 +3725,13 @@ void modulate_extrusion_by_overlapping_layers( // Filled polygons for the overlapping regions. svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency); for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency); } // Contours of the overlapping regions. svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2)); for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1)); } // Fill extrusion, the source. @@ -3765,7 +3773,7 @@ void modulate_extrusion_by_overlapping_layers( // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z. // Trim by the highest overlapping layer first. for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) { - const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + const SupportGeneratorLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming); @@ -3856,7 +3864,7 @@ void modulate_extrusion_by_overlapping_layers( ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); if (path != nullptr) { // Verify whether the path is compatible with the current fragment. - assert(this_layer.layer_type == PrintObjectSupportMaterial::sltBottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); + assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { path = nullptr; } @@ -3903,22 +3911,25 @@ void modulate_extrusion_by_overlapping_layers( extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); } -void PrintObjectSupportMaterial::generate_toolpaths( +void generate_support_toolpaths( SupportLayerPtrs &support_layers, - const MyLayersPtr &raft_layers, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - const MyLayersPtr &intermediate_layers, - const MyLayersPtr &interface_layers, - const MyLayersPtr &base_interface_layers) const + const PrintObjectConfig &config, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers) { // loop_interface_processor with a given circle radius. - LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width()); - loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; + LoopInterfaceProcessor loop_interface_processor(1.5 * support_params.support_material_interface_flow.scaled_width()); + loop_interface_processor.n_contact_loops = config.support_material_interface_contact_loops ? 1 : 0; - std::vector angles { m_support_params.base_angle }; - if (m_object_config->support_material_pattern == smpRectilinearGrid) - angles.push_back(m_support_params.interface_angle); + std::vector angles { support_params.base_angle }; + if (config.support_material_pattern == smpRectilinearGrid) + angles.push_back(support_params.interface_angle); BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); @@ -3928,34 +3939,34 @@ void PrintObjectSupportMaterial::generate_toolpaths( float raft_angle_1st_layer = 0.f; float raft_angle_base = 0.f; float raft_angle_interface = 0.f; - if (m_slicing_params.base_raft_layers > 1) { + if (slicing_params.base_raft_layers > 1) { // There are all raft layer types (1st layer, base, interface & contact layers) available. - raft_angle_1st_layer = m_support_params.interface_angle; - raft_angle_base = m_support_params.base_angle; - raft_angle_interface = m_support_params.interface_angle; - } else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) { + raft_angle_1st_layer = support_params.interface_angle; + raft_angle_base = support_params.base_angle; + raft_angle_interface = support_params.interface_angle; + } else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) { // 1st layer, interface & contact layers available. - raft_angle_1st_layer = m_support_params.base_angle; - if (this->has_support()) + raft_angle_1st_layer = support_params.base_angle; + if (config.support_material || config.support_material_enforce_layers > 0) // Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer. raft_angle_1st_layer += 0.7854f; - raft_angle_interface = m_support_params.interface_angle; - } else if (m_slicing_params.interface_raft_layers == 1) { + raft_angle_interface = support_params.interface_angle; + } else if (slicing_params.interface_raft_layers == 1) { // Only the contact raft layer is non-empty, which will be printed as the 1st layer. - assert(m_slicing_params.base_raft_layers == 0); - assert(m_slicing_params.interface_raft_layers == 1); - assert(m_slicing_params.raft_layers() == 1 && raft_layers.size() == 0); + assert(slicing_params.base_raft_layers == 0); + assert(slicing_params.interface_raft_layers == 1); + assert(slicing_params.raft_layers() == 1 && raft_layers.size() == 0); } else { // No raft. - assert(m_slicing_params.base_raft_layers == 0); - assert(m_slicing_params.interface_raft_layers == 0); - assert(m_slicing_params.raft_layers() == 0 && raft_layers.size() == 0); + assert(slicing_params.base_raft_layers == 0); + assert(slicing_params.interface_raft_layers == 0); + assert(slicing_params.raft_layers() == 0 && raft_layers.size() == 0); } // Insert the raft base layers. - size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); + size_t n_raft_layers = size_t(std::max(0, int(slicing_params.raft_layers()) - 1)); tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), - [this, &support_layers, &raft_layers, + [&support_layers, &raft_layers, &config, &support_params, &slicing_params, &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] (const tbb::blocked_range& range) { for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) @@ -3963,55 +3974,55 @@ void PrintObjectSupportMaterial::generate_toolpaths( assert(support_layer_id < raft_layers.size()); SupportLayer &support_layer = *support_layers[support_layer_id]; assert(support_layer.support_fills.entities.empty()); - MyLayer &raft_layer = *raft_layers[support_layer_id]; + SupportGeneratorLayer &raft_layer = *raft_layers[support_layer_id]; - std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.interface_fill_pattern)); - std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); + std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(support_params.interface_fill_pattern)); + std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); filler_interface->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object); // Print the support base below the support columns, or the support base for the support columns plus the contacts. if (support_layer_id > 0) { - const Polygons &to_infill_polygons = (support_layer_id < m_slicing_params.base_raft_layers) ? + const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ? raft_layer.polygons : //FIXME misusing contact_polygons for support columns. ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); if (! to_infill_polygons.empty()) { assert(! raft_layer.bridging); - Flow flow(float(m_support_params.support_material_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); + Flow flow(float(support_params.support_material_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter()); Fill * filler = filler_support.get(); filler->angle = raft_angle_base; - filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); + filler->spacing = support_params.support_material_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); fill_expolygons_with_sheath_generate_paths( // Destination support_layer.support_fills.entities, // Regions to fill to_infill_polygons, // Filler and its parameters - filler, float(m_support_params.support_density), + filler, float(support_params.support_density), // Extrusion parameters erSupportMaterial, flow, - m_support_params.with_sheath, false); + support_params.with_sheath, false); } } Fill *filler = filler_interface.get(); - Flow flow = m_support_params.first_layer_flow; + Flow flow = support_params.first_layer_flow; float density = 0.f; if (support_layer_id == 0) { // Base flange. filler->angle = raft_angle_1st_layer; - filler->spacing = m_support_params.first_layer_flow.spacing(); - density = float(m_object_config->raft_first_layer_density.value * 0.01); - } else if (support_layer_id >= m_slicing_params.base_raft_layers) { + filler->spacing = support_params.first_layer_flow.spacing(); + density = float(config.raft_first_layer_density.value * 0.01); + } else if (support_layer_id >= slicing_params.base_raft_layers) { filler->angle = raft_angle_interface; // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. - filler->spacing = m_support_params.support_material_flow.spacing(); + filler->spacing = support_params.support_material_flow.spacing(); assert(! raft_layer.bridging); - flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); - density = float(m_support_params.interface_density); + flow = Flow(float(support_params.support_material_interface_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter()); + density = float(support_params.interface_density); } else continue; filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); @@ -4023,27 +4034,27 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Filler and its parameters filler, density, // Extrusion parameters - (support_layer_id < m_slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow, + (support_layer_id < slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow, // sheath at first layer support_layer_id == 0, support_layer_id == 0); } }); struct LayerCacheItem { - LayerCacheItem(MyLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} - MyLayerExtruded *layer_extruded; - std::vector overlapping; + LayerCacheItem(SupportGeneratorLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} + SupportGeneratorLayerExtruded *layer_extruded; + std::vector overlapping; }; struct LayerCache { - MyLayerExtruded bottom_contact_layer; - MyLayerExtruded top_contact_layer; - MyLayerExtruded base_layer; - MyLayerExtruded interface_layer; - MyLayerExtruded base_interface_layer; + SupportGeneratorLayerExtruded bottom_contact_layer; + SupportGeneratorLayerExtruded top_contact_layer; + SupportGeneratorLayerExtruded base_layer; + SupportGeneratorLayerExtruded interface_layer; + SupportGeneratorLayerExtruded base_interface_layer; boost::container::static_vector nonempty; void add_nonempty_and_sort() { - for (MyLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) + for (SupportGeneratorLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) if (! item->empty()) this->nonempty.emplace_back(item); // Sort the layers with the same print_z coordinate by their heights, thickest first. @@ -4053,7 +4064,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( std::vector layer_caches(support_layers.size()); tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [this, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, + [&config, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, &bbox_object, &angles, link_max_length_factor] (const tbb::blocked_range& range) { // Indices of the 1st layer in their respective container at the support layer height. @@ -4063,15 +4074,15 @@ void PrintObjectSupportMaterial::generate_toolpaths( size_t idx_layer_interface = size_t(-1); size_t idx_layer_base_interface = size_t(-1); const auto fill_type_first_layer = ipRectilinear; - auto filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.contact_fill_pattern)); + auto filler_interface = std::unique_ptr(Fill::new_from_type(support_params.contact_fill_pattern)); // Filler for the 1st layer interface, if different from filler_interface. - auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && m_support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); + auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); // Pointer to the 1st layer interface filler. auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : - Fill::new_from_type(m_support_params.interface_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase)); - auto filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); + Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase)); + auto filler_support = std::unique_ptr(Fill::new_from_type(support_params.base_fill_pattern)); filler_interface->set_bounding_box(bbox_object); if (filler_first_layer_ptr) filler_first_layer_ptr->set_bounding_box(bbox_object); @@ -4082,19 +4093,19 @@ void PrintObjectSupportMaterial::generate_toolpaths( { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; - float interface_angle_delta = m_object_config->support_material_style.value == smsSnug ? + float interface_angle_delta = config.support_material_style.value == smsSnug ? (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : 0; // Find polygons with the same print_z. - MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; - MyLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; - MyLayerExtruded &base_layer = layer_cache.base_layer; - MyLayerExtruded &interface_layer = layer_cache.interface_layer; - MyLayerExtruded &base_interface_layer = layer_cache.base_interface_layer; + SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; + SupportGeneratorLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; + SupportGeneratorLayerExtruded &base_layer = layer_cache.base_layer; + SupportGeneratorLayerExtruded &interface_layer = layer_cache.interface_layer; + SupportGeneratorLayerExtruded &base_interface_layer = layer_cache.base_interface_layer; // Increment the layer indices to find a layer at support_layer.print_z. { - auto fun = [&support_layer](const MyLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; + auto fun = [&support_layer](const SupportGeneratorLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun); idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun); idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun); @@ -4113,23 +4124,23 @@ void PrintObjectSupportMaterial::generate_toolpaths( if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) base_layer.layer = intermediate_layers[idx_layer_intermediate]; - if (m_object_config->support_material_interface_layers == 0) { + if (config.support_material_interface_layers == 0) { // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. - if (m_support_params.can_merge_support_regions) { + if (support_params.can_merge_support_regions) { if (base_layer.could_merge(top_contact_layer)) base_layer.merge(std::move(top_contact_layer)); else if (base_layer.empty()) base_layer = std::move(top_contact_layer); } } else { - loop_interface_processor.generate(top_contact_layer, m_support_params.support_material_interface_flow); + loop_interface_processor.generate(top_contact_layer, support_params.support_material_interface_flow); // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used // to trim other layers. if (top_contact_layer.could_merge(interface_layer)) top_contact_layer.merge(std::move(interface_layer)); } - if ((m_object_config->support_material_interface_layers == 0 || m_object_config->support_material_bottom_interface_layers == 0) && m_support_params.can_merge_support_regions) { + if ((config.support_material_interface_layers == 0 || config.support_material_bottom_interface_layers == 0) && support_params.can_merge_support_regions) { if (base_layer.could_merge(bottom_contact_layer)) base_layer.merge(std::move(bottom_contact_layer)); else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) @@ -4153,23 +4164,23 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Top and bottom contacts, interface layers. for (size_t i = 0; i < 3; ++ i) { - MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); + SupportGeneratorLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) continue; - bool interface_as_base = m_object_config->support_material_interface_layers.value == 0 || - (m_object_config->support_material_bottom_interface_layers == 0 && &layer_ex == &bottom_contact_layer); + bool interface_as_base = config.support_material_interface_layers.value == 0 || + (config.support_material_bottom_interface_layers == 0 && &layer_ex == &bottom_contact_layer); //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) auto interface_flow = layer_ex.layer->bridging ? - Flow::bridging_flow(layer_ex.layer->height, m_support_params.support_material_bottom_interface_flow.nozzle_diameter()) : - (interface_as_base ? &m_support_params.support_material_flow : &m_support_params.support_material_interface_flow)->with_height(float(layer_ex.layer->height)); + Flow::bridging_flow(layer_ex.layer->height, support_params.support_material_bottom_interface_flow.nozzle_diameter()) : + (interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow)->with_height(float(layer_ex.layer->height)); filler_interface->angle = interface_as_base ? // If zero interface layers are configured, use the same angle as for the base layers. angles[support_layer_id % angles.size()] : // Use interface angle for the interface layers. - m_support_params.interface_angle + interface_angle_delta; - double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density; - filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing(); + support_params.interface_angle + interface_angle_delta; + double density = interface_as_base ? support_params.support_density : support_params.interface_density; + filler_interface->spacing = interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); fill_expolygons_generate_paths( // Destination @@ -4188,10 +4199,10 @@ void PrintObjectSupportMaterial::generate_toolpaths( //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) assert(! base_interface_layer.layer->bridging); - Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); - filler->angle = m_support_params.interface_angle + interface_angle_delta; - filler->spacing = m_support_params.support_material_interface_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.interface_density)); + Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); + filler->angle = support_params.interface_angle + interface_angle_delta; + filler->spacing = support_params.support_material_interface_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density)); fill_expolygons_generate_paths( // Destination base_interface_layer.extrusions, @@ -4199,7 +4210,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Regions to fill union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), // Filler and its parameters - filler, float(m_support_params.interface_density), + filler, float(support_params.interface_density), // Extrusion parameters erSupportMaterial, interface_flow); } @@ -4211,18 +4222,18 @@ void PrintObjectSupportMaterial::generate_toolpaths( // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. assert(! base_layer.layer->bridging); - auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height)); - filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); - float density = float(m_support_params.support_density); - bool sheath = m_support_params.with_sheath; + auto flow = support_params.support_material_flow.with_height(float(base_layer.layer->height)); + filler->spacing = support_params.support_material_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); + float density = float(support_params.support_density); + bool sheath = support_params.with_sheath; bool no_sort = false; if (base_layer.layer->bottom_z < EPSILON) { // Base flange (the 1st layer). filler = filler_first_layer; - filler->angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); - density = float(m_object_config->raft_first_layer_density.value * 0.01); - flow = m_support_params.first_layer_flow; + filler->angle = Geometry::deg2rad(float(config.support_material_angle.value + 90.)); + density = float(config.raft_first_layer_density.value * 0.01); + flow = support_params.first_layer_flow; // use the proper spacing for first layer as we don't need to align // its pattern to the other layers //FIXME When paralellizing, each thread shall have its own copy of the fillers. @@ -4269,12 +4280,12 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Collect overlapping top/bottom surfaces. layer_cache_item.overlapping.reserve(20); coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; - auto add_overlapping = [&layer_cache_item, bottom_z](const MyLayersPtr &layers, size_t idx_top) { + auto add_overlapping = [&layer_cache_item, bottom_z](const SupportGeneratorLayersPtr &layers, size_t idx_top) { for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) layer_cache_item.overlapping.push_back(layers[i]); }; add_overlapping(top_contacts, idx_layer_top_contact); - if (layer_cache_item.layer_extruded->layer->layer_type == sltBottomContact) { + if (layer_cache_item.layer_extruded->layer->layer_type == SupporLayerType::BottomContact) { // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. add_overlapping(intermediate_layers, idx_layer_intermediate); add_overlapping(interface_layers, idx_layer_interface); @@ -4322,7 +4333,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( } }; for (const SupportLayer *support_layer : support_layers) - assert(Test::verify_nonempty(&support_layer->support_fills)); + assert(Test::verify_nonempty(&support_layer->support_fills)); #endif // NDEBUG } diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index 65604ef72..304252584 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -11,147 +11,162 @@ class PrintObject; class PrintConfig; class PrintObjectConfig; +// Support layer type to be used by SupportGeneratorLayer. This type carries a much more detailed information +// about the support layer type than the final support layers stored in a PrintObject. +enum SupporLayerType { + Unknown = 0, + // Ratft base layer, to be printed with the support material. + RaftBase, + // Raft interface layer, to be printed with the support interface material. + RaftInterface, + // Bottom contact layer placed over a top surface of an object. To be printed with a support interface material. + BottomContact, + // Dense interface layer, to be printed with the support interface material. + // This layer is separated from an object by an BottomContact layer. + BottomInterface, + // Sparse base support layer, to be printed with a support material. + Base, + // Dense interface layer, to be printed with the support interface material. + // This layer is separated from an object with TopContact layer. + TopInterface, + // Top contact layer directly supporting an overhang. To be printed with a support interface material. + TopContact, + // Some undecided type yet. It will turn into Base first, then it may turn into BottomInterface or TopInterface. + Intermediate, +}; + +// A support layer type used internally by the SupportMaterial class. This class carries a much more detailed +// information about the support layer than the layers stored in the PrintObject, mainly +// the SupportGeneratorLayer is aware of the bridging flow and the interface gaps between the object and the support. +class SupportGeneratorLayer +{ +public: + void reset() { + *this = SupportGeneratorLayer(); + } + + bool operator==(const SupportGeneratorLayer &layer2) const { + return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; + } + + // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. + bool operator<(const SupportGeneratorLayer &layer2) const { + if (print_z < layer2.print_z) { + return true; + } else if (print_z == layer2.print_z) { + if (height > layer2.height) + return true; + else if (height == layer2.height) { + // Bridging layers first. + return bridging && ! layer2.bridging; + } else + return false; + } else + return false; + } + + void merge(SupportGeneratorLayer &&rhs) { + // The union_() does not support move semantic yet, but maybe one day it will. + this->polygons = union_(this->polygons, std::move(rhs.polygons)); + auto merge = [](std::unique_ptr &dst, std::unique_ptr &src) { + if (! dst || dst->empty()) + dst = std::move(src); + else if (src && ! src->empty()) + *dst = union_(*dst, std::move(*src)); + }; + merge(this->contact_polygons, rhs.contact_polygons); + merge(this->overhang_polygons, rhs.overhang_polygons); + merge(this->enforcer_polygons, rhs.enforcer_polygons); + rhs.reset(); + } + + // For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation. + // For the non-bridging flow, bottom_print_z will be equal to bottom_z. + coordf_t bottom_print_z() const { return print_z - height; } + + // To sort the extremes of top / bottom interface layers. + coordf_t extreme_z() const { return (this->layer_type == SupporLayerType::TopContact) ? this->bottom_z : this->print_z; } + + SupporLayerType layer_type { SupporLayerType::Unknown }; + // Z used for printing, in unscaled coordinates. + coordf_t print_z { 0 }; + // Bottom Z of this layer. For soluble layers, bottom_z + height = print_z, + // otherwise bottom_z + gap + height = print_z. + coordf_t bottom_z { 0 }; + // Layer height in unscaled coordinates. + coordf_t height { 0 }; + // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers. + // If this is not a contact layer, it will be set to size_t(-1). + size_t idx_object_layer_above { size_t(-1) }; + // Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers. + // If this is not a contact layer, it will be set to size_t(-1). + size_t idx_object_layer_below { size_t(-1) }; + // Use a bridging flow when printing this support layer. + bool bridging { false }; + + // Polygons to be filled by the support pattern. + Polygons polygons; + // Currently for the contact layers only. + std::unique_ptr contact_polygons; + std::unique_ptr overhang_polygons; + // Enforcers need to be propagated independently in case the "support on build plate only" option is enabled. + std::unique_ptr enforcer_polygons; +}; + +// Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained +// up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future, +// which would allocate layers by multiple chunks. +using SupportGeneratorLayerStorage = std::deque; +using SupportGeneratorLayersPtr = std::vector; + +struct SupportParameters { + SupportParameters(const PrintObject &object); + + Flow first_layer_flow; + Flow support_material_flow; + Flow support_material_interface_flow; + Flow support_material_bottom_interface_flow; + // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? + bool can_merge_support_regions; + + coordf_t support_layer_height_min; +// coordf_t support_layer_height_max; + + coordf_t gap_xy; + + float base_angle; + float interface_angle; + coordf_t interface_spacing; + coordf_t interface_density; + coordf_t support_spacing; + coordf_t support_density; + + InfillPattern base_fill_pattern; + InfillPattern interface_fill_pattern; + InfillPattern contact_fill_pattern; + bool with_sheath; +}; + +// Produce the support G-code. +// Used by both classic and tree supports. +static void generate_support_toolpaths( + SupportLayerPtrs &support_layers, + const PrintObjectConfig &config, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers); + // This class manages raft and supports for a single PrintObject. // Instantiated by Slic3r::Print::Object->_support_material() // This class is instantiated before the slicing starts as Object.pm will query // the parameters of the raft to determine the 1st layer height and thickness. class PrintObjectSupportMaterial { -public: - // Support layer type to be used by MyLayer. This type carries a much more detailed information - // about the support layer type than the final support layers stored in a PrintObject. - enum SupporLayerType { - sltUnknown = 0, - // Ratft base layer, to be printed with the support material. - sltRaftBase, - // Raft interface layer, to be printed with the support interface material. - sltRaftInterface, - // Bottom contact layer placed over a top surface of an object. To be printed with a support interface material. - sltBottomContact, - // Dense interface layer, to be printed with the support interface material. - // This layer is separated from an object by an sltBottomContact layer. - sltBottomInterface, - // Sparse base support layer, to be printed with a support material. - sltBase, - // Dense interface layer, to be printed with the support interface material. - // This layer is separated from an object with sltTopContact layer. - sltTopInterface, - // Top contact layer directly supporting an overhang. To be printed with a support interface material. - sltTopContact, - // Some undecided type yet. It will turn into sltBase first, then it may turn into sltBottomInterface or sltTopInterface. - sltIntermediate, - }; - - // A support layer type used internally by the SupportMaterial class. This class carries a much more detailed - // information about the support layer than the layers stored in the PrintObject, mainly - // the MyLayer is aware of the bridging flow and the interface gaps between the object and the support. - class MyLayer - { - public: - void reset() { - *this = MyLayer(); - } - - bool operator==(const MyLayer &layer2) const { - return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; - } - - // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. - bool operator<(const MyLayer &layer2) const { - if (print_z < layer2.print_z) { - return true; - } else if (print_z == layer2.print_z) { - if (height > layer2.height) - return true; - else if (height == layer2.height) { - // Bridging layers first. - return bridging && ! layer2.bridging; - } else - return false; - } else - return false; - } - - void merge(MyLayer &&rhs) { - // The union_() does not support move semantic yet, but maybe one day it will. - this->polygons = union_(this->polygons, std::move(rhs.polygons)); - auto merge = [](std::unique_ptr &dst, std::unique_ptr &src) { - if (! dst || dst->empty()) - dst = std::move(src); - else if (src && ! src->empty()) - *dst = union_(*dst, std::move(*src)); - }; - merge(this->contact_polygons, rhs.contact_polygons); - merge(this->overhang_polygons, rhs.overhang_polygons); - merge(this->enforcer_polygons, rhs.enforcer_polygons); - rhs.reset(); - } - - // For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation. - // For the non-bridging flow, bottom_print_z will be equal to bottom_z. - coordf_t bottom_print_z() const { return print_z - height; } - - // To sort the extremes of top / bottom interface layers. - coordf_t extreme_z() const { return (this->layer_type == sltTopContact) ? this->bottom_z : this->print_z; } - - SupporLayerType layer_type { sltUnknown }; - // Z used for printing, in unscaled coordinates. - coordf_t print_z { 0 }; - // Bottom Z of this layer. For soluble layers, bottom_z + height = print_z, - // otherwise bottom_z + gap + height = print_z. - coordf_t bottom_z { 0 }; - // Layer height in unscaled coordinates. - coordf_t height { 0 }; - // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers. - // If this is not a contact layer, it will be set to size_t(-1). - size_t idx_object_layer_above { size_t(-1) }; - // Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers. - // If this is not a contact layer, it will be set to size_t(-1). - size_t idx_object_layer_below { size_t(-1) }; - // Use a bridging flow when printing this support layer. - bool bridging { false }; - - // Polygons to be filled by the support pattern. - Polygons polygons; - // Currently for the contact layers only. - std::unique_ptr contact_polygons; - std::unique_ptr overhang_polygons; - // Enforcers need to be propagated independently in case the "support on build plate only" option is enabled. - std::unique_ptr enforcer_polygons; - }; - - struct SupportParams { - Flow first_layer_flow; - Flow support_material_flow; - Flow support_material_interface_flow; - Flow support_material_bottom_interface_flow; - // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? - bool can_merge_support_regions; - - coordf_t support_layer_height_min; - // coordf_t support_layer_height_max; - - coordf_t gap_xy; - - float base_angle; - float interface_angle; - coordf_t interface_spacing; - coordf_t interface_density; - coordf_t support_spacing; - coordf_t support_density; - - InfillPattern base_fill_pattern; - InfillPattern interface_fill_pattern; - InfillPattern contact_fill_pattern; - bool with_sheath; - }; - - // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained - // up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future, - // which would allocate layers by multiple chunks. - typedef std::deque MyLayerStorage; - typedef std::vector MyLayersPtr; - public: PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params); @@ -175,58 +190,58 @@ private: // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. - MyLayersPtr top_contact_layers(const PrintObject &object, const std::vector &buildplate_covered, MyLayerStorage &layer_storage) const; + SupportGeneratorLayersPtr top_contact_layers(const PrintObject &object, const std::vector &buildplate_covered, SupportGeneratorLayerStorage &layer_storage) const; // Generate bottom contact layers supporting the top contact layers. // For a soluble interface material synchronize the layer heights with the object, // otherwise set the layer height to a bridging flow of a support interface nozzle. - MyLayersPtr bottom_contact_layers_and_layer_support_areas( - const PrintObject &object, const MyLayersPtr &top_contacts, std::vector &buildplate_covered, - MyLayerStorage &layer_storage, std::vector &layer_support_areas) const; + SupportGeneratorLayersPtr bottom_contact_layers_and_layer_support_areas( + const PrintObject &object, const SupportGeneratorLayersPtr &top_contacts, std::vector &buildplate_covered, + SupportGeneratorLayerStorage &layer_storage, std::vector &layer_support_areas) const; // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. - void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const; + void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const; // Generate raft layers and the intermediate support layers between the bottom contact and top contact surfaces. - MyLayersPtr raft_and_intermediate_support_layers( + SupportGeneratorLayersPtr raft_and_intermediate_support_layers( const PrintObject &object, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayerStorage &layer_storage) const; + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayerStorage &layer_storage) const; // Fill in the base layers with polygons. void generate_base_layers( const PrintObject &object, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &intermediate_layers, const std::vector &layer_support_areas) const; // Generate raft layers, also expand the 1st support layer // in case there is no raft layer to improve support adhesion. - MyLayersPtr generate_raft_base( + SupportGeneratorLayersPtr generate_raft_base( const PrintObject &object, - const MyLayersPtr &top_contacts, - const MyLayersPtr &interface_layers, - const MyLayersPtr &base_interface_layers, - const MyLayersPtr &base_layers, - MyLayerStorage &layer_storage) const; + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers, + const SupportGeneratorLayersPtr &base_layers, + SupportGeneratorLayerStorage &layer_storage) const; // Turn some of the base layers into base interface layers. // For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base // extruder to improve adhesion of the soluble filament to the base. - std::pair generate_interface_layers( - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayersPtr &intermediate_layers, - MyLayerStorage &layer_storage) const; + std::pair generate_interface_layers( + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage) const; // Trim support layers by an object to leave a defined gap between // the support volume and the object. void trim_support_layers_by_object( const PrintObject &object, - MyLayersPtr &support_layers, + SupportGeneratorLayersPtr &support_layers, const coordf_t gap_extra_above, const coordf_t gap_extra_below, const coordf_t gap_xy) const; @@ -236,16 +251,6 @@ private: void clip_with_shape(); */ - // Produce the actual G-code. - void generate_toolpaths( - SupportLayerPtrs &support_layers, - const MyLayersPtr &raft_layers, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - const MyLayersPtr &intermediate_layers, - const MyLayersPtr &interface_layers, - const MyLayersPtr &base_interface_layers) const; - // Following objects are not owned by SupportMaterial class. const PrintObject *m_object; const PrintConfig *m_print_config; @@ -254,7 +259,7 @@ private: // carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc. SlicingParameters m_slicing_params; // Various precomputed support parameters to be shared with external functions. - SupportParams m_support_params; + SupportParameters m_support_params; }; } // namespace Slic3r From 8a1e8f97a997be58b1c32f680698d4306ac031a4 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 27 Jul 2022 08:53:48 +0200 Subject: [PATCH 07/29] Minor refactoring of BoundingBox: change Eigen point accessor from indices to .x(), .y(), .z() Added Polyline vector accessors. Polished Point hash code. --- src/libslic3r/BoundingBox.hpp | 26 ++++++++++++++------------ src/libslic3r/Point.hpp | 10 ++++++---- src/libslic3r/Polyline.hpp | 3 +++ 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 6b06f8bab..1d6cd4ef1 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -18,7 +18,7 @@ public: BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {} BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : - min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} + min(pmin), max(pmax), defined(pmin.x() < pmax.x() && pmin.y() < pmax.y()) {} BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : min(p1), max(p1), defined(false) { merge(p2); merge(p3); } @@ -37,7 +37,7 @@ public: this->min = this->min.cwiseMin(vec); this->max = this->max.cwiseMax(vec); } - this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)); + this->defined = (this->min.x() < this->max.x()) && (this->min.y() < this->max.y()); } } @@ -58,15 +58,15 @@ public: BoundingBoxBase inflated(coordf_t delta) const throw() { BoundingBoxBase out(*this); out.offset(delta); return out; } PointClass center() const; bool contains(const PointClass &point) const { - return point(0) >= this->min(0) && point(0) <= this->max(0) - && point(1) >= this->min(1) && point(1) <= this->max(1); + return point.x() >= this->min.x() && point.x() <= this->max.x() + && point.y() >= this->min.y() && point.y() <= this->max.y(); } bool contains(const BoundingBoxBase &other) const { return contains(other.min) && contains(other.max); } bool overlap(const BoundingBoxBase &other) const { - return ! (this->max(0) < other.min(0) || this->min(0) > other.max(0) || - this->max(1) < other.min(1) || this->min(1) > other.max(1)); + return ! (this->max.x() < other.min.x() || this->min.x() > other.max.x() || + this->max.y() < other.min.y() || this->min.y() > other.max.y()); } bool operator==(const BoundingBoxBase &rhs) { return this->min == rhs.min && this->max == rhs.max; } bool operator!=(const BoundingBoxBase &rhs) { return ! (*this == rhs); } @@ -79,7 +79,7 @@ public: BoundingBox3Base() : BoundingBoxBase() {} BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) : BoundingBoxBase(pmin, pmax) - { if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; } + { if (pmin.z() >= pmax.z()) BoundingBoxBase::defined = false; } BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) : BoundingBoxBase(p1, p1) { merge(p2); merge(p3); } @@ -96,7 +96,7 @@ public: this->min = this->min.cwiseMin(vec); this->max = this->max.cwiseMax(vec); } - this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2)); + this->defined = (this->min.x() < this->max.x()) && (this->min.y() < this->max.y()) && (this->min.z() < this->max.z()); } BoundingBox3Base(const std::vector &points) @@ -116,15 +116,17 @@ public: coordf_t max_size() const; bool contains(const PointClass &point) const { - return BoundingBoxBase::contains(point) && point(2) >= this->min(2) && point(2) <= this->max(2); + return BoundingBoxBase::contains(point) && point.z() >= this->min.z() && point.z() <= this->max.z(); } bool contains(const BoundingBox3Base& other) const { return contains(other.min) && contains(other.max); } + // Intersects without boundaries. bool intersects(const BoundingBox3Base& other) const { - return (this->min(0) < other.max(0)) && (this->max(0) > other.min(0)) && (this->min(1) < other.max(1)) && (this->max(1) > other.min(1)) && (this->min(2) < other.max(2)) && (this->max(2) > other.min(2)); + return this->min.x() < other.max.x() && this->max.x() > other.min.x() && this->min.y() < other.max.y() && this->max.y() > other.min.y() && + this->min.z() < other.max.z() && this->max.z() > other.min.z(); } }; @@ -212,13 +214,13 @@ public: template inline bool empty(const BoundingBoxBase &bb) { - return ! bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1); + return ! bb.defined || bb.min.x() >= bb.max.x() || bb.min.y() >= bb.max.y(); } template inline bool empty(const BoundingBox3Base &bb) { - return ! bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1) || bb.min(2) >= bb.max(2); + return ! bb.defined || bb.min.x() >= bb.max.x() || bb.min.y() >= bb.max.y() || bb.min.z() >= bb.max.z(); } inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index c6dfb1223..77d99d14a 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -5,6 +5,7 @@ #include #include #include +#include // for hash #include #include #include @@ -280,7 +281,7 @@ namespace int128 { // To be used by std::unordered_map, std::unordered_multimap and friends. struct PointHash { - size_t operator()(const Vec2crd &pt) const { + size_t operator()(const Vec2crd &pt) const noexcept { return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y()); } }; @@ -511,12 +512,13 @@ inline Point align_to_grid(Point coord, Point spacing, Point base) } // namespace Slic3r +/* namespace std { - template <> struct hash { - size_t operator()(const Slic3r::Point &p) const - { return (89 * 31 + p.x()) * 31 + p.y(); } + template<> struct hash { + size_t operator()(const Slic3r::Point& p) const noexcept { return Slic3r::PointHash{}(p); } }; } +*/ // start Boost #include diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 5282f6c77..547664d4f 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -61,6 +61,9 @@ public: } } + Point& operator[](Points::size_type idx) { return this->points[idx]; } + const Point& operator[](Points::size_type idx) const { return this->points[idx]; } + const Point& last_point() const override { return this->points.back(); } const Point& leftmost_point() const; Lines lines() const override; From 665d1a94b4d8bcb090f39693f84ac3ce0b80bb3c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 27 Jul 2022 08:54:26 +0200 Subject: [PATCH 08/29] WIP Tree Supports: Major changes, it does not compile yet. --- src/libslic3r/PrintConfig.cpp | 2 + src/libslic3r/PrintConfig.hpp | 4 +- src/libslic3r/PrintObject.cpp | 10 +- src/libslic3r/TreeModelVolumes.cpp | 989 +++++------- src/libslic3r/TreeModelVolumes.hpp | 354 +++-- src/libslic3r/TreeSupport.cpp | 2323 ++++++++++++++-------------- src/libslic3r/TreeSupport.hpp | 291 +--- 7 files changed, 1861 insertions(+), 2112 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d32caf3c0..bab9109b7 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2770,8 +2770,10 @@ void PrintConfigDef::init_fff_params() def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("grid"); def->enum_values.push_back("snug"); + def->enum_values.push_back("tree"); def->enum_labels.push_back(L("Grid")); def->enum_labels.push_back(L("Snug")); + def->enum_labels.push_back(L("Tree")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(smsGrid)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 9e1d7989d..88ebd9470 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -60,7 +60,7 @@ enum InfillPattern : int { ipRectilinear, ipMonotonic, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipLightning, -ipCount, + ipCount, }; enum class IroningType { @@ -85,7 +85,7 @@ enum SupportMaterialPattern { }; enum SupportMaterialStyle { - smsGrid, smsSnug, + smsGrid, smsSnug, smsTree, }; enum SupportMaterialInterfacePattern { diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index aef501922..29cccd24e 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -8,6 +8,7 @@ #include "Layer.hpp" #include "MutablePolygon.hpp" #include "SupportMaterial.hpp" +#include "TreeSupport.hpp" #include "Surface.hpp" #include "Slicing.hpp" #include "Tesselate.hpp" @@ -2127,8 +2128,13 @@ void PrintObject::combine_infill() void PrintObject::_generate_support_material() { - PrintObjectSupportMaterial support_material(this, m_slicing_params); - support_material.generate(*this); + if (m_config.support_material_style == smsTree) { + TreeSupport tree_support; + tree_support.generateSupportAreas(); + } else { + PrintObjectSupportMaterial support_material(this, m_slicing_params); + support_material.generate(*this); + } } static void project_triangles_to_slabs(ConstLayerPtrsAdaptor layers, const indexed_triangle_set &custom_facets, const Transform3f &tr, bool seam, std::vector &out) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 4f445a307..ee208c545 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -9,164 +9,217 @@ #include "TreeModelVolumes.hpp" #include "TreeSupport.hpp" +#include "BuildVolume.hpp" +#include "ClipperUtils.hpp" +#include "Flow.hpp" +#include "Layer.hpp" +#include "Point.hpp" +#include "Print.hpp" +#include "PrintConfig.hpp" + +#include + #include #include namespace Slic3r { -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 +TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &print_object) +{ + const PrintConfig &print_config = print_object.print()->config(); + const PrintObjectConfig &config = print_object.config(); + const SlicingParameters &slicing_params = print_object.slicing_parameters(); +// const std::vector printing_extruders = print_object.object_extruders(); + + // Support must be enabled and set to Tree style. + assert(config.support_material); + assert(config.support_material_style == smsTree); + + coordf_t external_perimeter_width = 0.; + for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = print_object.printing_region(region_id); + external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(print_object, frExternalPerimeter, config.layer_height).width())); + } + + this->layer_height = scaled(config.layer_height.value); + this->resolution = scaled(print_config.resolution.value); + this->min_feature_size = scaled(config.min_feature_size.value); + this->support_angle = M_PI / 2. - config.support_angle * M_PI / 180.; + this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width(); + this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width(); + //FIXME add it to SlicingParameters and reuse in both tree and normal supports? + this->support_bottom_enable = config.support_material_interface_layers.value > 0 && config.support_material_bottom_interface_layers.value != 0; + this->support_bottom_height = this->support_bottom_enable ? + (config.support_material_bottom_interface_layers.value > 0 ? + config.support_material_bottom_interface_layers.value : + config.support_material_interface_layers.value) * this->layer_height : + 0; + this->support_material_buildplate_only = config.support_material_buildplate_only; +// this->support_xy_overrides_z = + this->support_xy_distance = scaled(config.support_material_xy_spacing.get_abs_value(external_perimeter_width)); + this->support_xy_distance_overhang = std::min(this->support_xy_distance, scaled(0.5 * external_perimeter_width)); + this->support_top_distance = scaled(slicing_params.gap_support_object); + this->support_bottom_distance = scaled(slicing_params.gap_object_support); +// this->support_interface_skip_height = +// this->support_infill_angles = + this->support_roof_enable = config.support_material_interface_layers.value > 0; + this->support_roof_height = config.support_material_interface_layers.value * this->layer_height; +// this->minimum_roof_area = +// this->support_roof_angles = + this->support_roof_pattern = config.support_material_interface_pattern; + this->support_pattern = config.support_material_pattern; + this->support_line_spacing = scaled(config.support_material_spacing.value); +// this->support_bottom_offset = + this->support_wall_count = config.support_material_with_sheath ? 1 : 0; + this->support_roof_line_distance = scaled(config.support_material_interface_spacing.value) + this->support_roof_line_width; +// this->minimum_support_area = +// this->minimum_bottom_area = +// this->support_offset = +} + +static Polygons calculateMachineBorderCollision(Polygon machine_border) +{ + Polygons machine_volume_border; + // Put a border of 1m around the print volume so that we don't collide. + append(machine_volume_border, offset(machine_border, scaled(1000.))); + machine_border.reverse(); // Makes the polygon negative so that we subtract the actual volume from the collision area. + machine_volume_border.emplace_back(std::move(machine_border)); + return machine_volume_border; +} + +TreeModelVolumes::TreeModelVolumes( + const PrintObject &print_object, + const BuildVolume &build_volume, + const coord_t max_move, const coord_t max_move_slow, size_t current_mesh_idx, + double progress_multiplier, double progress_offset, const std::vector& additional_excluded_areas) : + m_max_move{ std::max(max_move - 2, coord_t(0)) }, m_max_move_slow{ std::max(max_move_slow - 2, coord_t(0)) }, m_progress_multiplier{ progress_multiplier }, m_progress_offset{ progress_offset }, + // -2 to avoid rounding errors + m_machine_border{ calculateMachineBorderCollision(build_volume.polygon()) } { - 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++) - { + +#if 0 + 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)) - { + for (size_t idx = 0; idx < m_layer_outlines.size(); ++ idx) + if (TreeSupport::TreeSupportSettings(m_layer_outlines[idx].first) == TreeSupport::TreeSupportSettings(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())); + if (! added) { + mesh_to_layeroutline_idx[mesh_idx] = m_layer_outlines.size(); + m_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_xy_overrides_z) - { - 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]; - - tbb::parallel_for(tbb::blocked_range(0, layer_outlines_[mesh_to_layeroutline_idx[mesh_idx]].second.size()), - [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ 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); - } - }); - } - - tbb::parallel_for(tbb::blocked_range(0, anti_overhang_.size()), - [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ 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++) - { - tbb::parallel_for(tbb::blocked_range(0, anti_overhang_.size()), + for (size_t idx = 0; idx < m_layer_outlines.size(); ++ idx) { + tbb::parallel_for(tbb::blocked_range(0, m_layer_outlines[idx].second.size()), [&](const tbb::blocked_range &range) { for (const size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) - layer_outlines_[idx].second[layer_idx] = layer_outlines_[idx].second[layer_idx].unionPolygons(); + m_layer_outlines[idx].second[layer_idx] = union_(m_layer_outlines[idx].second[layer_idx]); }); } - radius_0 = config.getRadius(0); -} + m_current_outline_idx = mesh_to_layeroutline_idx[current_mesh_idx]; + m_support_rests_on_model = false; + m_min_resolution = std::numeric_limits::max(); + for (auto data_pair : m_layer_outlines) { + m_support_rests_on_model |= ! data_pair.first.support_material_buildplate_only; + m_min_resolution = std::min(m_min_resolution, data_pair.first.resolution); + } +#else + { + m_anti_overhang = print_object.slice_support_blockers(); + mesh_to_layeroutline_idx[0] = 0; + TreeSupportMeshGroupSettings mesh_settings(print_object); + m_layer_outlines.emplace_back(mesh_settings, std::vector{}); + m_current_outline_idx = 0; + std::vector &outlines = m_layer_outlines.front().second; + outlines.reserve(print_object.layer_count()); + for (const Layer *layer : print_object.layers()) + outlines.emplace_back(to_polygons(expolygons_simplify(layer->lslices, mesh_settings.resolution))); + } +#endif + + const TreeSupport::TreeSupportSettings &config = m_layer_outlines[m_current_outline_idx].first; + if (! config.support_xy_overrides_z) { + m_current_min_xy_dist = config.xy_min_distance; + if (TreeSupport::TreeSupportSettings::has_to_rely_on_min_xy_dist_only) + m_current_min_xy_dist = std::max(m_current_min_xy_dist, coord_t(100)); + m_current_min_xy_dist_delta = std::max(config.xy_distance - m_current_min_xy_dist, coord_t(0)); + } else { + m_current_min_xy_dist = config.xy_distance; + m_current_min_xy_dist_delta = 0; + } + m_increase_until_radius = config.increase_radius_until_radius; + m_radius_0 = config.getRadius(0); + +#if 0 + for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { + SliceMeshStorage mesh = storage.meshes[mesh_idx]; + tbb::parallel_for(tbb::blocked_range(0, m_layer_outlines[mesh_to_layeroutline_idx[mesh_idx]].second.size()), + [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) + if (layer_idx < mesh.layer_nr_max_filled_layer) { + Polygons outline = extractOutlineFromMesh(mesh, layer_idx); + append(m_layer_outlines[mesh_to_layeroutline_idx[mesh_idx]].second[layer_idx], outline); + } + }); + } + if (! additional_excluded_areas.empty()) { + tbb::parallel_for(tbb::blocked_range(0, m_anti_overhang.size()), + [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + if (layer_idx < coord_t(additional_excluded_areas.size())) + append(m_anti_overhang[layer_idx], additional_excluded_areas[layer_idx]); + // if (SUPPORT_TREE_AVOID_SUPPORT_BLOCKER) + // append(m_anti_overhang[layer_idx], storage.support.supportLayers[layer_idx].anti_overhang); + //FIXME block wipe tower + // if (storage.primeTower.enabled) + // append(m_anti_overhang[layer_idx], layer_idx == 0 ? storage.primeTower.outer_poly_first_layer : storage.primeTower.outer_poly); + m_anti_overhang[layer_idx] = union_(m_anti_overhang[layer_idx]); + } + }); + } +#endif +} void TreeModelVolumes::precalculate(coord_t max_layer) { auto t_start = std::chrono::high_resolution_clock::now(); - precalculated = true; + m_precalculated = true; // Get the config corresponding to one mesh that is in the current group. Which one has to be irrelevant. Not the prettiest way to do this, but it ensures some calculations that may be a bit more complex like inital layer diameter are only done in once. - TreeSupport::TreeSupportSettings config(layer_outlines_[current_outline_idx].first); + TreeSupport::TreeSupportSettings config(m_layer_outlines[m_current_outline_idx].first); // calculate which radius each layer in the tip may have. std::unordered_set possible_tip_radiis; - for (size_t dtt = 0; dtt <= config.tip_layers; dtt++) - { + 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)); + possible_tip_radiis.emplace(ceilRadius(config.getRadius(dtt) + m_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++) - { + 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_regular_radius = ceilRadius(config.getRadius(simulated_dtt, 0) + m_current_min_xy_dist_delta); const coord_t max_min_radius = ceilRadius(config.getRadius(simulated_dtt, 0)); // the maximal radius that the radius with the min_xy_dist can achieve - const coord_t max_initial_layer_diameter_radius = ceilRadius(config.recommendedMinRadius(current_layer) + current_min_xy_dist_delta); + const coord_t max_initial_layer_diameter_radius = ceilRadius(config.recommendedMinRadius(current_layer) + m_current_min_xy_dist_delta); if (!radius_until_layer.count(max_regular_radius)) - { radius_until_layer[max_regular_radius] = current_layer; - } if (!radius_until_layer.count(max_min_radius)) - { radius_until_layer[max_min_radius] = current_layer; - } if (!radius_until_layer.count(max_initial_layer_diameter_radius)) - { radius_until_layer[max_initial_layer_diameter_radius] = current_layer; - } } // Copy to deque to use in parallel for later. @@ -177,13 +230,11 @@ void TreeModelVolumes::precalculate(coord_t max_layer) // 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. + radius_until_layer[ceilRadius(m_increase_until_radius, false)] = max_layer; // To calculate collision holefree for every radius, the collision of radius m_increase_until_radius will be required. // 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; - } + if (m_current_min_xy_dist_delta != 0) + radius_until_layer[m_current_min_xy_dist_delta] = max_layer; std::deque relevant_collision_radiis; relevant_collision_radiis.insert(relevant_collision_radiis.end(), radius_until_layer.begin(), radius_until_layer.end()); // Now that required_avoidance_limit contains the maximum of ild and regular required radius just copy. @@ -195,12 +246,8 @@ void TreeModelVolumes::precalculate(coord_t max_layer) // 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) - { + if (key.first < m_increase_until_radius + m_current_min_xy_dist_delta) relevant_hole_collision_radiis.emplace_back(key); - } - } // ### Calculate collisions without holes, build from regular collision calculateCollisionHolefree(relevant_hole_collision_radiis); @@ -212,7 +259,7 @@ void TreeModelVolumes::precalculate(coord_t max_layer) tbb::task_group task_group; task_group.run([this, relevant_avoidance_radiis]{ calculateAvoidance(relevant_avoidance_radiis); }); task_group.run([this, relevant_avoidance_radiis]{ calculateWallRestrictions(relevant_avoidance_radiis); }); - if (support_rests_on_model) + if (m_support_rests_on_model) task_group.run([this, relevant_avoidance_radiis_to_model]{ calculatePlaceables(relevant_avoidance_radiis_to_model); calculateAvoidanceToModel(relevant_avoidance_radiis_to_model); @@ -223,174 +270,143 @@ void TreeModelVolumes::precalculate(coord_t max_layer) 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); + BOOST_LOG_TRIVIAL(info) << "Precalculating collision took" << dur_col << " ms. Precalculating avoidance took " << dur_avo << " ms."; } -const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) +/*! + * \brief Checks a cache for a given RadiusLayerPair and returns it if it is found + * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. + * \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found) + */ +std::optional> getArea(const TreeModelVolumes::RadiusLayerPolygonCache& cache, const TreeModelVolumes::RadiusLayerPair& key) +{ + const auto it = cache.find(key); + return it == cache.end() ? std::optional>{} : std::optional>{ it->second }; +} + +const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const { coord_t orig_radius = radius; std::optional> result; if (!min_xy_dist) - { - radius += current_min_xy_dist_delta; - } + radius += m_current_min_xy_dist_delta; // special case as if a radius 0 is requested it could be to ensure correct xy distance. As such it is beneficial if the collision is as close to the configured values as possible. if (orig_radius != 0) - { radius = ceilRadius(radius); - } RadiusLayerPair key{ radius, layer_idx }; { - std::lock_guard critical_section_support_max_layer_nr(*critical_avoidance_cache_); - result = getArea(collision_cache_, key); + std::lock_guard critical_section_support_max_layer_nr(*m_critical_avoidance_cache); + result = getArea(m_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); + if (m_precalculated) { + BOOST_LOG_TRIVIAL(warning) << "Had to calculate collision at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Collision requested.", false); } - calculateCollision(key); + const_cast(this)->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 Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const { coord_t orig_radius = radius; std::optional> result; if (!min_xy_dist) - { - radius += current_min_xy_dist_delta; - } - if (radius >= increase_until_radius + current_min_xy_dist_delta) - { + radius += m_current_min_xy_dist_delta; + if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta) return getCollision(orig_radius, layer_idx, min_xy_dist); - } + RadiusLayerPair key{ radius, layer_idx }; { - std::lock_guard critical_section_support_max_layer_nr(*critical_collision_cache_holefree_); - result = getArea(collision_cache_holefree_, key); + std::lock_guard critical_section_support_max_layer_nr(*m_critical_collision_cache_holefree); + result = getArea(m_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); + if (m_precalculated) { + BOOST_LOG_TRIVIAL(warning) << "Had to calculate collision holefree at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Holefree Collision requested.", false); } - calculateCollisionHolefree(key); + const_cast(this)->calculateCollisionHolefree(key); return getCollisionHolefree(orig_radius, layer_idx, min_xy_dist); } -const Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) +const Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) const { if (layer_idx == 0) // What on the layer directly above buildplate do i have to avoid to reach the buildplate ... - { return getCollision(radius, layer_idx, min_xy_dist); - } coord_t orig_radius = radius; std::optional> result; if (!min_xy_dist) - { - radius += current_min_xy_dist_delta; - } + radius += m_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 - { + if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta && type == AvoidanceType::FAST_SAFE) // no holes anymore by definition at this request type = AvoidanceType::FAST; - } const RadiusLayerPair key{ radius, layer_idx }; - std::unordered_map* cache_ptr = nullptr; + const RadiusLayerPolygonCache* cache_ptr = nullptr; std::mutex* mutex_ptr; - if (!to_model && type == AvoidanceType::FAST) - { - 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 - { - logError("Invalid Avoidance Request\n"); + if (!to_model && type == AvoidanceType::FAST) { + cache_ptr = &m_avoidance_cache; + mutex_ptr = m_critical_avoidance_cache.get(); + } else if (!to_model && type == AvoidanceType::SLOW) { + cache_ptr = &m_avoidance_cache_slow; + mutex_ptr = m_critical_avoidance_cache_slow.get(); + } else if (!to_model && type == AvoidanceType::FAST_SAFE) { + cache_ptr = &m_avoidance_cache_hole; + mutex_ptr = m_critical_avoidance_cache_holefree.get(); + } else if (to_model && type == AvoidanceType::FAST) { + cache_ptr = &m_avoidance_cache_to_model; + mutex_ptr = m_critical_avoidance_cache_to_model.get(); + } else if (to_model && type == AvoidanceType::SLOW) { + cache_ptr = &m_avoidance_cache_to_model_slow; + mutex_ptr = m_critical_avoidance_cache_to_model_slow.get(); + } else if (to_model && type == AvoidanceType::FAST_SAFE) { + cache_ptr = &m_avoidance_cache_hole_to_model; + mutex_ptr = m_critical_avoidance_cache_holefree_to_model.get(); + } else { + BOOST_LOG_TRIVIAL(error) << "Invalid Avoidance Request"; TreeSupport::showError("Invalid Avoidance Request.\n", true); } - - if (to_model) - { + 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); + if (m_precalculated) { + BOOST_LOG_TRIVIAL(warning) << "Had to calculate Avoidance to model at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Avoidance(to model) requested.", false); } - calculateAvoidanceToModel(key); - } - else - { + const_cast(this)->calculateAvoidanceToModel(key); + } else { { std::lock_guard critical_section(*mutex_ptr); result = getArea(*cache_ptr, key); } if (result) - { return result.value().get(); - } - if (precalculated) - { - logWarning("Had to calculate Avoidance at radius %lld and layer %lld, but precalculate was called. Performance may suffer!\n", key.first, key.second); + if (m_precalculated) { + BOOST_LOG_TRIVIAL(warning) << "Had to calculate Avoidance at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Avoidance(to buildplate) requested.", false); } - calculateAvoidance(key); + const_cast(this)->calculateAvoidance(key); } return getAvoidance(orig_radius, layer_idx, type, to_model, min_xy_dist); // retrive failed and correct result was calculated. Now it has to be retrived. } -const Polygons& TreeModelVolumes::getPlaceableAreas(coord_t radius, LayerIndex layer_idx) +const Polygons& TreeModelVolumes::getPlaceableAreas(coord_t radius, LayerIndex layer_idx) const { std::optional> result; const coord_t orig_radius = radius; @@ -398,116 +414,87 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(coord_t radius, LayerIndex l RadiusLayerPair key{ radius, layer_idx }; { - std::lock_guard critical_section(*critical_placeable_areas_cache_); - result = getArea(placeable_areas_cache_, key); + std::lock_guard critical_section(*m_critical_placeable_areas_cache); + result = getArea(m_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 (m_precalculated) { + BOOST_LOG_TRIVIAL(warning) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Placeable areas requested.", false); } if (radius != 0) - { - calculatePlaceables(key); - } + const_cast(this)->calculatePlaceables(key); else - { getCollision(0, layer_idx, true); - } return getPlaceableAreas(orig_radius, layer_idx); } - -const Polygons& TreeModelVolumes::getWallRestriction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) +const Polygons& TreeModelVolumes::getWallRestriction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const { if (layer_idx == 0) // Should never be requested as there will be no going below layer 0 ..., but just to be sure some semi-sane catch. Alternative would be empty Polygon. - { return getCollision(radius, layer_idx, min_xy_dist); - } coord_t orig_radius = radius; - min_xy_dist = min_xy_dist && current_min_xy_dist_delta > 0; + min_xy_dist = min_xy_dist && m_current_min_xy_dist_delta > 0; std::optional> result; radius = ceilRadius(radius); const RadiusLayerPair key{ radius, layer_idx }; - std::unordered_map* cache_ptr; - if (min_xy_dist) - { - cache_ptr = &wall_restrictions_cache_min_; - } - else - { - cache_ptr = &wall_restrictions_cache_; - } + const RadiusLayerPolygonCache* cache_ptr = min_xy_dist ? &m_wall_restrictions_cache_min : &m_wall_restrictions_cache; - - if (min_xy_dist) - { + if (min_xy_dist) { { - std::lock_guard critical_section(*critical_wall_restrictions_cache_min_); + std::lock_guard critical_section(*m_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); + if (m_precalculated) { + BOOST_LOG_TRIVIAL(warning) << "Had to calculate Wall restricions at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Wall restriction of minimum xy distance requested ).", false); } - } - else - { + } else { { - std::lock_guard critical_section(*critical_wall_restrictions_cache_); + std::lock_guard critical_section(*m_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); + if (m_precalculated) { + BOOST_LOG_TRIVIAL(warning) << "Had to calculate Wall restricions at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Wall restriction requested ).", false); } } - calculateWallRestrictions(key); + const_cast(this)->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, bool min_xy_dist) const { - if (!min_xy_dist) - { - radius += current_min_xy_dist_delta; - } + if (! min_xy_dist) + radius += m_current_min_xy_dist_delta; return ceilRadius(radius); } + coord_t TreeModelVolumes::getRadiusNextCeil(coord_t radius, bool min_xy_dist) const { coord_t ceiled_radius = ceilRadius(radius, min_xy_dist); - - if (!min_xy_dist) - ceiled_radius -= current_min_xy_dist_delta; + if (! min_xy_dist) + ceiled_radius -= m_current_min_xy_dist_delta; return ceiled_radius; } -bool TreeModelVolumes::checkSettingsEquality(const TreeSupportMeshGroupSettings& me, const TreeSupportMeshGroupSettings& other) const +static inline [[nodiscard]] Polygons simplify(const Polygons &polygons, coord_t resolution) { - return TreeSupport::TreeSupportSettings(me) == TreeSupport::TreeSupportSettings(other); + //FIXME + return polygons; } - -Polygons TreeModelVolumes::extractOutlineFromMesh(const SliceMeshStorage& mesh, LayerIndex layer_idx) const +#if 0 +Polygons TreeModelVolumes::extractOutlineFromMesh(const PrintObject &print_object, LayerIndex layer_idx) const { constexpr bool external_polys_only = false; Polygons total; @@ -515,223 +502,172 @@ Polygons TreeModelVolumes::extractOutlineFromMesh(const SliceMeshStorage& mesh, // 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; + total = union_(total, layer.openPolyLines.offsetPolyLine(100)); + coord_t resolution = mesh.settings.get("resolution"); + return simplify(total, resolution); } +#endif -LayerIndex TreeModelVolumes::getMaxCalculatedLayer(coord_t radius, const std::unordered_map& map) const +LayerIndex TreeModelVolumes::getMaxCalculatedLayer(coord_t radius, const RadiusLayerPolygonCache& map) const { LayerIndex max_layer = -1; - // the placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist. const RadiusLayerPair key_layer_1(radius, 1); if (getArea(map, key_layer_1)) - { max_layer = 1; - } - while (map.count(RadiusLayerPair(radius, max_layer + 1))) - { max_layer++; - } - return max_layer; } - void TreeModelVolumes::calculateCollision(std::deque keys) { tbb::parallel_for(tbb::blocked_range(0, keys.size()), [&](const tbb::blocked_range &range) { - for (const size_t i = range.begin(); i < range.end(); ++ i) { + for (size_t i = range.begin(); i < range.end(); ++ 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++) + RadiusLayerPolygonCache data_outer; + RadiusLayerPolygonCache data_placeable_outer; + for (size_t outline_idx = 0; outline_idx < m_layer_outlines.size(); outline_idx++) { - std::unordered_map data; - std::unordered_map data_placeable; + RadiusLayerPolygonCache data; + RadiusLayerPolygonCache 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 coord_t layer_height = m_layer_outlines[outline_idx].first.layer_height; + const bool support_rests_on_this_model = ! m_layer_outlines[outline_idx].first.support_material_buildplate_only; + const coord_t z_distance_bottom = m_layer_outlines[outline_idx].first.support_bottom_distance; const size_t z_distance_bottom_layers = round_up_divide(z_distance_bottom, layer_height); - const coord_t z_distance_top_layers = round_up_divide(layer_outlines_[outline_idx].first.get("support_top_distance"), layer_height); - const LayerIndex max_anti_overhang_layer = anti_overhang_.size() - 1; + const coord_t z_distance_top_layers = round_up_divide(m_layer_outlines[outline_idx].first.support_top_distance, layer_height); + const LayerIndex max_anti_overhang_layer = m_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. + const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : m_layer_outlines[outline_idx].first.support_xy_distance; + // technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all not currently processing meshes as this delta will be added at request time. // avoiding this would require saving each collision for each outline_idx separately. // and later for each avoidance... But avoidance calculation has to be for the whole scene and can NOT be done for each outline_idx separately and combined later. // so avoiding this inaccuracy seems infeasible as it would require 2x the avoidance calculations => 0.5x the performance. coord_t min_layer_bottom; { - std::lock_guard critical_section(*critical_collision_cache_); - min_layer_bottom = getMaxCalculatedLayer(radius, collision_cache_) - z_distance_bottom_layers; + std::lock_guard critical_section(*m_critical_collision_cache); + min_layer_bottom = getMaxCalculatedLayer(radius, m_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++) - { + 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! + Polygons collision_areas = m_machine_border; + if (size_t(layer_idx) < m_layer_outlines[outline_idx].second.size()) + append(collision_areas, m_layer_outlines[outline_idx].second[layer_idx]); + // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. + collision_areas = offset(union_ex(collision_areas), radius + xy_distance, ClipperLib::jtMiter, 1.2); + append(data[key], 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--) - { + 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(); + for (size_t layer_offset = 1; layer_offset <= z_distance_bottom_layers && layer_idx - coord_t(layer_offset) > min_layer_bottom; ++ layer_offset) + append(data[key], data[RadiusLayerPair(radius, layer_idx - layer_offset)]); + if (support_rests_on_this_model && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) { + data[key] = union_(data[key]); 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); + // just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly. + above = max_anti_overhang_layer >= layer_idx + 1 ? union_(above, m_anti_overhang[layer_idx]) : union_(above); + Polygons placeable = diff(data[key], above); + data_placeable[RadiusLayerPair(radius, layer_idx + 1)] = union_(data_placeable[RadiusLayerPair(radius, layer_idx + 1)], 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++) - { + 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(); - } + append(data[key], data[RadiusLayerPair(radius, layer_idx + layer_offset)]); + data[key] = max_anti_overhang_layer >= layer_idx ? union_(data[key], offset(union_ex(m_anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(data[key]); } - 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 (LayerIndex layer_idx = max_required_layer; layer_idx > keys[i].second; layer_idx--) { + // all these dont have the correct z_distance_top_layers as they can still have areas above them + auto it = data.find(RadiusLayerPair(radius, layer_idx)); + if (it != data.end()) + data.erase(it); } - for (auto pair : data) - { - pair.second.simplify(min_maximum_resolution_, min_maximum_deviation_); - data_outer[pair.first] = data_outer[pair.first].unionPolygons(pair.second); + for (auto pair : data) { + pair.second = simplify(pair.second, m_min_resolution); + data_outer[pair.first] = union_(data_outer[pair.first], 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); + for (auto pair : data_placeable) { + pair.second = simplify(pair.second, m_min_resolution); + data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], pair.second); } - } } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS { - 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(*m_critical_progress); + if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL) { + m_precalculation_progress += TREE_PROGRESS_PRECALC_COLL / keys.size(); + Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); } } +#endif { - std::lock_guard critical_section(*critical_collision_cache_); - collision_cache_.insert(data_outer.begin(), data_outer.end()); + std::lock_guard critical_section(*m_critical_collision_cache); + m_collision_cache.insert(data_outer.begin(), data_outer.end()); } - if (radius == 0) - { - { - std::lock_guard critical_section(*critical_placeable_areas_cache_); - placeable_areas_cache_.insert(data_placeable_outer.begin(), data_placeable_outer.end()); - } + if (radius == 0) { + std::lock_guard critical_section(*m_critical_placeable_areas_cache); + m_placeable_areas_cache.insert(data_placeable_outer.begin(), data_placeable_outer.end()); } } }); } + 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); - } tbb::parallel_for(tbb::blocked_range(0, max_layer + 1), [&](const tbb::blocked_range &range) { - for (const size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - std::unordered_map data; - for (RadiusLayerPair key : keys) - { - // Logically increase the collision by increase_until_radius + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + RadiusLayerPolygonCache data; + for (RadiusLayerPair key : keys) { + // Logically increase the collision by m_increase_until_radius coord_t radius = key.first; - coord_t increase_radius_ceil = ceilRadius(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_); + coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - ceilRadius(radius, true); + // this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area. + Polygons col = offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound); + col = simplify(col, m_min_resolution); data[RadiusLayerPair(radius, layer_idx)] = col; } - { - std::lock_guard critical_section(*critical_collision_cache_holefree_); - collision_cache_holefree_.insert(data.begin(), data.end()); - } + std::lock_guard critical_section(*m_critical_collision_cache_holefree); + m_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 +static Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) { const size_t steps = std::abs(distance / max_safe_step_distance); assert(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); + ExPolygons ret = union_ex(me); + for (size_t i = 0; i < steps; ++ i) + ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, 1.2), collision)); + return union_(offset(ret, distance % max_safe_step_distance, jt, 1.2), collision); } void TreeModelVolumes::calculateAvoidance(std::deque keys) @@ -740,7 +676,7 @@ void TreeModelVolumes::calculateAvoidance(std::deque keys) const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; tbb::parallel_for(tbb::blocked_range(0, keys.size() * 3), [&, keys, all_types](const tbb::blocked_range &range) { - for (const size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { + for (size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { size_t key_idx = iter_idx / 3; { size_t type_idx = iter_idx % all_types.size(); @@ -752,74 +688,62 @@ void TreeModelVolumes::calculateAvoidance(std::deque keys) 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; - } + if (holefree && radius >= m_increase_until_radius + m_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); + const coord_t offset_speed = slow ? m_max_move_slow : m_max_move; + const coord_t max_step_move = std::max(1.9 * radius, m_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_); + std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_slow : holefree ? m_critical_avoidance_cache_holefree : m_critical_avoidance_cache)); + start_layer = 1 + getMaxCalculatedLayer(radius, slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_hole : m_avoidance_cache); } - if (start_layer > max_required_layer) - { - logDebug("Requested calculation for value already calculated ?\n"); - return; + if (start_layer > max_required_layer) { + BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; + 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++) - { + 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); - } + Polygons col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ? + getCollisionHolefree(radius, layer, true) : + getCollision(radius, layer, true); latest_avoidance = safeOffset(latest_avoidance, -offset_speed, ClipperLib::jtRound, -max_step_move, col); - latest_avoidance.simplify(min_maximum_resolution_, min_maximum_deviation_); + latest_avoidance = simplify(latest_avoidance, m_min_resolution); data[layer] = std::pair(key, latest_avoidance); } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS { - 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(*m_critical_progress); + if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { + m_precalculation_progress += m_support_rests_on_model ? 0.4 : 1 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3); + Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); } } +#endif { - 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()); + std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_slow : holefree ? m_critical_avoidance_cache_holefree : m_critical_avoidance_cache)); + (slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_hole : m_avoidance_cache).insert(data.begin(), data.end()); } } } }); - return ret; } void TreeModelVolumes::calculatePlaceables(std::deque keys) { tbb::parallel_for(tbb::blocked_range(0, keys.size()), [&, keys](const tbb::blocked_range &range) { - for (const size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { + for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { const coord_t radius = keys[key_idx].first; const LayerIndex max_required_layer = keys[key_idx].second; std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); @@ -827,58 +751,52 @@ void TreeModelVolumes::calculatePlaceables(std::deque keys) LayerIndex start_layer; { - std::lock_guard critical_section(*critical_placeable_areas_cache_); - start_layer = 1 + getMaxCalculatedLayer(radius, placeable_areas_cache_); + std::lock_guard critical_section(*m_critical_placeable_areas_cache); + start_layer = 1 + getMaxCalculatedLayer(radius, m_placeable_areas_cache); } - if (start_layer > max_required_layer) - { - logDebug("Requested calculation for value already calculated ?\n"); - return; + if (start_layer > max_required_layer) { + BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; + continue; } - if (start_layer == 0) - { - data[0] = std::pair(key, machine_border_.difference(getCollision(radius, 0, true))); + if (start_layer == 0) { + data[0] = std::pair(key, diff(m_machine_border, getCollision(radius, 0, true))); start_layer = 1; } - for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) - { + 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); - + placeable = simplify(placeable, m_min_resolution); // it is faster to do this here in each thread than once in calculateCollision. + placeable = offset(union_ex(placeable), - radius); data[layer] = std::pair(key, placeable); } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS { - 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(*m_critical_progress); + if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { + m_precalculation_progress += 0.2 * TREE_PROGRESS_PRECALC_AVO / (keys.size()); + Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); } } +#endif { - std::lock_guard critical_section(*critical_placeable_areas_cache_); - placeable_areas_cache_.insert(data.begin(), data.end()); + std::lock_guard critical_section(*m_critical_placeable_areas_cache); + m_placeable_areas_cache.insert(data.begin(), data.end()); } } }); - return ret; } - void TreeModelVolumes::calculateAvoidanceToModel(std::deque keys) { // For every RadiusLayer pair there are 3 avoidances that have to be calculated, calculated in the same parallel_for loop for better parallelization. const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; tbb::parallel_for(tbb::blocked_range(0, keys.size() * 3), [&, keys, all_types](const tbb::blocked_range &range) { - for (const size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { + for (size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { size_t key_idx = iter_idx / 3; size_t type_idx = iter_idx % all_types.size(); AvoidanceType type = all_types[type_idx]; @@ -888,13 +806,12 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque key 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; - } + if (holefree && radius >= m_increase_until_radius + m_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); + const coord_t offset_speed = slow ? m_max_move_slow : m_max_move; + const coord_t max_step_move = std::max(1.9 * radius, m_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); @@ -902,13 +819,12 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque key 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_); + std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_to_model_slow : holefree ? m_critical_avoidance_cache_holefree_to_model : m_critical_avoidance_cache_to_model)); + start_layer = 1 + getMaxCalculatedLayer(radius, slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_hole_to_model : m_avoidance_cache_to_model); } - if (start_layer > max_required_layer) - { - logDebug("Requested calculation for value already calculated ?\n"); - return; + if (start_layer > max_required_layer) { + BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; + 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. @@ -919,7 +835,7 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque key key.second = layer; Polygons col = getCollision(radius, layer, true); - if ((slow && radius < increase_until_radius + current_min_xy_dist_delta) || holefree) + if ((slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree) { col = getCollisionHolefree(radius, layer, true); } @@ -928,30 +844,28 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque key col = getCollision(radius, layer, true); } - latest_avoidance = safeOffset(latest_avoidance, -offset_speed, ClipperLib::jtRound, -max_step_move, col).difference(getPlaceableAreas(radius, layer)); + latest_avoidance = diff(safeOffset(latest_avoidance, -offset_speed, ClipperLib::jtRound, -max_step_move, col), getPlaceableAreas(radius, layer)); - latest_avoidance.simplify(min_maximum_resolution_, min_maximum_deviation_); + latest_avoidance = simplify(latest_avoidance, m_min_resolution); data[layer] = std::pair(key, latest_avoidance); } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS { - 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(*m_critical_progress); + if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { + m_precalculation_progress += 0.4 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3); + Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); } } +#endif { - 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()); + std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_to_model_slow : holefree ? m_critical_avoidance_cache_holefree_to_model : m_critical_avoidance_cache_to_model)); + (slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_hole_to_model : m_avoidance_cache_to_model).insert(data.begin(), data.end()); } } }); - - return ret; } @@ -960,7 +874,6 @@ void TreeModelVolumes::calculateWallRestrictions(std::deque key // 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 @@ -994,127 +907,79 @@ void TreeModelVolumes::calculateWallRestrictions(std::deque key tbb::parallel_for(tbb::blocked_range(0, keys.size()), [&, keys](const tbb::blocked_range &range) { - for (const size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { + for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { coord_t radius = keys[key_idx].first; RadiusLayerPair key(radius, 0); coord_t min_layer_bottom; - std::unordered_map data; - std::unordered_map data_min; + RadiusLayerPolygonCache data; + RadiusLayerPolygonCache data_min; { - std::lock_guard critical_section(*critical_wall_restrictions_cache_); - min_layer_bottom = getMaxCalculatedLayer(radius, wall_restrictions_cache_); + std::lock_guard critical_section(*m_critical_wall_restrictions_cache); + min_layer_bottom = getMaxCalculatedLayer(radius, m_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++) - { + + 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_); + Polygons wall_restriction = intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx_below, true)); // radius contains m_current_min_xy_dist_delta already if required + wall_restriction = simplify(wall_restriction, m_min_resolution); data.emplace(key, wall_restriction); - if (current_min_xy_dist_delta > 0) + if (m_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_); + Polygons wall_restriction_min = intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx_below, true)); + wall_restriction = simplify(wall_restriction_min, m_min_resolution); 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(*m_critical_wall_restrictions_cache); + m_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()); + std::lock_guard critical_section(*m_critical_wall_restrictions_cache_min); + m_wall_restrictions_cache_min.insert(data_min.begin(), data_min.end()); } } }); - return ret; } coord_t TreeModelVolumes::ceilRadius(coord_t radius) const { if (radius == 0) - { return 0; - } - if (radius <= radius_0) - { - return radius_0; - } + if (radius <= m_radius_0) + return m_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++) - { + const coord_t stepsize = (exponential_result - m_radius_0) / (SUPPORT_TREE_PRE_EXPONENTIAL_STEPS + 1); + coord_t result = m_radius_0; + for (size_t step = 0; step < SUPPORT_TREE_PRE_EXPONENTIAL_STEPS; step++) { result += stepsize; - if (result >= radius && !ignorable_radii_.count(result)) - { + if (result >= radius && !m_ignorable_radii.count(result)) return result; - } } - while (exponential_result < radius || ignorable_radii_.count(exponential_result)) - { + while (exponential_result < radius || m_ignorable_radii.count(exponential_result)) exponential_result = std::max(coord_t(exponential_result * SUPPORT_TREE_EXPONENTIAL_FACTOR), exponential_result + SUPPORT_TREE_COLLISION_RESOLUTION); - } return exponential_result; } else - { // generates equidistant steps of size SUPPORT_TREE_COLLISION_RESOLUTION starting from 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; - } + { // generates equidistant steps of size SUPPORT_TREE_COLLISION_RESOLUTION starting from m_radius_0. If SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION then this code is dead, and can safely be removed. + coord_t ceil_step_n = (radius - m_radius_0) / SUPPORT_TREE_COLLISION_RESOLUTION; + coord_t resulting_ceil = m_radius_0 + (ceil_step_n + ((radius - m_radius_0) % SUPPORT_TREE_COLLISION_RESOLUTION != 0)) * SUPPORT_TREE_COLLISION_RESOLUTION; + return + radius <= m_radius_0 && radius != 0 ? m_radius_0 : + m_ignorable_radii.count(resulting_ceil) ? ceilRadius(resulting_ceil + 1) : resulting_ceil; } } -template -const std::optional> TreeModelVolumes::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 TreeModelVolumes::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; -} - -} \ No newline at end of file diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 684dd1fe9..82d1a5d9d 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -13,6 +13,9 @@ #include #include +#include + +#include "Point.hpp" #include "Polygon.hpp" #include "PrintConfig.hpp" @@ -22,42 +25,174 @@ namespace Slic3r using LayerIndex = size_t; using AngleRadians = double; -//FIXME -class SliceDataStorage; -class SliceMeshStorage; +class BuildVolume; +class PrintObject; struct TreeSupportMeshGroupSettings { - AngleRadians support_tree_angle; - AngleRadians support_tree_angle_slow; - AngleRadians support_tree_branch_diameter_angle; - coord_t support_tree_bp_diameter; - coord_t support_tree_max_diameter_increase_by_merges_when_support_to_model; - coord_t support_tree_min_height_to_model; - coord_t support_line_width; - coord_t layer_height; - coord_t support_tree_branch_diameter; - coord_t support_tree_tip_diameter; - bool support_bottom_enable; - coord_t support_bottom_height; - bool support_material_buildplate_only; - bool support_xy_overrides_z; - coord_t support_xy_distance; - coord_t support_xy_distance_overhang; - coord_t support_top_distance; - coord_t support_bottom_distance; - coord_t support_interface_skip_height; - std::vector support_infill_angles; - std::vector support_roof_angles; - SupportMaterialInterfacePattern support_roof_pattern; - SupportMaterialPattern support_pattern; - coord_t support_roof_line_width; - coord_t support_line_distance; - coord_t support_bottom_offset; - int support_wall_count; - coord_t meshfix_maximum_deviation; - coord_t meshfix_maximum_resolution; - coord_t support_roof_line_distance; - coord_t min_feature_size; + TreeSupportMeshGroupSettings() = default; + TreeSupportMeshGroupSettings(const PrintObject &print_object); + +/*********************************************************************/ +/* Print parameters, not support specific: */ +/*********************************************************************/ + coord_t layer_height { scaled(0.15) }; + // Maximum Deviation (meshfix_maximum_deviation) + // The maximum deviation allowed when reducing the resolution for the Maximum Resolution setting. If you increase this, + // the print will be less accurate, but the g-code will be smaller. Maximum Deviation is a limit for Maximum Resolution, + // so if the two conflict the Maximum Deviation will always be held true. + coord_t resolution { scaled(0.025) }; + // Minimum Feature Size (aka minimum line width) + // Minimum thickness of thin features. Model features that are thinner than this value will not be printed, while features thicker + // than the Minimum Feature Size will be widened to the Minimum Wall Line Width. + coord_t min_feature_size { scaled(0.1) }; + +/*********************************************************************/ +/* General support parameters: */ +/*********************************************************************/ + + // Support Overhang Angle + // The minimum angle of overhangs for which support is added. At a value of 0° all overhangs are supported, 90° will not provide any support. + AngleRadians support_angle { 50. * M_PI / 180. }; + // Support Line Width + // Width of a single support structure line. + coord_t support_line_width { scaled(0.4) }; + // Support Roof Line Width: Width of a single support roof line. + coord_t support_roof_line_width { scaled(0.4) }; + // Enable Support Floor (aka bottom interfaces) + // Generate a dense slab of material between the bottom of the support and the model. This will create a skin between the model and support. + bool support_bottom_enable { false }; + // Support Floor Thickness + // The thickness of the support floors. This controls the number of dense layers that are printed on top of places of a model on which support rests. + coord_t support_bottom_height { scaled(1.) }; + bool support_material_buildplate_only { false }; + // Support Distance Priority + // Whether the Support X/Y Distance overrides the Support Z Distance or vice versa. When X/Y overrides Z the X/Y distance can push away + // the support from the model, influencing the actual Z distance to the overhang. We can disable this by not applying the X/Y distance around overhangs. + bool support_xy_overrides_z { false }; + // Support X/Y Distance + // Distance of the support structure from the print in the X/Y directions. + // minimum: 0, maximum warning: 1.5 * machine_nozzle_tip_outer_diameter + coord_t support_xy_distance { scaled(0.7) }; + // Minimum Support X/Y Distance + // Distance of the support structure from the overhang in the X/Y directions. + // minimum_value: 0, minimum warning": support_xy_distance - support_line_width * 2, maximum warning: support_xy_distance + // Used if ! support_xy_overrides_z. + coord_t support_xy_distance_overhang { scaled(0.2) }; + // Support Top Distance + // Distance from the top of the support to the print. + coord_t support_top_distance { scaled(0.1) }; + // Support Bottom Distance + // Distance from the print to the bottom of the support. + coord_t support_bottom_distance { scaled(0.1) }; + //FIXME likely not needed, optimization for clipping of interface layers + // When checking where there's model above and below the support, take steps of the given height. Lower values will slice slower, while higher values + // may cause normal support to be printed in some places where there should have been support interface. + coord_t support_interface_skip_height { scaled(0.3) }; + // Support Infill Line Directions + // A list of integer line directions to use. Elements from the list are used sequentially as the layers progress and when the end + // of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained + // in square brackets. Default is an empty list which means use the default angle 0 degrees. + std::vector support_infill_angles {}; + // Enable Support Roof + // Generate a dense slab of material between the top of support and the model. This will create a skin between the model and support. + bool support_roof_enable { false }; + // Support Roof Thickness + // The thickness of the support roofs. This controls the amount of dense layers at the top of the support on which the model rests. + coord_t support_roof_height { scaled(1.) }; + // Minimum Support Roof Area + // Minimum area size for the roofs of the support. Polygons which have an area smaller than this value will be printed as normal support. + double minimum_roof_area { scaled(scaled(1.)) }; + // A list of integer line directions to use. Elements from the list are used sequentially as the layers progress + // and when the end of the list is reached, it starts at the beginning again. The list items are separated + // by commas and the whole list is contained in square brackets. Default is an empty list which means + // use the default angles (alternates between 45 and 135 degrees if interfaces are quite thick or 90 degrees). + std::vector support_roof_angles {}; + // Support Roof Pattern (aka top interface) + // The pattern with which the roofs of the support are printed. + SupportMaterialInterfacePattern support_roof_pattern { smipAuto }; + // Support Pattern + // The pattern of the support structures of the print. The different options available result in sturdy or easy to remove support. + SupportMaterialPattern support_pattern { smpRectilinear }; + // Support Line Distance + // Distance between the printed support structure lines. This setting is calculated by the support density. + coord_t support_line_spacing { scaled(2.66 - 0.4) }; + // Support Floor Horizontal Expansion + // Amount of offset applied to the floors of the support. + coord_t support_bottom_offset { scaled(0.) }; + // Support Wall Line Count + // The number of walls with which to surround support infill. Adding a wall can make support print more reliably + // and can support overhangs better, but increases print time and material used. + // tree: 1, zig-zag: 0, concentric: 1 + int support_wall_count { 1 }; + // Support Roof Line Distance + // Distance between the printed support roof lines. This setting is calculated by the Support Roof Density, but can be adjusted separately. + coord_t support_roof_line_distance { scaled(0.4) }; + // Minimum Support Area + // Minimum area size for support polygons. Polygons which have an area smaller than this value will not be generated. + coord_t minimum_support_area { scaled(0.) }; + // Minimum Support Floor Area + // Minimum area size for the floors of the support. Polygons which have an area smaller than this value will be printed as normal support. + coord_t minimum_bottom_area { scaled(1.0) }; + // Support Horizontal Expansion + // Amount of offset applied to all support polygons in each layer. Positive values can smooth out the support areas and result in more sturdy support. + coord_t support_offset { scaled(0.) }; + +/*********************************************************************/ +/* Parameters for the Cura tree supports implementation: */ +/*********************************************************************/ + + // Tree Support Maximum Branch Angle + // The maximum angle of the branches, when the branches have to avoid the model. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach. + // minimum: 0, minimum warning: 20, maximum: 89, maximum warning": 85 + AngleRadians support_tree_angle { 60. * M_PI / 180. }; + // Tree Support Branch Diameter Angle + // The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. + // A bit of an angle can increase stability of the tree support. + // minimum: 0, maximum: 89.9999, maximum warning: 15 + AngleRadians support_tree_branch_diameter_angle { 5. * M_PI / 180. }; + // Tree Support Branch Distance + // How far apart the branches need to be when they touch the model. Making this distance small will cause + // the tree support to touch the model at more points, causing better overhang but making support harder to remove. + coord_t support_tree_branch_distance { scaled(50.) }; + // Tree Support Branch Diameter + // The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this. + // minimum: 0.001, minimum warning: support_line_width * 2 + coord_t support_tree_branch_diameter { scaled(2.) }; + +/*********************************************************************/ +/* Parameters new to the Thomas Rahm's tree supports implementation: */ +/*********************************************************************/ + + // Tree Support Preferred Branch Angle + // The preferred angle of the branches, when they do not have to avoid the model. Use a lower angle to make them more vertical and more stable. Use a higher angle for branches to merge faster. + // minimum: 0, minimum warning: 10, maximum: support_tree_angle, maximum warning: support_tree_angle-1 + AngleRadians support_tree_angle_slow { 50. * M_PI / 180. }; + // Tree Support Diameter Increase To Model + // The most the diameter of a branch that has to connect to the model may increase by merging with branches that could reach the buildplate. + // Increasing this reduces print time, but increases the area of support that rests on model + // minimum: 0 + coord_t support_tree_max_diameter_increase_by_merges_when_support_to_model { scaled(1.0) }; + // Tree Support Minimum Height To Model + // How tall a branch has to be if it is placed on the model. Prevents small blobs of support. This setting is ignored when a branch is supporting a support roof. + // minimum: 0, maximum warning: 5 + coord_t support_tree_min_height_to_model { scaled(1.0) }; + // Tree Support Inital Layer Diameter + // Diameter every branch tries to achieve when reaching the buildplate. Improves bed adhesion. + // minimum: 0, maximum warning: 20 + coord_t support_tree_bp_diameter { scaled(7.5) }; + // Tree Support Branch Density + // Adjusts the density of the support structure used to generate the tips of the branches. A higher value results in better overhangs, + // but the supports are harder to remove. Use Support Roof for very high values or ensure support density is similarly high at the top. + // 5%-35% + double support_tree_top_rate { 15. }; + // Tree Support Tip Diameter + // The diameter of the top of the tip of the branches of tree support." + // minimum: min_wall_line_width, minimum warning: min_wall_line_width+0.05, maximum_value: support_tree_branch_diameter, value: support_line_width + coord_t support_tree_tip_diameter { scaled(0.4) }; + + // Support Interface Priority + // How support interface and support will interact when they overlap. Currently only implemented for support roof. + //enum support_interface_priority { support_lines_overwrite_interface_area }; }; inline coord_t round_up_divide(const coord_t dividend, const coord_t divisor) //!< Return dividend divided by divisor rounded to the nearest integer @@ -67,9 +202,10 @@ inline coord_t round_up_divide(const coord_t dividend, const coord_t divisor) // 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(const PrintObject &print_object, const BuildVolume &build_volume, + coord_t max_move, coord_t max_move_slow, size_t current_mesh_idx, double progress_multiplier, double progress_offset, const std::vector &additional_excluded_areas = {}); TreeModelVolumes(TreeModelVolumes&&) = default; TreeModelVolumes& operator=(TreeModelVolumes&&) = default; @@ -103,22 +239,20 @@ class TreeModelVolumes * \param min_xy_dist Is the minimum xy distance used. * \return Polygons object */ - - const Polygons& getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist = false); + const Polygons& getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist = false) const; /*! * \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed. * * The result is a 2D area that would cause nodes of given radius to * collide with the model or be inside a hole. - * A Hole is defined as an area, in which a branch with increase_until_radius radius would collide with the wall. + * A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall. * \param radius The radius of the node of interest * \param layer_idx The layer of interest * \param min_xy_dist Is the minimum xy distance used. * \return Polygons object */ - const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist = false); - + const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist = false) const; /*! * \brief Provides the areas that have to be avoided by the tree's branches @@ -137,14 +271,14 @@ class TreeModelVolumes * \param min_xy_dist is the minimum xy distance used. * \return Polygons object */ - const Polygons& getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model = false, bool min_xy_dist = false); + const Polygons& getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model = false, bool min_xy_dist = false) const; /*! * \brief Provides the area represents all areas on the model where the branch does completely fit on the given layer. * \param radius The radius of the node of interest * \param layer_idx The layer of interest * \return Polygons object */ - const Polygons& getPlaceableAreas(coord_t radius, LayerIndex layer_idx); + const Polygons& getPlaceableAreas(coord_t radius, LayerIndex layer_idx) const; /*! * \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. @@ -152,9 +286,9 @@ class TreeModelVolumes * \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); + const Polygons& getWallRestriction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const; /*! - * \brief Round \p radius upwards to either a multiple of radius_sample_resolution_ or a exponentially increasing value + * \brief Round \p radius upwards to either a multiple of m_radius_sample_resolution or a exponentially increasing value * * It also adds the difference between the minimum xy distance and the regular one. * @@ -172,16 +306,17 @@ class TreeModelVolumes */ 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; + using RadiusLayerPair = std::pair; + using RadiusLayerPolygonCache = std::unordered_map>; + friend std::optional> getArea(const TreeModelVolumes::RadiusLayerPolygonCache &cache, const TreeModelVolumes::RadiusLayerPair &key); /*! - * \brief Round \p radius upwards to either a multiple of radius_sample_resolution_ or a exponentially increasing value + * \brief Round \p radius upwards to either a multiple of m_radius_sample_resolution or a exponentially increasing value * * \param radius The radius of the node of interest */ @@ -193,7 +328,7 @@ class TreeModelVolumes * \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; +// Polygons extractOutlineFromMesh(const PrintObject &print_object, LayerIndex layer_idx) const; /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. @@ -219,7 +354,7 @@ class TreeModelVolumes * * 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. + * A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall. * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. */ void calculateCollisionHolefree(std::deque keys); @@ -229,7 +364,7 @@ class TreeModelVolumes * * 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. + * A Hole is defined as an area, in which a branch with m_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) @@ -237,7 +372,7 @@ class TreeModelVolumes 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; + static Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision); /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model. @@ -316,14 +451,6 @@ class TreeModelVolumes calculateWallRestrictions(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 TreeSupportMeshGroupSettings& me, const TreeSupportMeshGroupSettings& 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. @@ -331,141 +458,132 @@ class TreeModelVolumes * * \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; + LayerIndex getMaxCalculatedLayer(coord_t radius, const RadiusLayerPolygonCache& 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_; + coord_t m_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_; + coord_t m_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; + coord_t m_min_resolution; + + bool m_precalculated = false; /*! * \brief The index to access the outline corresponding with the currently processing mesh */ - size_t current_outline_idx; + size_t m_current_outline_idx; /*! * \brief The minimum required clearance between the model and the tree branches */ - coord_t current_min_xy_dist; + coord_t m_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; + coord_t m_current_min_xy_dist_delta; /*! * \brief Does at least one mesh allow support to rest on a model. */ - bool support_rests_on_model; + bool m_support_rests_on_model; /*! * \brief The progress of the precalculate function for communicating it to the progress bar. */ - coord_t precalculation_progress = 0; + coord_t m_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; + double m_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; + double m_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; + coord_t m_increase_until_radius; /*! * \brief Polygons representing the limits of the printable area of the * machine */ - Polygons machine_border_; + Polygons m_machine_border; /*! * \brief Storage for layer outlines and the corresponding settings of the meshes grouped by meshes with identical setting. */ - std::vector>> layer_outlines_; + std::vector>> m_layer_outlines; /*! * \brief Storage for areas that should be avoided, like support blocker or previous generated trees. */ - std::vector anti_overhang_; + std::vector m_anti_overhang; /*! * \brief Radii that can be ignored by ceilRadius as they will never be requested. */ - std::unordered_set ignorable_radii_; + std::unordered_set m_ignorable_radii; /*! * \brief Smallest radius a branch can have. This is the radius of a SupportElement with DTT=0. */ - coord_t radius_0; + coord_t m_radius_0; /*! * \brief Caches for the collision, avoidance and areas on the model where support can be placed safely * at given radius and layer indices. - * - * 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_; - std::unique_ptr critical_collision_cache_ = std::make_unique(); + RadiusLayerPolygonCache m_collision_cache; + std::unique_ptr m_critical_collision_cache { std::make_unique() }; - mutable std::unordered_map collision_cache_holefree_; - std::unique_ptr critical_collision_cache_holefree_ = std::make_unique(); + RadiusLayerPolygonCache m_collision_cache_holefree; + std::unique_ptr m_critical_collision_cache_holefree { std::make_unique() }; - mutable std::unordered_map avoidance_cache_; - std::unique_ptr critical_avoidance_cache_ = std::make_unique(); + RadiusLayerPolygonCache m_avoidance_cache; + std::unique_ptr m_critical_avoidance_cache { std::make_unique() }; - mutable std::unordered_map avoidance_cache_slow_; - std::unique_ptr critical_avoidance_cache_slow_ = std::make_unique(); + RadiusLayerPolygonCache m_avoidance_cache_slow; + std::unique_ptr m_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(); + RadiusLayerPolygonCache m_avoidance_cache_to_model; + std::unique_ptr m_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(); + RadiusLayerPolygonCache m_avoidance_cache_to_model_slow; + std::unique_ptr m_critical_avoidance_cache_to_model_slow { std::make_unique() }; + RadiusLayerPolygonCache m_placeable_areas_cache; + std::unique_ptr m_critical_placeable_areas_cache { std::make_unique() }; /*! - * \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. + * \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(); + RadiusLayerPolygonCache m_avoidance_cache_hole; + std::unique_ptr m_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(); + RadiusLayerPolygonCache m_avoidance_cache_hole_to_model; + std::unique_ptr m_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(); + RadiusLayerPolygonCache m_wall_restrictions_cache; + std::unique_ptr m_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(); + // A different cache for min_xy_dist as the maximal safe distance an influence area can be increased(guaranteed overlap of two walls in consecutive layer) + // is much smaller when min_xy_dist is used. This causes the area of the wall restriction to be thinner and as such just using the min_xy_dist wall + // restriction would be slower. + RadiusLayerPolygonCache m_wall_restrictions_cache_min; + std::unique_ptr m_critical_wall_restrictions_cache_min = std::make_unique(); - std::unique_ptr critical_progress = std::make_unique(); + std::unique_ptr m_critical_progress { std::make_unique() }; }; } -#endif //slic3r_TreeModelVolumes_hpp \ No newline at end of file +#endif //slic3r_TreeModelVolumes_hpp diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 1ed7d2810..fb36fbe14 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -7,7 +7,16 @@ // CuraEngine is released under the terms of the AGPLv3 or higher. #include "TreeSupport.hpp" +#include "ClipperUtils.hpp" +#include "Fill/Fill.hpp" +#include "Layer.hpp" +#include "Print.hpp" +#include "MultiPoint.hpp" +#include "Polygon.hpp" +#include "Polyline.hpp" +#include "SupportMaterial.hpp" +#include #include #include #include @@ -15,160 +24,192 @@ #include #include //todo Remove! ONLY FOR PUBLIC BETA!! +#include + #include namespace Slic3r { -TreeSupport::TreeSupport(const SliceDataStorage& storage) -{ - size_t largest_printed_mesh_idx = 0; +static constexpr const auto tiny_area_threshold = sqr(scaled(0.001)); - 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; - } +static [[nodiscard]] std::vector>> group_meshes(const Print &print, std::vector &print_object_ids) +{ + std::vector>> grouped_meshes; + + //FIXME this is ugly, it does not belong here. + for (size_t object_id = 0; object_id < print_object_ids.size(); ++ object_id) { + const PrintObject &print_object = *print.get_object(object_id); + const PrintObjectConfig &object_config = print_object.config(); + if (object_config.support_material_interface_layers >= 2) + TreeSupport::TreeSupportSettings::some_model_contains_thick_roof = true; + if (object_config.support_material_contact_distance < EPSILON) + // || min_feature_size < scaled(0.1) that is the minimum line width + TreeSupport::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. - for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) - { - SliceMeshStorage mesh = storage.meshes[mesh_idx]; + size_t largest_printed_mesh_idx = 0; - const bool non_supportable_mesh = mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh") || storage.meshes[mesh_idx].settings.get("support_mesh"); - if (storage.meshes[mesh_idx].settings.get("support_structure") != ESupportStructure::TREE || !storage.meshes[mesh_idx].settings.get("support_enable") || non_supportable_mesh) - { - continue; - } + // 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. + for (size_t object_id = 0; object_id < print_object_ids.size(); ++ object_id) { + const PrintObject &print_object = *print.get_object(object_id); + const PrintObjectConfig &object_config = print_object.config(); + // Support must be enabled and set to Tree style. + assert(object_config.support_material); + assert(object_config.support_material_style == smsTree); - bool added = false; - - TreeSupportSettings next_settings(mesh.settings); - - for (size_t idx = 0; idx < grouped_meshes.size(); idx++) - { - if (next_settings == grouped_meshes[idx].first) - { - added = true; - grouped_meshes[idx].second.emplace_back(mesh_idx); + bool found_existing_group = false; + TreeSupport::TreeSupportSettings next_settings(print_object); + //FIXME for now only a single object per group is enabled. +#if 0 + for (size_t idx = 0; idx < grouped_meshes.size(); ++ idx) + if (next_settings == grouped_meshes[idx].first) { + found_existing_group = true; + grouped_meshes[idx].second.emplace_back(object_id); // handle some settings that are only used for performance reasons. This ensures that a horrible set setting intended to improve performance can not reduce it drastically. grouped_meshes[idx].first.performance_interface_skip_layers = std::min(grouped_meshes[idx].first.performance_interface_skip_layers, next_settings.performance_interface_skip_layers); } - } - if (!added) - { - grouped_meshes.emplace_back(next_settings, std::vector{ mesh_idx }); - } +#endif + if (! found_existing_group) + grouped_meshes.emplace_back(next_settings, std::vector{ object_id }); // no need to do this per mesh group as adaptive layers and raft setting are not setable per mesh. - if (storage.meshes[largest_printed_mesh_idx].layers.back().printZ < mesh.layers.back().printZ) - { - largest_printed_mesh_idx = mesh_idx; - } - } - std::vector known_z(storage.meshes[largest_printed_mesh_idx].layers.size()); - - for (size_t z = 0; z < storage.meshes[largest_printed_mesh_idx].layers.size(); z++) - { - known_z[z] = storage.meshes[largest_printed_mesh_idx].layers[z].printZ; + if (print.get_object(largest_printed_mesh_idx)->layers().back()->print_z < print_object.layers().back()->print_z) + largest_printed_mesh_idx = object_id; } - for (size_t idx = 0; idx < grouped_meshes.size(); idx++) +#if 0 { - grouped_meshes[idx].first.setActualZ(known_z); + std::vector known_z(storage.meshes[largest_printed_mesh_idx].layers.size()); + for (size_t z = 0; z < storage.meshes[largest_printed_mesh_idx].layers.size(); z++) + known_z[z] = storage.meshes[largest_printed_mesh_idx].layers[z].printZ; + for (size_t idx = 0; idx < grouped_meshes.size(); ++ idx) + grouped_meshes[idx].first.setActualZ(known_z); } +#endif + + return grouped_meshes; } - // todo remove as only for debugging relevant -std::string getPolygonAsString(const Polygons& poly) +static [[nodiscard]] std::string getPolygonAsString(const Polygons& poly) { - std::string ret = ""; + std::string ret; for (auto path : poly) - { - for (Point p : path) - { + for (Point p : path) { if (ret != "") ret += ","; ret += "(" + std::to_string(p.x()) + "," + std::to_string(p.y()) + ")"; } - } return ret; } - +//todo Remove! Only relevant for public BETA! +static bool inline g_showed_critical_error = false; +static bool inline g_showed_performance_warning = false; void TreeSupport::showError(std::string message, bool critical) { // todo Remove! ONLY FOR PUBLIC BETA!! std::string bugtype = std::string(critical ? " This is a critical bug. It may cause missing or malformed branches.\n" : "This bug should only decrease performance.\n"); - bool show = (critical && !TreeSupport::showed_critical) || (!critical && !TreeSupport::showed_performance); - (critical ? TreeSupport::showed_critical : TreeSupport::showed_performance) = true; + bool show = (critical && !g_showed_critical_error) || (!critical && !g_showed_performance_warning); + (critical ? g_showed_critical_error : g_showed_performance_warning) = true; if (show) - { - MessageBox(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + message + "\n" + bugtype).c_str(), "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); - } + MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + message + "\n" + bugtype).c_str(), + "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); } - - -void TreeSupport::generateSupportAreas(SliceDataStorage& storage) +static bool layer_has_overhangs(const Layer &layer) { - if (grouped_meshes.empty()) - { - return; - } + for (const LayerRegion* layerm : layer.regions()) + if (layerm->slices.has(stBottom) || layerm->slices.has(stBottom)) + return true; + return false; +} - if (storage.support.cross_fill_provider == nullptr) - { - AreaSupport::precomputeCrossInfillTree(storage); +static [[nodiscard]] Polygons layer_overhangs(const Layer &layer) +{ + Polygons out; + for (const LayerRegion* layerm : layer.regions()) + for (const Surface& surface : layerm->slices.surfaces) + if (surface.is_bottom()) + polygons_append(out, surface.expolygon); + return out; +} + +/*! + * \brief Precalculates all avoidances, that could be required. + * + * \param storage[in] Background storage to access meshes. + * \param currently_processing_meshes[in] Indexes of all meshes that are processed in this iteration + */ +LayerIndex precalculate(const Print &print, const TreeSupport::TreeSupportSettings &config, const std::vector &object_ids, TreeModelVolumes &volumes) +{ + // calculate top most layer that is relevant for support + LayerIndex max_layer = 0; + for (size_t object_id : object_ids) { + const PrintObject &print_object = *print.get_object(object_id); + int max_support_layer_id = 0; + for (int layer_id = 1; layer_id < print_object.layer_count(); ++ layer_id) + if (layer_has_overhangs(*print_object.get_layer(layer_id))) + max_support_layer_id = layer_id; + max_layer = std::max(max_support_layer_id - int(config.z_distance_top_layers), 0); } + if (max_layer > 0) + // The actual precalculation happens in TreeModelVolumes. + volumes.precalculate(max_layer); + return max_layer; +} + +void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_volume, std::vector &print_object_ids) +{ + std::vector>> grouped_meshes = group_meshes(print, print_object_ids); + if (grouped_meshes.empty()) + return; size_t counter = 0; // Process every mesh group. These groups can not be processed parallel, as the processing in each group is parallelized, and nested parallelization is disables and slow. - for (std::pair> processing : grouped_meshes) + for (std::pair> &processing : grouped_meshes) { // process each combination of meshes - std::vector> move_bounds(storage.support.supportLayers.size()); // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in drawAreas - log("Processing support tree mesh group %lld of %lld containing %lld meshes.\n", counter + 1, grouped_meshes.size(), grouped_meshes[counter].second.size()); - std::vector exclude(storage.support.supportLayers.size()); + // this struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generateInitialAreas have knowledge of the existence of multiple meshes being processed. + //FIXME this is a copy + m_config = processing.first; + BOOST_LOG_TRIVIAL(info) << "Processing support tree mesh group " << counter + 1 << " of " << grouped_meshes.size() << " containing " << grouped_meshes[counter].second.size() << " meshes."; auto t_start = std::chrono::high_resolution_clock::now(); +#if 0 + std::vector exclude(num_support_layers); // get all already existing support areas and exclude them - tbb::parallel_for(tbb::blocked_range(0, storage.support.supportLayers.size()), + tbb::parallel_for(tbb::blocked_range(0, num_support_layers), [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { Polygons exlude_at_layer; - exlude_at_layer.add(storage.support.supportLayers[layer_idx].support_bottom); - exlude_at_layer.add(storage.support.supportLayers[layer_idx].support_roof); + append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_bottom); + append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_roof); for (auto part : storage.support.supportLayers[layer_idx].support_infill_parts) - { - exlude_at_layer.add(part.outline); - } - exclude[layer_idx] = exlude_at_layer.unionPolygons(); + append(exlude_at_layer, part.outline); + exclude[layer_idx] = union_(exlude_at_layer); } }); - 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 : 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); +#endif +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + m_progress_multiplier = 1.0 / double(m_grouped_meshes.size()); + m_progress_offset = counter == 0 ? 0 : TREE_PROGRESS_TOTAL * (double(counter) * m_progress_multiplier); +#endif // SLIC3R_TREESUPPORT_PROGRESS + m_volumes = TreeModelVolumes(*print.get_object(processing.second.front()), build_volume, m_config.maximum_move_distance, m_config.maximum_move_distance_slow, processing.second.front(), m_progress_multiplier, m_progress_offset, {} /* exclude */); // ### Precalculate avoidances, collision etc. - precalculate(storage, processing.second); + size_t num_support_layers = precalculate(print, processing.first, processing.second, m_volumes); auto t_precalc = std::chrono::high_resolution_clock::now(); + // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in drawAreas + std::vector> move_bounds(num_support_layers); + // ### Place tips of the support tree for (size_t mesh_idx : processing.second) - { - generateInitialAreas(storage.meshes[mesh_idx], move_bounds, storage); - } + generateInitialAreas(*print.get_object(mesh_idx), move_bounds); auto t_gen = std::chrono::high_resolution_clock::now(); // ### Propagate the influence areas downwards. @@ -180,7 +221,7 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) auto t_place = std::chrono::high_resolution_clock::now(); // ### draw these points as circles - drawAreas(move_bounds, storage); + drawAreas(*print.get_object(processing.second.front()), move_bounds); auto t_draw = std::chrono::high_resolution_clock::now(); auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); @@ -189,105 +230,84 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) auto dur_place = 0.001 * std::chrono::duration_cast(t_place - t_path).count(); auto dur_draw = 0.001 * std::chrono::duration_cast(t_draw - t_place).count(); auto dur_total = 0.001 * std::chrono::duration_cast(t_draw - t_start).count(); - log("Total time used creating Tree support for the currently grouped meshes: %.3lf ms. Different subtasks:\nCalculating Avoidance: %.3lf ms Creating inital influence areas: %.3lf ms Influence area creation: %.3lf ms Placement of Points in InfluenceAreas: %.3lf ms Drawing result as support %.3lf ms\n", dur_total, dur_pre_gen, dur_gen, dur_path, dur_place, dur_draw); - if(config.branch_radius==2121) - { - showError("Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)",false); - } + BOOST_LOG_TRIVIAL(info) << + "Total time used creating Tree support for the currently grouped meshes: " << dur_total << " ms. " + "Different subtasks:\nCalculating Avoidance: " << dur_pre_gen << " ms " + "Creating inital influence areas: " << dur_gen << " ms " + "Influence area creation: " << dur_path << "ms " + "Placement of Points in InfluenceAreas: " << dur_place << "ms " + "Drawing result as support " << dur_draw << " ms"; +// if (m_config.branch_radius==2121) +// BOOST_LOG_TRIVIAL(error) << "Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)"; - for (auto& layer : move_bounds) - { - for (auto elem : layer) - { + for (auto &layer : move_bounds) { + for (auto elem : layer) { delete elem->area; delete elem; } } - counter++; + ++ counter; } - storage.support.generated = true; +// storage.support.generated = true; } - -void TreeSupport::precalculate(const SliceDataStorage& storage, std::vector currently_processing_meshes) +static inline [[nodiscard]] bool contains(const Polygons &polygons, const Point &p) { - // calculate top most layer that is relevant for support - LayerIndex max_layer = 0; - for (size_t mesh_idx : currently_processing_meshes) - { - const SliceMeshStorage& mesh = storage.meshes[mesh_idx]; - const coord_t layer_height = mesh.settings.get("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. - if (mesh.overhang_areas.size() <= z_distance_top_layers) - { - continue; - } - for (LayerIndex layer_idx = (mesh.overhang_areas.size() - z_distance_top_layers) - 1; layer_idx != 0; layer_idx--) - { - // look for max relevant layer - const Polygons& overhang = mesh.overhang_areas[layer_idx + z_distance_top_layers]; - if (!overhang.empty()) - { - if (layer_idx > max_layer) // iterates over multiple meshes - { - max_layer = 1 + layer_idx; // plus one to avoid problems if something is of by one - } - break; - } - } - } - - // ### The actual precalculation happens in TreeModelVolumes. - volumes_.precalculate(max_layer); + for (const Polygon &polygon : polygons) + if (polygon.contains(p)) + return true; + return false; } - -std::vector TreeSupport::convertLinesToInternal(Polygons polylines, LayerIndex layer_idx) +enum class LineStatus { - const bool xy_overrides = config.support_xy_overrides_z; + INVALID, + TO_MODEL, + TO_MODEL_GRACIOUS, + TO_MODEL_GRACIOUS_SAFE, + TO_BP, + TO_BP_SAFE +}; - std::vector result; +using LineInformation = std::vector>; +using LineInformations = std::vector; + +/*! + * \brief Converts a Polygons object representing a line into the internal format. + * + * \param polylines[in] The Polyline that will be converted. + * \param layer_idx[in] The current layer. + * \return All lines of the \p polylines object, with information for each point regarding in which avoidance it is currently valid in. + */ +static [[nodiscard]] LineInformations convertLinesToInternal( + const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, + const Polylines &polylines, LayerIndex layer_idx) +{ + const bool xy_overrides_z = config.support_xy_overrides_z; + + LineInformations result; // Also checks if the position is valid, if it is NOT, it deletes that point - for (auto line : polylines) - { + for (const Polyline &line : polylines) { LineInformation res_line; - for (Point p : line) - { - if (!volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST_SAFE, false, !xy_overrides).inside(p, true)) - { + for (Point p : line) { + if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST_SAFE, false, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_BP_SAFE); - } - else if (!volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides).inside(p, true)) - { + else if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST, false, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_BP); - } - else if (config.support_rests_on_model && !volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST_SAFE, true, !xy_overrides).inside(p, true)) - { + else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST_SAFE, true, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS_SAFE); - } - else if (config.support_rests_on_model && !volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides).inside(p, true)) - { + else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST, true, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS); - } - else if (config.support_rests_on_model && !volumes_.getCollision(config.getRadius(0), layer_idx, !xy_overrides).inside(p, true)) - { + else if (config.support_rests_on_model && ! contains(volumes.getCollision(config.getRadius(0), layer_idx, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_MODEL); - } - else - { - if (!res_line.empty()) - { - result.emplace_back(res_line); - res_line.clear(); - } + else if (!res_line.empty()) { + result.emplace_back(res_line); + res_line.clear(); } } - if (!res_line.empty()) - { + if (!res_line.empty()) { result.emplace_back(res_line); res_line.clear(); } @@ -296,85 +316,81 @@ std::vector TreeSupport::convertLinesToInternal(Po return result; } -Polygons TreeSupport::convertInternalToLines(std::vector lines) +/*! + * \brief Converts lines in internal format into a Polygons object representing these lines. + * + * \param lines[in] The lines that will be converted. + * \return All lines of the \p lines object as a Polygons object. + */ +static [[nodiscard]] Polylines convertInternalToLines(LineInformations lines) { - Polygons result; - - for (LineInformation line : lines) - { - Polygon path; + Polylines result; + for (LineInformation line : lines) { + Polyline path; for (auto point_data : line) - { - path.add(point_data.first); - } - result.add(path); + path.points.emplace_back(point_data.first); + result.emplace_back(std::move(path)); } return result; } - -std::function)> TreeSupport::getEvaluatePointForNextLayerFunction(size_t current_layer) +/*! + * \brief Evaluates 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, point and its status. + * \return whether the point is valid. + */ +static [[nodiscard]] bool evaluatePointForNextLayerFunction( + const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, + size_t current_layer, std::pair &p) { - const bool xy_overrides = config.support_xy_overrides_z; - std::function)> evaluatePoint = [=](std::pair p) - { - if (!volumes_.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, false, !xy_overrides).inside(p.first, true)) - { - return true; - } - if (config.support_rests_on_model && (p.second != LineStatus::TO_BP && p.second != LineStatus::TO_BP_SAFE)) - { - if (p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE) - { - return !volumes_.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, true, !xy_overrides).inside(p.first, true); - } - else - { - return !volumes_.getCollision(config.getRadius(0), current_layer - 1, !xy_overrides).inside(p.first, true); - } - } - return false; - }; - return evaluatePoint; + using AvoidanceType = TreeSupport::AvoidanceType; + if (! contains(volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, false, !config.support_xy_overrides_z), p.first)) + return true; + if (config.support_rests_on_model && (p.second != LineStatus::TO_BP && p.second != LineStatus::TO_BP_SAFE)) + return ! contains( + p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? + volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, true, !config.support_xy_overrides_z) : + volumes.getCollision(config.getRadius(0), current_layer - 1, !config.support_xy_overrides_z), + p.first); + return false; } - -std::pair, std::vector> TreeSupport::splitLines(std::vector lines, std::function)> evaluatePoint) +/*! + * \brief Evaluates which points of some lines are not valid one layer below and which are. Assumes all points are valid on the current layer. Validity is evaluated using supplied lambda. + * + * \param lines[in] The lines that have to be evaluated. + * \param evaluatePoint[in] The function used to evaluate the points. + * \return A pair with which points are still valid in the first slot and which are not in the second slot. + */ +template +static [[nodiscard]] std::pair splitLines(LineInformations lines, EvaluatePointFn evaluatePoint) { // assumes all Points on the current line are valid - std::vector keep(1); - std::vector set_free(1); + LineInformations keep(1); + LineInformations set_free(1); enum STATE { keeping, freeing }; - for (std::vector> line : lines) - { + for (std::vector> line : lines) { STATE current = keeping; LineInformation resulting_line; - for (std::pair me : line) - { - if (evaluatePoint(me)) - { - if (keeping != current) - { - if (!resulting_line.empty()) - { + for (std::pair me : line) { + if (evaluatePoint(me)) { + if (keeping != current) { + if (!resulting_line.empty()) { set_free.emplace_back(resulting_line); resulting_line.clear(); } current = keeping; } resulting_line.emplace_back(me); - } - else - { - if (freeing != current) - { - if (!resulting_line.empty()) - { + } else { + if (freeing != current) { + if (!resulting_line.empty()) { keep.emplace_back(resulting_line); resulting_line.clear(); } @@ -383,92 +399,138 @@ std::pair, std::vector>>, std::vector>>>(keep, set_free); +} + +/*! + * A point within a polygon and the index of which segment in the polygon the point lies on. + */ + //from CURA +struct GivenDistPoint +{ + Point location; //!< Result location + int pos; //!< Index to the first point in the polygon of the line segment on which the result was found +}; + +//from CURA +static bool getNextPointWithDistance(Point from, double dist, Points &poly, int start_idx, int poly_start_idx, GivenDistPoint& result) +{ + Point prev_poly_point = poly[(start_idx + poly_start_idx) % poly.size()]; + + for (unsigned int prev_idx = start_idx; prev_idx < poly.size(); ++ prev_idx) + { + // last checked segment is between last point in poly and poly[0]... + int next_idx = (prev_idx + 1 + poly_start_idx) % poly.size(); + const Point &next_poly_point = poly[next_idx]; + if ((next_poly_point - from).cast().norm() >= dist) { + /* + * x r + * p.---------+---+------------.n + * L| / + * | / dist + * |/ + * f. + * + * f=from + * p=prev_poly_point + * n=next_poly_point + * x= f projected on pn + * r=result point at distance [dist] from f + */ + + Point pn = next_poly_point - prev_poly_point; + + static constexpr const double eps = scaled(0.1); + if (pn.cast().squaredNorm() < sqr(eps)) { // when precision is limited + Point middle = (next_poly_point + prev_poly_point) / 2; + double dist_to_middle = (from - middle).cast().norm(); + if (dist_to_middle - dist < eps && dist_to_middle - dist > -eps) { + result.location = middle; + result.pos = prev_idx; + return true; + } else { + prev_poly_point = next_poly_point; + continue; + } } + + Point pf = from - prev_poly_point; + Point px = (pn.cast() * (pf.cast().dot(pn.cast()) / pn.cast().squaredNorm())).cast(); + Point xf = pf - px; + + if (xf.cast().norm() >= dist) { // line lies wholly further than pn + prev_poly_point = next_poly_point; + continue; + } + + double xr_dist = std::sqrt(dist * dist - xf.cast().squaredNorm()); // inverse Pythagoras + + if ((pn - px).cast().norm() - xr_dist < scaled(1.)) { // r lies beyond n + prev_poly_point = next_poly_point; + continue; + } + + Point xr = (pn * (xr_dist / pn.cast().norm())).cast(); + Point pr = px + xr; + + result.location = prev_poly_point + pr; + result.pos = prev_idx; + return true; } + prev_poly_point = next_poly_point; } - return std::pair>>, std::vector>>>(keep, set_free); + return false; } - -void writePolylines(SVG& svg, Polygons polylines, SVG::Color color) // todo remove as only for debugging relevant +/*! + * \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. + */ +static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &input, double distance, size_t min_points) { - for (auto path : polylines) - { - if (path.size() == 0) - { + Polylines result; + for (Polyline part : input) { + if (part.empty() == 0) continue; - } - if (path.size() == 1) - { - svg.writePoint(path[0], false, 2, color); - } - Point before = path[0]; - for (size_t i = 1; i < path.size(); i++) - { - svg.writeLine(before, path[i], color, 2); - before = path[i]; - } - } -} -void writePoints(SVG& svg, Polygons polylines, SVG::Color color) // todo remove as only for debugging relevant -{ - for (auto path : polylines) - { - for (Point p : path) + double len = length(part.points); + Polyline line; + double current_distance = std::max(distance, scaled(0.1)); + if (len < 2 * distance && min_points <= 1) { - svg.writePoint(p, false, 2, color); - } - } -} - -Polygons TreeSupport::ensureMaximumDistancePolyline(const Polygons& input, coord_t distance, size_t min_points) const -{ - Polygons result; - for (auto part : input) - { - if (part.size() == 0) - { - continue; - } - coord_t length = Polygon(part).offset(0).polyLineLength(); - Polygon line; - coord_t current_distance = std::max(distance, coord_t(100)); - if (length < 2 * distance && min_points <= 1) - { - ClosestPolygonPoint middle_point(part[0], 0, part); - middle_point = PolygonUtils::walk(middle_point, coord_t(length / 2)); - line.add(middle_point.location); + // Insert the opposite point of the first one. + //FIXME pretty expensive + Polyline pl(line.points); + pl.clip_end(len / 2); + line.points.emplace_back(pl.points.back()); } else { size_t optimal_end_index = part.size() - 1; - if (part.front() == part.back()) - { + if (part.front() == part.back()) { size_t optimal_start_index = 0; // If the polyline was a polygon, there is a high chance it was an overhang. Overhangs that are <60� tend to be very thin areas, so lets get the beginning and end of them and ensure that they are supported. // The first point of the line will always be supported, so rotate the order of points in this polyline that one of the two corresponding points that are furthest from each other is in the beginning. // The other will be manually added (optimal_end_index) coord_t max_dist2_between_vertecies = 0; - for (size_t idx = 0; idx < part.size() - 1; idx++) - { - for (size_t inner_idx = 0; inner_idx < part.size() - 1; inner_idx++) - { - if (vSize2(part[idx] - part[inner_idx]) > max_dist2_between_vertecies) - { + for (size_t idx = 0; idx < part.size() - 1; ++ idx) { + for (size_t inner_idx = 0; inner_idx < part.size() - 1; inner_idx++) { + if ((part[idx] - part[inner_idx]).cast().squaredNorm() > max_dist2_between_vertecies) { optimal_start_index = idx; optimal_end_index = inner_idx; - max_dist2_between_vertecies = vSize2(part[idx] - part[inner_idx]); + max_dist2_between_vertecies = (part[idx] - part[inner_idx]).cast().squaredNorm(); } } } @@ -477,55 +539,46 @@ Polygons TreeSupport::ensureMaximumDistancePolyline(const Polygons& input, coord optimal_end_index = (optimal_end_index - optimal_start_index + part.size() - 1) % (part.size() - 1); } - - while (line.size() < min_points && current_distance >= coord_t(100)) + while (line.size() < min_points && current_distance >= scaled(0.1)) { line.clear(); Point current_point = part[0]; - line.add(part[0]); - if (min_points > 1 || vSize(part[0] - part[optimal_end_index]) > current_distance) - { - line.add(part[optimal_end_index]); - } + line.points.emplace_back(part[0]); + if (min_points > 1 || (part[0] - part[optimal_end_index]).cast().norm() > current_distance) + line.points.emplace_back(part[optimal_end_index]); size_t current_index = 0; GivenDistPoint next_point; coord_t next_distance = current_distance; // Get points so that at least min_points are added and they each are current_distance away from each other. If that is impossible, decrease current_distance a bit. - while (PolygonUtils::getNextPointWithDistance(current_point, next_distance, part, current_index, 0, next_point) && next_point.pos < coord_t(part.size())) // The input are lines, that means that the line from the last to the first vertex does not have to exist, so exclude all points that are on this line! + // The input are lines, that means that the line from the last to the first vertex does not have to exist, so exclude all points that are on this line! + while (getNextPointWithDistance(current_point, next_distance, part.points, current_index, 0, next_point) && next_point.pos < coord_t(part.size())) { // Not every point that is distance away, is valid, as it may be much closer to another point. This is especially the case when the overhang is very thin. // So this ensures that the points are actually a certain distance from each other. // This assurance is only made on a per polygon basis, as different but close polygon may not be able to use support below the other polygon. - coord_t min_distance_to_existing_point = std::numeric_limits::max(); + double min_distance_to_existing_point = std::numeric_limits::max(); for (Point p : line) - { - min_distance_to_existing_point = std::min(min_distance_to_existing_point, vSize(p - next_point.location)); - } - if (min_distance_to_existing_point >= current_distance) - { + min_distance_to_existing_point = std::min(min_distance_to_existing_point, (p - next_point.location).cast().norm()); + if (min_distance_to_existing_point >= current_distance) { // viable point was found. Add to possible result. - line.add(next_point.location); + line.points.emplace_back(next_point.location); current_point = next_point.location; current_index = next_point.pos; next_distance = current_distance; - } - else - { - if (current_point == next_point.location) - { + } else { + if (current_point == next_point.location) { // In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here... - logWarning("Tree Support: Encountered a fixpoint in getNextPointWithDistance. This is expected to happen if the distance (currently %lld) is smaller than 100\n", next_distance); + BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in getNextPointWithDistance. This is expected to happen if the distance (currently " << next_distance << + ") is smaller than 100"; TreeSupport::showError("Encountered issue while placing tips. Some tips may be missing.", true); if (next_distance > 2 * current_distance) - { // This case should never happen, but better safe than sorry. break; - } next_distance += current_distance; continue; } // if the point was too close, the next possible viable point is at least distance-min_distance_to_existing_point away from the one that was just checked. - next_distance = std::max(current_distance - min_distance_to_existing_point, coord_t(100)); + next_distance = std::max(current_distance - min_distance_to_existing_point, scaled(0.1)); current_point = next_point.location; current_index = next_point.pos; } @@ -533,31 +586,26 @@ Polygons TreeSupport::ensureMaximumDistancePolyline(const Polygons& input, coord current_distance *= 0.9; } } - result.add(line); + result.emplace_back(std::move(line)); } return result; } -// adds the implicit line from the last vertex to the first explicitly -Polygons TreeSupport::toPolylines(const Polygons& poly) const -{ - Polygons result; - for (auto path : poly) - { - Polygon part; - for (size_t i = 0; i < path.size(); i++) - { - part.add(path[i]); - } - part.add(path[0]); - result.add(part); - } - return result; -} - - -Polygons TreeSupport::generateSupportInfillLines(const Polygons& area, bool roof, LayerIndex layer_idx, coord_t support_infill_distance, SierpinskiFillProvider* cross_fill_provider, bool include_walls) +/*! + * \brief Returns Polylines representing the (infill) lines that will result in slicing the given area + * + * \param area[in] The area that has to be filled with infill. + * \param roof[in] Whether the roofing or regular support settings should be used. + * \param layer_idx[in] The current layer index. + * \param support_infill_distance[in] The distance that should be between the infill lines. + * + * \return A Polygons object that represents the resulting infill lines. + */ +static [[nodiscard]] Polylines generateSupportInfillLines( + const Polygons &area, const SupportParameters &support_params, + bool roof, LayerIndex layer_idx, coord_t support_infill_distance) { +#if 0 Polygons gaps; // as we effectivly use lines to place our supportPoints we may use the Infill class for it, while not made for it it works perfect @@ -583,15 +631,45 @@ Polygons TreeSupport::generateSupportInfillLines(const Polygons& area, bool roof int divisor = static_cast(angles.size()); int index = ((layer_idx % divisor) + divisor) % divisor; const AngleRadians fill_angle = angles[index]; - Infill roof_computation(pattern, true /* 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, false /* skip_some_zags */, zag_skip_count, pocket_size); + Infill roof_computation(pattern, true /* 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.resolution, wall_line_count, infill_origin, + perimeter_gaps, connected_zigzags, use_endpieces, false /* skip_some_zags */, zag_skip_count, pocket_size); Polygons areas; Polygons lines; - roof_computation.generate(toolpaths, areas, lines, config.settings, cross_fill_provider); - lines.add(toPolylines(areas)); + roof_computation.generate(toolpaths, areas, lines, config.settings); + append(lines, to_polylines(areas)); return lines; +#else + const Flow &flow = roof ? support_params.support_material_interface_flow : support_params.support_material_flow; + std::unique_ptr filler = std::unique_ptr(Fill::new_from_type(roof ? support_params.interface_fill_pattern : support_params.base_fill_pattern)); + FillParams fill_params; + + filler->layer_id = layer_idx; + filler->spacing = flow.spacing(); + fill_params.density = float(roof ? support_params.interface_density : float(filler->spacing) / float(support_infill_distance)); + fill_params.dont_adjust = true; + + Polylines out; + for (ExPolygon &expoly : union_ex(area)) { + // The surface type does not matter. + Surface surface(stInternal, std::move(expoly)); + try { + append(out, filler->fill_surface(&surface, fill_params)); + } catch (InfillFailedException &) { + } + } + return out; +#endif } -Polygons TreeSupport::safeUnion(const Polygons first, const Polygons second) const +/*! + * \brief Unions two Polygons. Ensures that if the input is non empty that the output also will be non empty. + * \param first[in] The first Polygon. + * \param second[in] The second Polygon. + * \return The union of both Polygons + */ +static [[nodiscard]] Polygons safeUnion(const Polygons first, const Polygons second = Polygons()) { // unionPolygons can slowly remove Polygons under certain circumstances, because of rounding issues (Polygons that have a thin area). // This does not cause a problem when actually using it on large areas, but as influence areas (representing centerpoints) can be very thin, this does occur so this ugly workaround is needed @@ -607,151 +685,158 @@ Polygons TreeSupport::safeUnion(const Polygons first, const Polygons second) con exampleInner.add(Point(120419,83580));//F example.add(exampleInner); for(int i=0;i<10;i++){ - log("Iteration %d Example area: %f\n",i,example.area()); + log("Iteration %d Example area: %f\n",i,area(example)); example=example.unionPolygons(); } */ - - bool was_empty = first.empty() && second.empty(); - - Polygons result = first.unionPolygons(second); - - if (result.empty() && !was_empty) // error occurred - { - logDebug("Caught an area destroying union, enlarging areas a bit.\n"); - return toPolylines(first).offsetPolyLine(2).unionPolygons(toPolylines(second).offsetPolyLine(2)); // just take the few lines we have, and offset them a tiny bit. Needs to be offsetPolylines, as offset may aleady have problems with the area. - } - - else - { - return result; + Polygons result; + if (! first.empty() || ! second.empty()) { + result = union_(first, second); + if (result.empty()) { + BOOST_LOG_TRIVIAL(debug) << "Caught an area destroying union, enlarging areas a bit."; + // just take the few lines we have, and offset them a tiny bit. Needs to be offsetPolylines, as offset may aleady have problems with the area. + result = union_(offset(to_polylines(first), scaled(0.002)), offset(to_polylines(second), scaled(0.002))); + } } + + return result; } -SierpinskiFillProvider* TreeSupport::generateCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width) +/*! + * \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_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. + */ +static [[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 EFillMethod& support_pattern = mesh.settings.get("support_pattern"); - if (support_pattern == EFillMethod::CROSS || support_pattern == EFillMethod::CROSS_3D) - { - AABB3D aabb; - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) - { - logWarning("Tree support tried to generate a CrossFillProvider for a non model mesh.\n"); - TreeSupport::showError("Tried to generate a CrossFillProvider for a non model mesh..\n", true); - return nullptr; - } - - const coord_t aabb_expansion = mesh.settings.get("support_offset"); - AABB3D aabb_here(mesh.bounding_box); - aabb_here.include(aabb_here.min - Point3(-aabb_expansion, -aabb_expansion, 0)); - aabb_here.include(aabb_here.max + Point3(-aabb_expansion, -aabb_expansion, 0)); - aabb.include(aabb_here); - - std::string cross_subdisivion_spec_image_file = mesh.settings.get("cross_support_density_image"); - std::ifstream cross_fs(cross_subdisivion_spec_image_file.c_str()); - if (cross_subdisivion_spec_image_file != "" && cross_fs.good()) - { - return new SierpinskiFillProvider(aabb, line_distance, line_width, cross_subdisivion_spec_image_file); - } - else - { - return new SierpinskiFillProvider(aabb, line_distance, line_width); - } + bool do_final_difference = last_step_offset_without_check == 0; + Polygons ret = safeUnion(me); // ensure sane input + if (distance == 0) + return do_final_difference ? diff(ret, collision) : union_(ret); + if (safe_step_size < 0 || last_step_offset_without_check < 0) { + BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!"; + TreeSupport::showError("Negative offset distance... How did you manage this ?", true); + return do_final_difference ? diff(ret, collision) : union_(ret); } - return nullptr; + + coord_t step_size = safe_step_size; + 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) { + if ((steps + 1) * step_size <= distance) + // This will be the case when last_step_offset_without_check >= safe_step_size + ++ steps; + 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++) { + ret = diff(offset(ret, step_size, ClipperLib::jtRound), collision); + // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. + if (i % 10 == 7) + ret = polygons_simplify(ret, scaled(0.015)); + } + // offset the remainder + ret = polygons_simplify(offset(ret, distance - steps * step_size, ClipperLib::jtRound), scaled(0.015)); + + if (do_final_difference) + ret = diff(ret, collision); + return union_(ret); } - -void TreeSupport::generateInitialAreas(const SliceMeshStorage& mesh, std::vector>& move_bounds, SliceDataStorage& storage) +void TreeSupport::generateInitialAreas(const PrintObject &print_object, std::vector> &move_bounds) { Polygon base_circle; const int base_radius = 10; - for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; i++) - { + for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; ++ i) { const AngleRadians angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); - base_circle.emplace_back(coord_t(cos(angle) * base_radius), coord_t(sin(angle) * base_radius)); + base_circle.points.emplace_back(coord_t(cos(angle) * base_radius), coord_t(sin(angle) * base_radius)); } - TreeSupportSettings mesh_config(mesh.settings); + TreeSupportSettings mesh_config(print_object); + TreeSupportMeshGroupSettings mesh_group_settings(print_object); + SupportParameters support_params(print_object); const size_t z_distance_delta = mesh_config.z_distance_top_layers + 1; // To ensure z_distance_top_layers are left empty between the overhang (zeroth empty layer), the support has to be added z_distance_top_layers+1 layers below - const bool xy_overrides = mesh_config.support_xy_overrides_z; - const coord_t support_roof_line_distance = mesh.settings.get("support_roof_line_distance"); - const double minimum_roof_area = mesh.settings.get("minimum_roof_area"); - const double minimum_support_area = mesh.settings.get("minimum_support_area"); - const size_t support_roof_layers = mesh.settings.get("support_roof_enable") ? (mesh.settings.get("support_roof_height") + mesh_config.layer_height / 2) / mesh_config.layer_height : 0; - const bool roof_enabled = support_roof_layers != 0; - const bool only_gracious = SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL; - 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 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. - const bool force_tip_to_roof = (mesh_config.min_radius * mesh_config.min_radius * M_PI > minimum_roof_area * (1000 * 1000)) && roof_enabled; - const double support_overhang_angle = mesh.settings.get("support_angle"); - const coord_t max_overhang_speed = (support_overhang_angle < (2.0 * M_PI) / 4) ? (coord_t)(tan(support_overhang_angle) * mesh_config.layer_height) : std::numeric_limits::max(); - const size_t max_overhang_insert_lag = std::max((size_t)round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers); // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. The 2*z_distance_delta is only a catch for when the support angle is very high. - SierpinskiFillProvider* cross_fill_provider = generateCrossFillProvider(mesh, support_tree_branch_distance, mesh_config.support_line_width); + const bool xy_overrides_z = mesh_config.support_xy_overrides_z; +#if 0 if (mesh.overhang_areas.size() <= z_distance_delta) - { return; - } - std::vector> already_inserted(mesh.overhang_areas.size() - z_distance_delta); +#endif + const coord_t connect_length = (mesh_config.support_line_width * 100. / mesh_group_settings.support_tree_top_rate) + std::max(2 * mesh_config.min_radius - 1.0 * mesh_config.support_line_width, 0.0); + const coord_t circle_length_to_half_linewidth_change = mesh_config.min_radius < mesh_config.support_line_width ? mesh_config.min_radius / 2 : sqrt(sqr(mesh_config.min_radius) - sqr(mesh_config.min_radius - mesh_config.support_line_width / 2)); // 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 extra_outset = std::max(coord_t(0), mesh_config.min_radius - mesh_config.support_line_width) + (xy_overrides_z ? 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. + const size_t support_roof_layers = mesh_group_settings.support_roof_enable ? (mesh_group_settings.support_roof_height + mesh_config.layer_height / 2) / mesh_config.layer_height : 0; + const bool roof_enabled = support_roof_layers != 0; + const bool force_tip_to_roof = (mesh_config.min_radius * mesh_config.min_radius * M_PI > mesh_group_settings.minimum_roof_area) && roof_enabled; + const coord_t max_overhang_speed = (mesh_group_settings.support_angle < 0.5 * M_PI) ? (coord_t)(tan(mesh_group_settings.support_angle) * mesh_config.layer_height) : std::numeric_limits::max(); + const size_t max_overhang_insert_lag = std::max((size_t)round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers); // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. The 2*z_distance_delta is only a catch for when the support angle is very high. + + //FIXME + size_t num_support_layers = print_object.layer_count(); + std::vector> already_inserted(num_support_layers - z_distance_delta); std::mutex critical_sections; - tbb::parallel_for(tbb::blocked_range(1, mesh.overhang_areas.size() - z_distance_delta), - [&](const tbb::blocked_range &range) { + tbb::parallel_for(tbb::blocked_range(1, num_support_layers - z_distance_delta), + [this, &print_object, &mesh_config, &mesh_group_settings, &support_params, z_distance_delta, xy_overrides_z, force_tip_to_roof, roof_enabled, support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, + &base_circle, &critical_sections, &already_inserted, + &move_bounds, &base_radius](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + if (! layer_has_overhangs(*print_object.get_layer(layer_idx + z_distance_delta))) + continue; - if (mesh.overhang_areas[layer_idx + z_distance_delta].empty()) - { - return; // This is a continue if imagined in a loop context - } + Polygons relevant_forbidden = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides_z)); // take the least restrictive avoidance possible + // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. + relevant_forbidden = offset(union_ex(relevant_forbidden), scaled(0.005)); - 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) - { - 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); + auto generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) -> Polylines { + const coord_t support_infill_distance = roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance; + return generateSupportInfillLines(area, support_params, roof, layer_idx, support_infill_distance); }; - 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) + auto addPointAsInfluenceArea = [&](std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) { bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - if (!mesh_config.support_rests_on_model && !to_bp) - { - logWarning("Tried to add an invalid support point\n"); + if (!mesh_config.support_rests_on_model && !to_bp) { + BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; TreeSupport::showError("Unable to add tip. Some overhang may not be supported correctly.", true); return; } Polygon circle; for (Point corner : base_circle) - { - circle.add(p.first + corner); - } - Polygons area = circle.offset(0); + circle.points.emplace_back(p.first + corner); { std::lock_guard critical_section_movebounds(critical_sections); - if (!already_inserted[insert_layer].count(p.first / ((mesh_config.min_radius + 1) / 10))) - { + 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, skip_ovalisation); - elem->area = new Polygons(area); + SupportElement* elem = new SupportElement(dtt, insert_layer, p.first, to_bp, gracious, !xy_overrides_z, dont_move_until, roof, safe_radius, force_tip_to_roof, skip_ovalisation); + elem->area = new Polygons(); + elem->area->emplace_back(std::move(circle)); move_bounds[insert_layer].emplace(elem); } } }; - - 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) + auto addLinesAsInfluenceAreas = [&](LineInformations 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_tip; @@ -759,146 +844,137 @@ void TreeSupport::generateInitialAreas(const SliceMeshStorage& mesh, std::vector { std::function)> evaluateRoofWillGenerate = [&](std::pair p) { + //FIXME Vojtech: The circle is just shifted, it has a known size, the infill should fit all the time! +#if 0 Polygon roof_circle; for (Point corner : base_circle) - { - roof_circle.add(p.first + corner * mesh_config.min_radius); - } - Polygons area = roof_circle.offset(0); - return !generateLines(area, true, insert_layer_idx - dtt_roof_tip).empty(); + roof_circle.points.emplace_back(p.first + corner * mesh_config.min_radius); + return !generateSupportInfillLines({ roof_circle }, mesh_config, true, insert_layer_idx - dtt_roof_tip, mesh_config.support_roof_line_distance).empty(); +#else + return true; +#endif }; - 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 + std::pair split = + // keep all lines that are still valid on the next layer + splitLines(lines, [this, insert_layer_idx, dtt_roof_tip](std::pair &p){ return evaluatePointForNextLayerFunction(m_volumes, m_config, insert_layer_idx - dtt_roof_tip, p); }); for (LineInformation line : split.second) // add all points that would not be valid - { - for (std::pair point_data : line) - { + for (std::pair point_data : line) addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); - } - } // not all roofs are guaranteed to actually generate lines, so filter these out and add them as points split = splitLines(split.first, evaluateRoofWillGenerate); lines = split.first; for (LineInformation line : split.second) - { - for (std::pair point_data : line) - { + for (std::pair point_data : line) addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); - } - } // add all tips as roof to the roof storage Polygons added_roofs; for (LineInformation line : lines) - { - for (std::pair p : line) - { + for (std::pair p : line) { Polygon roof_circle; for (Point corner : base_circle) - { - roof_circle.add(p.first + corner * mesh_config.min_radius / base_radius); - } - added_roofs.add(roof_circle); + roof_circle.points.emplace_back(p.first + corner * mesh_config.min_radius / base_radius); + added_roofs.emplace_back(roof_circle); } - } - added_roofs = added_roofs.unionPolygons(); + added_roofs = union_(added_roofs); { std::lock_guard critical_section_storage(critical_sections); - - storage.support.supportLayers[insert_layer_idx - dtt_roof_tip].support_roof.add(added_roofs); +//FIXME store top support interface areas or maybe hatch them? +// append(storage.support.supportLayers[insert_layer_idx - dtt_roof_tip].support_roof, added_roofs); } } - for (LineInformation line : lines) - { + for (LineInformation line : lines) { bool disable_ovalistation = mesh_config.min_radius < 3 * mesh_config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > 5; // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width for (auto point_data : line) - { addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, dtt_roof_tip != 0 || supports_roof, disable_ovalistation); - } } }; - 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 * 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 + // 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) + std::vector> overhang_processing; + Polygons overhang_raw = layer_overhangs(*print_object.get_layer(layer_idx + z_distance_delta)); + Polygons overhang_regular = safeOffsetInc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, mesh_config.min_radius * 1.75 + mesh_config.xy_min_distance, 0, 1); + // offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang + Polygons remaining_overhang = intersection(diff(offset(union_ex(overhang_raw), mesh_group_settings.support_offset), offset(union_ex(overhang_regular), mesh_config.support_line_width * 0.5)), relevant_forbidden); coord_t extra_total_offset_acc = 0; // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. while (extra_total_offset_acc + mesh_config.support_line_width / 8 < extra_outset) //+mesh_config.support_line_width / 80 to avoid calculating very small (useless) offsets because of rounding errors. { - 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); + 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, 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, 0, 1); - overhang_regular = overhang_regular.unionPolygons(next_overhang.difference(relevant_forbidden)); + Polygons overhang_offset = safeOffsetInc(overhang_regular, 1.5 * extra_total_offset_acc, m_volumes.getCollision(0, layer_idx, true), mesh_config.xy_min_distance + mesh_config.support_line_width, 0, 1); + remaining_overhang = diff(remaining_overhang, overhang_offset); + Polygons next_overhang = safeOffsetInc(remaining_overhang, extra_total_offset_acc, m_volumes.getCollision(0, layer_idx, true), mesh_config.xy_min_distance + mesh_config.support_line_width, 0, 1); + overhang_regular = union_(overhang_regular, diff(next_overhang, relevant_forbidden)); } // If the xy distance overrides the z distance, some support needs to be inserted further down. //=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the the setting description, it would not produce reasonable results. ) - if (xy_overrides) + if (xy_overrides_z) { - std::vector overhang_lines; - Polygons polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang, false, layer_idx), mesh_config.min_radius, 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 consistent behavior as some infill patterns generate each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that the area that is valid a layer below is to small for support roof. - if (polylines.pointCount() <= 3) + LineInformations overhang_lines; { - // add the outer wall to ensure it is correct supported instead - polylines = ensureMaximumDistancePolyline(toPolylines(remaining_overhang), connect_length, 3); - } - - for (auto line : polylines) - { - LineInformation res_line; - for (Point p : line) - { - res_line.emplace_back(p, LineStatus::INVALID); + //Vojtech: Generate support heads at support_tree_branch_distance spacing by producing a zig-zag infill at support_tree_branch_distance spacing, + // which is then resmapled + // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, + // mbut not only is this the only reasonable choice, but it ensures consistent behavior as some infill patterns generate + // each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that + // the area that is valid a layer below is to small for support roof. + Polylines polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang, false, layer_idx), mesh_config.min_radius, 1); + if (polylines.size() <= 3) + // add the outer wall to ensure it is correct supported instead + polylines = ensureMaximumDistancePolyline(to_polylines(remaining_overhang), connect_length, 3); + for (const auto &line : polylines) { + LineInformation res_line; + for (Point p : line) + res_line.emplace_back(p, LineStatus::INVALID); + overhang_lines.emplace_back(res_line); } - overhang_lines.emplace_back(res_line); } - for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) - { + for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) { // get least restricted avoidance for layer_idx-lag_ctr - Polygons relevant_forbidden_below = (mesh_config.support_rests_on_model ? (only_gracious ? volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, true, !xy_overrides) : volumes_.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, !xy_overrides)) : volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, false, !xy_overrides)); + Polygons relevant_forbidden_below = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, false, !xy_overrides_z)); // it is not required to offset the forbidden area here as the points wont change: If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. - std::function)> evaluatePoint = [&](std::pair p) { return relevant_forbidden_below.inside(p.first, true); }; + std::function)> evaluatePoint = [&](std::pair p) { return contains(relevant_forbidden_below, p.first); }; - std::pair, std::vector> split = splitLines(overhang_lines, evaluatePoint); // keep all lines that are invalid + std::pair split = splitLines(overhang_lines, evaluatePoint); // keep all lines that are invalid 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. + LineInformations fresh_valid_points = convertLinesToInternal(m_volumes, m_config, convertInternalToLines(split.second), layer_idx - lag_ctr); // set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. 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, 0, 1); - overhang_roofs.removeSmallAreas(minimum_roof_area); - overhang_regular = overhang_regular.difference(overhang_roofs); - for (Polygons roof_part : overhang_roofs.splitIntoParts(true)) - { - overhang_processing.emplace_back(roof_part, true); - } + if (roof_enabled) { + static constexpr const coord_t support_roof_offset = 0; + overhang_roofs = safeOffsetInc(layer_overhangs(*print_object.get_layer(layer_idx + z_distance_delta)), support_roof_offset, relevant_forbidden, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, 0, 1); + remove_small(overhang_roofs, mesh_group_settings.minimum_roof_area); + overhang_regular = diff(overhang_regular, overhang_roofs); + for (ExPolygon &roof_part : union_ex(overhang_roofs)) + overhang_processing.emplace_back(std::move(roof_part), true); } - overhang_regular.removeSmallAreas(minimum_support_area); + remove_small(overhang_regular, mesh_group_settings.minimum_support_area); - for (Polygons support_part : overhang_regular.splitIntoParts(true)) - { - overhang_processing.emplace_back(support_part, false); - } + for (ExPolygon &support_part : union_ex(overhang_regular)) + overhang_processing.emplace_back(std::move(support_part), false); - for (std::pair overhang_pair : overhang_processing) + for (const std::pair &overhang_pair : overhang_processing) { const bool roof_allowed_for_this_part = overhang_pair.second; - Polygons overhang_outset = overhang_pair.first; - const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), overhang_outset.polygonLength() / connect_length)); - std::vector overhang_lines; + Polygons overhang_outset = to_polygons(overhang_pair.first); + const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), coord_t(total_length(overhang_outset) / connect_length))); + LineInformations overhang_lines; Polygons last_overhang = overhang_outset; size_t dtt_roof = 0; std::vector added_roofs(support_roof_layers); // sometimes roofs could be empty as the pattern does not generate lines if the area is narrow enough (i am looking at you, concentric infill). To catch these cases the added roofs are saved to be evaluated later. @@ -908,21 +984,21 @@ void TreeSupport::generateInitialAreas(const SliceMeshStorage& mesh, std::vector // Main problem is that some patterns change each layer, so just calculating points and checking if they are still valid an layer below is not useful, as the pattern may be different one layer below. // Same with calculating which points are now no longer being generated as result from a decreasing roof, as there is no guarantee that a line will be above these points. // Implementing a separate roof support behavior for each pattern harms maintainability as it very well could be >100 LOC - if (roof_allowed_for_this_part) - { - for (dtt_roof = 0; dtt_roof < support_roof_layers && layer_idx - dtt_roof >= 1; dtt_roof++) - { + if (roof_allowed_for_this_part) { + for (dtt_roof = 0; dtt_roof < support_roof_layers && layer_idx - dtt_roof >= 1; dtt_roof++) { // here the roof is handled. If roof can not be added the branches will try to not move instead - Polygons forbidden_next = (mesh_config.support_rests_on_model ? (only_gracious ? volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, true, !xy_overrides) : volumes_.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides)) : volumes_.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides)); - forbidden_next = forbidden_next.offset(5); // prevent rounding errors down the line - Polygons overhang_outset_next = overhang_outset.difference(forbidden_next); - if (overhang_outset_next.area() / (1000 * 1000) < minimum_roof_area) // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter - { + Polygons forbidden_next = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides_z));\ + // prevent rounding errors down the line + forbidden_next = offset(union_ex(forbidden_next), scaled(0.005)); + Polygons overhang_outset_next = diff(overhang_outset, forbidden_next); + if (area(overhang_outset_next) < mesh_group_settings.minimum_roof_area) { + // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; - if (dtt_roof != 0) - { - overhang_lines = convertLinesToInternal(ensureMaximumDistancePolyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); - overhang_lines = splitLines(overhang_lines, getEvaluatePointForNextLayerFunction(layer_idx - dtt_before)).first; + if (dtt_roof != 0) { + // Produce support head points supporting an interface layer: First produce the interface lines, then sample them. + overhang_lines = convertLinesToInternal(m_volumes, m_config, ensureMaximumDistancePolyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); + overhang_lines = splitLines(overhang_lines, + [this, layer_idx, dtt_before](std::pair &p){ return evaluatePointForNextLayerFunction(m_volumes, m_config, layer_idx - dtt_before, p); }).first; } break; @@ -934,135 +1010,190 @@ void TreeSupport::generateInitialAreas(const SliceMeshStorage& mesh, std::vector } size_t layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; // 1 inside max and -1 outside to avoid underflow. layer_generation_dtt=dtt_roof-1 if dtt_roof!=0; - if (overhang_lines.empty() && dtt_roof != 0 && generateLines(overhang_outset, true, layer_idx - layer_generation_dtt).empty()) // if the roof should be valid, check that the area does generate lines. This is NOT guaranteed. - { - for (size_t idx = 0; idx < dtt_roof; idx++) - { + // if the roof should be valid, check that the area does generate lines. This is NOT guaranteed. + if (overhang_lines.empty() && dtt_roof != 0 && generateLines(overhang_outset, true, layer_idx - layer_generation_dtt).empty()) + for (size_t idx = 0; idx < dtt_roof; idx++) { // check for every roof area that it has resulting lines. Remember idx 1 means the 2. layer of roof => higher idx == lower layer - if (generateLines(added_roofs[idx], true, layer_idx - idx).empty()) - { + if (generateLines(added_roofs[idx], true, layer_idx - idx).empty()) { dtt_roof = idx; layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; break; } } - } { std::lock_guard 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 - } +//FIXME store support areas or hatch the lines? +// for (size_t idx = 0; idx < dtt_roof; idx++) +// append(storage.support.supportLayers[layer_idx - idx].support_roof, added_roofs[idx]); // will be unioned in finalizeInterfaceAndSupportAreas } - 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) - { + if (overhang_lines.empty()) { + // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, but not only is this the only reasonable choice, + // but it ensures consistant behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. + // This is not doen when a roof is above as the roof will support the model and the trees only need to support the roof + Polylines polylines = ensureMaximumDistancePolyline(generateLines(overhang_outset, dtt_roof != 0, layer_idx - layer_generation_dtt), dtt_roof == 0 ? mesh_config.min_radius / 2 : connect_length, 1); + size_t point_count = 0; + for (const Polyline &poly : polylines) + point_count += poly.size(); + if (point_count <= min_support_points) { // add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the the support line width. - 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); - } - else - { - polylines = ensureMaximumDistancePolyline(toPolylines(overhang_outset), connect_length, min_support_points); - } + // I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them + // will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, + // as some support is better than none. + Polygons reduced_overhang_outset = offset(union_ex(overhang_outset), -mesh_config.support_line_width / 2.2); + polylines = ensureMaximumDistancePolyline( + to_polylines(!reduced_overhang_outset.empty() && area(offset(diff_ex(overhang_outset, reduced_overhang_outset), std::max(mesh_config.support_line_width, connect_length))) < 1 ? + reduced_overhang_outset : + overhang_outset), + connect_length, min_support_points); } LayerIndex last_insert_layer = layer_idx - dtt_roof; - overhang_lines = convertLinesToInternal(polylines, last_insert_layer); + overhang_lines = convertLinesToInternal(m_volumes, m_config, polylines, last_insert_layer); } - if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part) // reached buildplate - { - { - std::lock_guard critical_section_storage(critical_sections); - storage.support.supportLayers[0].support_roof.add(overhang_outset); - } - } - else // normal trees have to be generated - { + if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part) { // reached buildplate + std::lock_guard critical_section_storage(critical_sections); +//FIXME store support roofs +// append(storage.support.supportLayers[0].support_roof, overhang_outset); + } else // normal trees have to be generated addLinesAsInfluenceAreas(overhang_lines, force_tip_to_roof ? support_roof_layers - dtt_roof : 0, layer_idx - dtt_roof, dtt_roof > 0, roof_enabled ? support_roof_layers - dtt_roof : 0); - } } } }); - - delete cross_fill_provider; } -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 +static unsigned int moveInside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits::max()) { - bool do_final_difference = last_step_offset_without_check == 0; - Polygons ret = safeUnion(me); // ensure sane input - if (distance == 0) - { - return (do_final_difference ? ret.difference(collision) : ret).unionPolygons(); - } - if (safe_step_size < 0 || last_step_offset_without_check < 0) - { - logError("Offset increase got invalid parameter!\n"); - TreeSupport::showError("Negative offset distance... How did you manage this ?", true); - return (do_final_difference ? ret.difference(collision) : ret).unionPolygons(); - } + Point ret = from; + double bestDist2 = std::numeric_limits::max(); + auto bestPoly = static_cast(-1); + bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary + for (unsigned int poly_idx = 0; poly_idx < polygons.size(); ++ poly_idx) { + const Polygon &poly = polygons[poly_idx]; + if (poly.size() < 2) + continue; + Point p0 = poly[poly.size() - 2]; + Point p1 = poly.back(); + // because we compare with vSize2 here (no division by zero), we also need to compare by vSize2 inside the loop + // to avoid integer rounding edge cases + bool projected_p_beyond_prev_segment = (p1 - p0).cast().dot((from - p0).cast()) >= (p1 - p0).cast().squaredNorm(); + for (const Point& p2 : poly) { + // X = A + Normal(B-A) * (((B-A) dot (P-A)) / VSize(B-A)); + // = A + (B-A) * ((B-A) dot (P-A)) / VSize2(B-A); + // X = P projected on AB + const Point& a = p1; + const Point& b = p2; + const Point& p = from; + auto ab = (b - a).cast(); + auto ap = (p - a).cast(); + int64_t ab_length2 = ab.squaredNorm(); + if (ab_length2 <= 0) { //A = B, i.e. the input polygon had two adjacent points on top of each other. + p1 = p2; //Skip only one of the points. + continue; + } + int64_t dot_prod = ab.dot(ap); + if (dot_prod <= 0) { // x is projected to before ab + if (projected_p_beyond_prev_segment) { + // case which looks like: > . + projected_p_beyond_prev_segment = false; + Point& x = p1; - coord_t step_size = safe_step_size; - 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) - { - 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; + auto dist2 = (x - p).cast().squaredNorm(); + if (dist2 < bestDist2) { + bestDist2 = dist2; + bestPoly = poly_idx; + if (distance == 0) + ret = x; + else { + Vec2d abd = ab.cast(); + Vec2d p1p2 = p1 - p0; + double lab = abd.norm(); + double lp1p2 = p1p2.norm(); + // inward direction irrespective of sign of [distance] + auto inward_dir = perp(abd * (scaled(10.0) / lab) + p1p2 * (scaled(10.0) / lp1p2)); + // MM2INT(10.0) to retain precision for the eventual normalization + ret = x + (inward_dir * (distance / inward_dir.norm())).cast(); + is_already_on_correct_side_of_boundary = inward_dir.dot((p - x).cast()) * distance >= 0; + } + } + } else { + projected_p_beyond_prev_segment = false; + p0 = p1; + p1 = p2; + continue; + } + } else if (dot_prod >= ab_length2) { + // x is projected to beyond ab + projected_p_beyond_prev_segment = true; + p0 = p1; + p1 = p2; + continue; + } else { + // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | . + projected_p_beyond_prev_segment = false; + Point x = a + (ab.cast() * (double(dot_prod) / double(ab_length2))).cast(); + auto dist2 = (p - x).cast().squaredNorm(); + if (dist2 < bestDist2) { + bestDist2 = dist2; + bestPoly = poly_idx; + if (distance == 0) + ret = x; + else { + Vec2d abd = ab.cast(); + Vec2d inward_dir = perp(abd * (distance / abd.norm())); // inward or outward depending on the sign of [distance] + ret = x + inward_dir.cast(); + is_already_on_correct_side_of_boundary = inward_dir.dot((p - x).cast()) >= 0; + } + } + } + p0 = p1; + p1 = p2; } } - 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; + // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside + if (is_already_on_correct_side_of_boundary) { + if (bestDist2 < distance * distance) + from = ret; + else { + // from = from; // original point stays unaltered. It is already inside by enough distance } + return bestPoly; + } else if (bestDist2 < maxDist2) { + from = ret; + return bestPoly; } - // offset in steps - for (size_t i = 0; i < steps; i++) - { - ret = ret.offset(step_size, ClipperLib::jtRound).difference(collision).unionPolygons(); - // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. - if (i % 10 == 7) - { - ret.simplify(15); - } - } - ret = ret.offset(distance - steps * step_size, ClipperLib::jtRound); // offset the remainder - - ret.simplify(15); - - if (do_final_difference) - { - ret = ret.difference(collision); - } - return ret.unionPolygons(); + return -1; } - -void TreeSupport::mergeHelper(std::map& reduced_aabb, std::map& input_aabb, const std::unordered_map& to_bp_areas, const std::unordered_map& to_model_areas, const std::map& influence_areas, std::unordered_map& insert_bp_areas, std::unordered_map& insert_model_areas, std::unordered_map& insert_influence, std::vector& erase, const LayerIndex layer_idx) +/*! + * \brief Merges Influence Areas if possible. + * + * Branches which do overlap have to be merged. This helper merges all elements in input with the elements into reduced_new_layer. + * Elements in input_aabb are merged together if possible, while elements reduced_new_layer_aabb are not checked against each other. + * + * \param reduced_aabb[in,out] The already processed elements. + * \param input_aabb[in] Not yet processed elements + * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. Value is the influence area where the center of a circle of support may be placed. + * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. + * Value is the influence area where the center of a circle of support may be placed. + * \param influence_areas[in] The influence areas without avoidance removed. + * \param insert_bp_areas[out] Elements to be inserted into the main dictionary after the Helper terminates. + * \param insert_model_areas[out] Elements to be inserted into the secondary dictionary after the Helper terminates. + * \param insert_influence[out] Elements to be inserted into the dictionary containing the largest possibly valid influence area (ignoring if the area may not be there because of avoidance) + * \param erase[out] Elements that should be deleted from the above dictionaries. + * \param layer_idx[in] The Index of the current Layer. + */ +static void mergeHelper( + const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, + std::map& reduced_aabb, std::map& input_aabb, + const std::unordered_map& to_bp_areas, const std::unordered_map& to_model_areas, + const std::map& influence_areas, + std::unordered_map& insert_bp_areas, std::unordered_map& insert_model_areas, + std::unordered_map& insert_influence, std::vector& erase, const LayerIndex layer_idx) { + using SupportElement = TreeSupport::SupportElement; + const bool first_merge_iteration = reduced_aabb.empty(); // If this is the first iteration, all elements in input have to be merged with each other for (std::map::iterator influence_iter = input_aabb.begin(); influence_iter != input_aabb.end(); influence_iter++) { @@ -1073,12 +1204,9 @@ void TreeSupport::mergeHelper(std::map& reduced_aab // As every area has to be checked for overlaps with other areas, some fast heuristic is needed to abort early if clearly possible // This is so performance critical that using a map lookup instead of the direct access of the cached AABBs can have a surprisingly large performance impact BoundingBox aabb = reduced_check_iter->second; - if (aabb.hit(influence_aabb)) - { + if (aabb.overlap(influence_aabb)) { if (!first_merge_iteration && input_aabb.count(reduced_check_iter->first)) - { break; // Do not try to merge elements that already should have been merged. Done for potential performance improvement. - } bool merging_gracious_and_non_gracious = reduced_check_iter->first.to_model_gracious != influence_iter->first.to_model_gracious; // we do not want to merge a gracious with a non gracious area as bad placement could negatively impact the dependability of the whole subtree bool merging_to_bp = reduced_check_iter->first.to_buildplate && influence_iter->first.to_buildplate; @@ -1086,25 +1214,16 @@ void TreeSupport::mergeHelper(std::map& reduced_aab coord_t increased_to_model_radius = 0; size_t larger_to_model_dtt = 0; - if (!merging_to_bp) - { + if (!merging_to_bp) { coord_t infl_radius = config.getRadius(influence_iter->first); // get the real radius increase as the user does not care for the collision model. coord_t redu_radius = config.getRadius(reduced_check_iter->first); - if (reduced_check_iter->first.to_buildplate != influence_iter->first.to_buildplate) - { - if (reduced_check_iter->first.to_buildplate) - { + if (reduced_check_iter->first.to_buildplate != influence_iter->first.to_buildplate) { + if (reduced_check_iter->first.to_buildplate) { if (infl_radius < redu_radius) - { increased_to_model_radius = influence_iter->first.increased_to_model_radius + redu_radius - infl_radius; - } - } - else - { + } else { if (infl_radius > redu_radius) - { increased_to_model_radius = reduced_check_iter->first.increased_to_model_radius + infl_radius - redu_radius; - } } } larger_to_model_dtt = std::max(influence_iter->first.distance_to_top, reduced_check_iter->first.distance_to_top); @@ -1112,20 +1231,14 @@ void TreeSupport::mergeHelper(std::map& reduced_aab // if a merge could place a stable branch on unstable ground, would be increasing the radius further than allowed to when merging to model and to_bp trees or would merge to model before it is known they will even been drawn the merge is skipped if (merging_min_and_regular_xy || merging_gracious_and_non_gracious || increased_to_model_radius > config.max_to_model_radius_increase || (!merging_to_bp && larger_to_model_dtt < config.min_dtt_to_model && !reduced_check_iter->first.supports_roof && !influence_iter->first.supports_roof)) - { continue; - } Polygons relevant_infl; Polygons relevant_redu; - - if (merging_to_bp) - { + if (merging_to_bp) { relevant_infl = to_bp_areas.count(influence_iter->first) ? to_bp_areas.at(influence_iter->first) : Polygons(); // influence_iter->first is a new element => not required to check if it was changed relevant_redu = insert_bp_areas.count(reduced_check_iter->first) ? insert_bp_areas[reduced_check_iter->first] : (to_bp_areas.count(reduced_check_iter->first) ? to_bp_areas.at(reduced_check_iter->first) : Polygons()); - } - else - { + } else { relevant_infl = to_model_areas.count(influence_iter->first) ? to_model_areas.at(influence_iter->first) : Polygons(); relevant_redu = insert_model_areas.count(reduced_check_iter->first) ? insert_model_areas[reduced_check_iter->first] : (to_model_areas.count(reduced_check_iter->first) ? to_model_areas.at(reduced_check_iter->first) : Polygons()); } @@ -1138,9 +1251,7 @@ void TreeSupport::mergeHelper(std::map& reduced_aab // the area of the bigger radius is used to ensure correct placement regarding the relevant avoidance, so if that would change an invalid area may be created if (!bigger_rad.first.can_use_safe_radius && smaller_rad.first.can_use_safe_radius) - { continue; - } // the bigger radius is used to verify that the area is still valid after the increase with the delta. If there were a point where the big influence area could be valid with can_use_safe_radius the element would already be can_use_safe_radius // the smaller radius, which gets increased by delta may reach into the area where use_min_xy_dist is no longer required. @@ -1150,15 +1261,12 @@ void TreeSupport::mergeHelper(std::map& reduced_aab // If this area has any intersections with the influence area of the larger collision radius, a branch (of the larger collision radius) placed in this intersection, has already engulfed the branch of the smaller collision radius. // Because of this a merge may happen even if the influence areas (that represent possible center points of branches) do not intersect yet. // Remember that collision radius <= real radius as otherwise this assumption would be false. - Polygons small_rad_increased_by_big_minus_small = safeOffsetInc(smaller_rad.second, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); // -3 avoids possible rounding errors - Polygons intersect = small_rad_increased_by_big_minus_small.intersection(bigger_rad.second); + Polygons small_rad_increased_by_big_minus_small = safeOffsetInc(smaller_rad.second, real_radius_delta, volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); // -3 avoids possible rounding errors + Polygons intersect = intersection(small_rad_increased_by_big_minus_small, bigger_rad.second); - if (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) - { - if (intersect.offset(-25).area() <= 1) // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). While 25 was guessed as enough, i did not have reason to change it. - { + if (area(intersect) > tiny_area_threshold) { // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) + if (area(offset(intersect, scaled(-0.025))) <= tiny_area_threshold) // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). While 25 was guessed as enough, i did not have reason to change it. continue; - } // Do the actual merge now that the branches are confirmed to be able to intersect. @@ -1166,40 +1274,31 @@ void TreeSupport::mergeHelper(std::map& reduced_aab // used at the end to estimate where to best place the branch on the bottom most layer // could be replaced with a random point inside the new area Point new_pos = reduced_check_iter->first.next_position; - if (!intersect.inside(new_pos, true)) - { - PolygonUtils::moveInside(intersect, new_pos); - } + if (! contains(intersect, new_pos)) + moveInside(intersect, new_pos); if (increased_to_model_radius == 0) - { increased_to_model_radius = std::max(reduced_check_iter->first.increased_to_model_radius, influence_iter->first.increased_to_model_radius); - } SupportElement key(reduced_check_iter->first, influence_iter->first, layer_idx - 1, new_pos, increased_to_model_radius, config); Polygons intersect_influence; Polygons infl_small = insert_influence.count(smaller_rad.first) ? insert_influence[smaller_rad.first] : (influence_areas.count(smaller_rad.first) ? influence_areas.at(smaller_rad.first) : Polygons()); Polygons infl_big = insert_influence.count(bigger_rad.first) ? insert_influence[bigger_rad.first] : (influence_areas.count(bigger_rad.first) ? influence_areas.at(bigger_rad.first) : Polygons()); - Polygons small_rad_increased_by_big_minus_small_infl = safeOffsetInc(infl_small, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); - intersect_influence = 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 + Polygons small_rad_increased_by_big_minus_small_infl = safeOffsetInc(infl_small, real_radius_delta, volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); + intersect_influence = intersection(small_rad_increased_by_big_minus_small_infl, infl_big); // if the one with the bigger radius with the lower radius removed overlaps we can merge intersect_influence = safeUnion(intersect_influence, intersect); // Rounding errors again. Do not ask me where or why. Polygons intersect_sec; - if (merging_to_bp && config.support_rests_on_model) - { - if (key.to_model_gracious) - { + if (merging_to_bp && config.support_rests_on_model) { + if (key.to_model_gracious) { Polygons sec_small = insert_model_areas.count(smaller_rad.first) ? insert_model_areas[smaller_rad.first] : (to_model_areas.count(smaller_rad.first) ? to_model_areas.at(smaller_rad.first) : Polygons()); Polygons sec_big = insert_model_areas.count(bigger_rad.first) ? insert_model_areas[bigger_rad.first] : (to_model_areas.count(bigger_rad.first) ? to_model_areas.at(bigger_rad.first) : Polygons()); - Polygons small_rad_increased_by_big_minus_small_sec = safeOffsetInc(sec_small, real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); - intersect_sec = 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 + Polygons small_rad_increased_by_big_minus_small_sec = safeOffsetInc(sec_small, real_radius_delta, volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); + intersect_sec = intersection(small_rad_increased_by_big_minus_small_sec, sec_big); // if the one with the bigger radius with the lower radius removed overlaps we can merge intersect_influence = safeUnion(intersect_influence, intersect_sec); // still rounding errors - } - else - { + } else intersect_sec = intersect_influence; - } } // remove the now merged elements from all buckets, as they do not exist anymore in their old form @@ -1212,17 +1311,15 @@ void TreeSupport::mergeHelper(std::map& reduced_aab (merging_to_bp ? insert_bp_areas : insert_model_areas).emplace(key, intersect); if (merging_to_bp && config.support_rests_on_model) - { insert_model_areas.emplace(key, intersect_sec); - } insert_influence.emplace(key, intersect_influence); erase.emplace_back(reduced_check_iter->first); erase.emplace_back(influence_iter->first); - Polygons merge = intersect.unionPolygons(intersect_sec).offset(config.getRadius(key), ClipperLib::jtRound).difference(volumes_.getCollision(0, layer_idx - 1)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. + Polygons merge = diff(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound), volumes.getCollision(0, layer_idx - 1)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. reduced_aabb.erase(reduced_check_iter->first); // this invalidates reduced_check_iter - reduced_aabb.emplace(key, BoundingBox(merge)); + reduced_aabb.emplace(key, get_extents(merge)); merged = true; break; @@ -1231,15 +1328,28 @@ void TreeSupport::mergeHelper(std::map& reduced_aab } if (!merged) - { reduced_aabb[influence_iter->first] = influence_aabb; - } } } - -void TreeSupport::mergeInfluenceAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, LayerIndex layer_idx) +/*! + * \brief Merges Influence Areas if possible. + * + * Branches which do overlap have to be merged. This manages the helper and uses a divide and conquer approach to parallelize this problem. This parallelization can at most accelerate the merging by a factor of 2. + * + * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. + * Value is the influence area where the center of a circle of support may be placed. + * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. + * Value is the influence area where the center of a circle of support may be placed. + * \param influence_areas[in] The Elements of the current Layer without avoidances removed. This is the largest possible influence area for this layer. + * Value is the influence area where the center of a circle of support may be placed. + * \param layer_idx[in] The current layer. + */ +static void mergeInfluenceAreas( + const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, + std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, LayerIndex layer_idx) { + using SupportElement = TreeSupport::SupportElement; /* * Idea behind this is that the calculation of merges can be accelerated a bit using divide and conquer: * If two groups of areas are already merged, only all elements in group 2 have to be merged into group one. @@ -1247,14 +1357,11 @@ void TreeSupport::mergeInfluenceAreas(std::unordered_map> buckets_area(2 * bucket_count); std::vector> buckets_aabb(2 * bucket_count); - size_t position = 0, counter = 0; const size_t over_elements = input_size % bucket_count; const size_t elements_per_step = input_size / bucket_count; // split the data in x parts to be able to divide and conquer // the first "over_elements" of buckets gets elements_per_step+1 elements - for (std::map::iterator iter = influence_areas.begin(); iter != influence_areas.end(); iter++) - { + for (std::map::iterator iter = influence_areas.begin(); iter != influence_areas.end(); ++ iter) { buckets_area[position * 2 + 1].emplace(iter->first, iter->second); // only use every second bucket beginning with 1 as this makes the parallel call later easier as we assume everything in a bucket i%2==0 is already processed - counter++; - if ((counter == elements_per_step && position >= over_elements) || counter > elements_per_step) - { + ++ counter; + if ((counter == elements_per_step && position >= over_elements) || counter > elements_per_step) { position++; counter = 0; } } // precalculate the AABBs from the influence areas. - tbb::parallel_for(tbb::blocked_range(0, bucket_count), [&](const tbb::blocked_range &range) { for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - { // +=2 as in the beginning only uneven buckets will be filled size_t bucket_idx = 2 * idx + 1; for (const std::pair& input_pair : buckets_area[bucket_idx]) - { - BoundingBox outer_support_wall_aabb = BoundingBox(input_pair.second); - outer_support_wall_aabb.expand(config.getRadius(input_pair.first)); - buckets_aabb[bucket_idx].emplace(input_pair.first, outer_support_wall_aabb); - } + buckets_aabb[bucket_idx].emplace(input_pair.first, get_extents(input_pair.second).inflated(config.getRadius(input_pair.first))); } }); - while (buckets_area.size() > 1) - { + while (buckets_area.size() > 1) { // Some temporary storage, of elements that have to be inserted or removed from the background storage. Only one per two buckets required std::vector> insert_main(buckets_area.size() / 2); std::vector> insert_secondary(buckets_area.size() / 2); std::vector> insert_influence(buckets_area.size() / 2); std::vector> erase(buckets_area.size() / 2); - tbb::parallel_for(tbb::blocked_range(0, buckets_area.size() / 2), [&](const tbb::blocked_range &range) { for (size_t idx = range.begin(); idx < range.end(); ++ idx) { const size_t bucket_pair_idx = idx * 2; // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets - mergeHelper(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 + mergeHelper(volumes, config, buckets_aabb[bucket_pair_idx], buckets_aabb[bucket_pair_idx + 1], to_bp_areas, to_model_areas, influence_areas, insert_main[bucket_pair_idx / 2], insert_secondary[bucket_pair_idx / 2], insert_influence[bucket_pair_idx / 2], erase[bucket_pair_idx / 2], layer_idx); + // clear now irrelevant max_bucket_count, and delete them later + buckets_area[bucket_pair_idx + 1].clear(); buckets_aabb[bucket_pair_idx + 1].clear(); } }); - for (coord_t i = 0; i < (coord_t)buckets_area.size() - 1; i = i + 2) - { - for (SupportElement& del : erase[i / 2]) - { + for (size_t i = 0; i + 1 < buckets_area.size(); i += 2) { + for (SupportElement &del : erase[i / 2]) { to_bp_areas.erase(del); to_model_areas.erase(del); influence_areas.erase(del); } - - for (const std::pair& tup : insert_main[i / 2]) - { - to_bp_areas.emplace(tup); - } - - for (const std::pair& tup : insert_secondary[i / 2]) - { - to_model_areas.emplace(tup); - } - for (const std::pair& tup : insert_influence[i / 2]) - { - influence_areas.emplace(tup); - } + for (const std::pair &tup : insert_main[i / 2]) + to_bp_areas.emplace(std::move(tup)); + for (const std::pair &tup : insert_secondary[i / 2]) + to_model_areas.emplace(std::move(tup)); + for (const std::pair &tup : insert_influence[i / 2]) + influence_areas.emplace(std::move(tup)); } - 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()); + buckets_area.erase(std::remove_if(buckets_area.begin(), buckets_area.end(), [&](const std::map &x) { return x.empty(); }), buckets_area.end()); + buckets_aabb.erase(std::remove_if(buckets_aabb.begin(), buckets_aabb.end(), [&](const std::map &x) { return x.empty(); }), buckets_aabb.end()); } } @@ -1362,170 +1446,123 @@ std::optional TreeSupport::increaseSingleArea(AreaI SupportElement current_elem(parent); // also increases DTT by one Polygons check_layer_data; if (settings.increase_radius) - { current_elem.effective_radius_height += 1; - } - coord_t radius = config.getCollisionRadius(current_elem); + coord_t radius = m_config.getCollisionRadius(current_elem); - if (settings.move) - { + if (settings.move) { increased = relevant_offset; - if (overspeed > 0) - { - 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); + if (overspeed > 0) { + const coord_t safe_movement_distance = (current_elem.use_min_xy_dist ? m_config.xy_min_distance : m_config.xy_distance) + (std::min(m_config.z_distance_top_layers, m_config.z_distance_bottom_layers) > 0 ? m_config.min_feature_size : 0); // The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later. The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later. - increased = safeOffsetInc(increased, overspeed, volumes_.getWallRestriction(config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist), safe_movement_distance, safe_movement_distance + radius, 1); + increased = safeOffsetInc(increased, overspeed, m_volumes.getWallRestriction(m_config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist), safe_movement_distance, safe_movement_distance + radius, 1); } if (settings.no_error && settings.move) - { - increased.simplify(25); // as ClipperLib::jtRound has to be used for offsets this simplify is VERY important for performance. - } - } - else // if no movement is done the areas keep parent area as no move == offset(0) - { + // as ClipperLib::jtRound has to be used for offsets this simplify is VERY important for performance. + polygons_simplify(increased, scaled(0.025)); + } else + // if no movement is done the areas keep parent area as no move == offset(0) increased = *parent->area; - } - if (mergelayer || current_elem.to_buildplate) - { - to_bp_data = safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); - if (!current_elem.to_buildplate && to_bp_data.area() > 1) // mostly happening in the tip, but with merges one should check every time, just to be sure. - { + if (mergelayer || current_elem.to_buildplate) { + to_bp_data = safeUnion(diff(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); + if (! current_elem.to_buildplate && area(to_bp_data) > tiny_area_threshold) { + // mostly happening in the tip, but with merges one should check every time, just to be sure. current_elem.to_buildplate = true; // sometimes nodes that can reach the buildplate are marked as cant reach, tainting subtrees. This corrects it. - logDebug("Corrected taint leading to a wrong to model value on layer %lld targeting %lld with radius %lld\n", layer_idx - 1, current_elem.target_height, radius); + BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong to model value on layer " << layer_idx - 1 << " targeting " << current_elem.target_height << " with radius " << radius; } } - if (config.support_rests_on_model) - { + if (m_config.support_rests_on_model) { if (mergelayer || current_elem.to_model_gracious) - { - to_model_data = safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); - } + to_model_data = safeUnion(diff(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); - if (!current_elem.to_model_gracious) - { - if (mergelayer && to_model_data.area() >= 1) - { + if (!current_elem.to_model_gracious) { + if (mergelayer && area(to_model_data) >= tiny_area_threshold) { current_elem.to_model_gracious = true; - logDebug("Corrected taint leading to a wrong non gracious value on layer %lld targeting %lld with radius %lld\n", layer_idx - 1, current_elem.target_height, radius); - } - else - { - to_model_data = safeUnion(increased.difference(volumes_.getCollision(radius, layer_idx - 1, settings.use_min_distance))); - } + BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong non gracious value on layer " << layer_idx - 1 << " targeting " << current_elem.target_height << " with radius " << radius; + } else + to_model_data = safeUnion(diff(increased, m_volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance))); } } check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; - if (settings.increase_radius && check_layer_data.area() > 1) - { + if (settings.increase_radius && area(check_layer_data) > tiny_area_threshold) { std::function validWithRadius = [&](coord_t next_radius) { - if (volumes_.ceilRadius(next_radius, settings.use_min_distance) <= volumes_.ceilRadius(radius, settings.use_min_distance)) - { + if (m_volumes.ceilRadius(next_radius, settings.use_min_distance) <= m_volumes.ceilRadius(radius, settings.use_min_distance)) return true; - } Polygons to_bp_data_2; if (current_elem.to_buildplate) - { - to_bp_data_2 = increased.difference(volumes_.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)).unionPolygons(); // regular union as output will not be used later => this area should always be a subset of the safeUnion one (i think) - } + to_bp_data_2 = diff(increased, m_volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)); // regular union as output will not be used later => this area should always be a subset of the safeUnion one (i think) Polygons to_model_data_2; - if (config.support_rests_on_model && !current_elem.to_buildplate) - { - if (!current_elem.to_model_gracious) - { - to_model_data_2 = increased.difference(volumes_.getCollision(next_radius, layer_idx - 1, settings.use_min_distance)).unionPolygons(); - } - else - { - to_model_data_2 = increased.difference(volumes_.getAvoidance(next_radius, layer_idx - 1, settings.type, true, settings.use_min_distance)).unionPolygons(); - } - } + if (m_config.support_rests_on_model && !current_elem.to_buildplate) + to_model_data_2 = diff(increased, + current_elem.to_model_gracious ? + m_volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : + m_volumes.getCollision(next_radius, layer_idx - 1, settings.use_min_distance)); Polygons check_layer_data_2 = current_elem.to_buildplate ? to_bp_data_2 : to_model_data_2; - - return check_layer_data_2.area() > 1; + return area(check_layer_data_2) > tiny_area_threshold; }; - coord_t ceil_radius_before = volumes_.ceilRadius(radius, settings.use_min_distance); + coord_t ceil_radius_before = m_volumes.ceilRadius(radius, settings.use_min_distance); + if (m_config.getCollisionRadius(current_elem) < m_config.increase_radius_until_radius && m_config.getCollisionRadius(current_elem) < m_config.getRadius(current_elem)) { + coord_t target_radius = std::min(m_config.getRadius(current_elem), m_config.increase_radius_until_radius); + coord_t current_ceil_radius = m_volumes.getRadiusNextCeil(radius, settings.use_min_distance); - if (config.getCollisionRadius(current_elem) < config.increase_radius_until_radius && config.getCollisionRadius(current_elem) < config.getRadius(current_elem)) - { - coord_t target_radius = std::min(config.getRadius(current_elem), config.increase_radius_until_radius); - coord_t current_ceil_radius = volumes_.getRadiusNextCeil(radius, settings.use_min_distance); - - while (current_ceil_radius < target_radius && validWithRadius(volumes_.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance))) - { - current_ceil_radius = volumes_.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance); - } + while (current_ceil_radius < target_radius && validWithRadius(m_volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance))) + current_ceil_radius = m_volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance); size_t resulting_eff_dtt = current_elem.effective_radius_height; - while (resulting_eff_dtt + 1 < current_elem.distance_to_top && config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= current_ceil_radius && config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= config.getRadius(current_elem)) - { + while (resulting_eff_dtt + 1 < current_elem.distance_to_top && m_config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= current_ceil_radius && m_config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= m_config.getRadius(current_elem)) resulting_eff_dtt++; - } current_elem.effective_radius_height = resulting_eff_dtt; } - radius = config.getCollisionRadius(current_elem); + radius = m_config.getCollisionRadius(current_elem); - const coord_t foot_radius_increase = config.branch_radius * (std::max(config.diameter_scale_bp_radius - config.diameter_angle_scale_factor, 0.0)); - double planned_foot_increase = std::min(1.0, double(config.recommendedMinRadius(layer_idx - 1) - config.getRadius(current_elem)) / foot_radius_increase); // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, which could cause the radius to become bigger than precalculated. + const coord_t foot_radius_increase = m_config.branch_radius * (std::max(m_config.diameter_scale_bp_radius - m_config.diameter_angle_scale_factor, 0.0)); + double planned_foot_increase = std::min(1.0, double(m_config.recommendedMinRadius(layer_idx - 1) - m_config.getRadius(current_elem)) / foot_radius_increase); // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, which could cause the radius to become bigger than precalculated. bool increase_bp_foot = planned_foot_increase > 0 && current_elem.to_buildplate; - if (increase_bp_foot && config.getRadius(current_elem) >= config.branch_radius && config.getRadius(current_elem) >= config.increase_radius_until_radius) - { - if (validWithRadius(config.getRadius(current_elem.effective_radius_height, current_elem.elephant_foot_increases + planned_foot_increase))) - { + if (increase_bp_foot && m_config.getRadius(current_elem) >= m_config.branch_radius && m_config.getRadius(current_elem) >= m_config.increase_radius_until_radius) + if (validWithRadius(m_config.getRadius(current_elem.effective_radius_height, current_elem.elephant_foot_increases + planned_foot_increase))) { current_elem.elephant_foot_increases += planned_foot_increase; - radius = config.getCollisionRadius(current_elem); + radius = m_config.getCollisionRadius(current_elem); } - } - if (ceil_radius_before != volumes_.ceilRadius(radius, settings.use_min_distance)) - { + if (ceil_radius_before != m_volumes.ceilRadius(radius, settings.use_min_distance)) { if (current_elem.to_buildplate) - { - to_bp_data = safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); - } - if (config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) - { - if (!current_elem.to_model_gracious) - { - to_model_data = safeUnion(increased.difference(volumes_.getCollision(radius, layer_idx - 1, settings.use_min_distance))); - } - else - { - to_model_data = safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); - } - } + to_bp_data = safeUnion(diff(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); + if (m_config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) + to_model_data = safeUnion(diff(increased, + current_elem.to_model_gracious ? + m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : + m_volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance) + )); check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; - if (check_layer_data.area() < 1) - { - logError("Lost area by doing catch up from %lld to radius %lld\n", ceil_radius_before, volumes_.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance)); + if (area(check_layer_data) < tiny_area_threshold) { + BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << m_volumes.ceilRadius(m_config.getCollisionRadius(current_elem), settings.use_min_distance); TreeSupport::showError("Area lost catching up radius. May not cause visible malformation.", true); } } } - return check_layer_data.area() > 1 ? std::optional(current_elem) : std::optional(); + return area(check_layer_data) > tiny_area_threshold ? std::optional(current_elem) : std::optional(); } - void TreeSupport::increaseAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, std::vector& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, const bool mergelayer) { std::mutex critical_sections; tbb::parallel_for(tbb::blocked_range(0, last_layer.size()), [&](const tbb::blocked_range &range) { - for (const size_t idx = range.begin(); idx < range.end(); ++ idx) { + for (size_t idx = range.begin(); idx < range.end(); ++ idx) { SupportElement* parent = last_layer[idx]; SupportElement elem(parent); // also increases dtt - 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 wall_restriction = m_volumes.getWallRestriction(m_config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist); // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. Polygons to_bp_data, to_model_data; - coord_t radius = config.getCollisionRadius(elem); + coord_t radius = m_config.getCollisionRadius(elem); // When the radius increases, the outer "support wall" of the branch will have been moved farther away from the center (as this is the definition of radius). // As it is not specified that the support_tree_angle has to be one of the center of the branch, it is here seen as the smaller angle of the outer wall of the branch, to the outer wall of the same branch one layer above. @@ -1534,9 +1571,9 @@ void TreeSupport::increaseAreas(std::unordered_map& to coord_t extra_speed = 5; // The extra speed is added to both movement distances. Also move 5 microns faster than allowed to avoid rounding errors, this may cause issues at VERY VERY small layer heights. coord_t extra_slow_speed = 0; // Only added to the slow movement distance. - const coord_t ceiled_parent_radius = 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); + const coord_t ceiled_parent_radius = m_volumes.ceilRadius(m_config.getCollisionRadius(*parent), parent->use_min_xy_dist); + coord_t projected_radius_increased = m_config.getRadius(parent->effective_radius_height + 1, parent->elephant_foot_increases); + coord_t projected_radius_delta = projected_radius_increased - m_config.getCollisionRadius(*parent); // 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): /* @@ -1545,33 +1582,24 @@ void TreeSupport::increaseAreas(std::unordered_map& to * layer z-1:dddddxxxxxxxxxx * For more detailed visualisation see calculateWallRestrictions */ - const coord_t safe_movement_distance = (elem.use_min_xy_dist ? 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) - { + const coord_t safe_movement_distance = (elem.use_min_xy_dist ? m_config.xy_min_distance : m_config.xy_distance) + (std::min(m_config.z_distance_top_layers, m_config.z_distance_bottom_layers) > 0 ? m_config.min_feature_size : 0); + if (ceiled_parent_radius == m_volumes.ceilRadius(projected_radius_increased, parent->use_min_xy_dist) || projected_radius_increased < m_config.increase_radius_until_radius) // If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of the slower moving wall extra_speed += projected_radius_delta; - } else - { // if a guaranteed radius increase is not possible, only increase the slow speed - extra_slow_speed += std::min(projected_radius_delta, (config.maximum_move_distance + extra_speed) - (config.maximum_move_distance_slow + extra_slow_speed)); // Ensure that the slow movement distance can not become larger than the fast one. - } + extra_slow_speed += std::min(projected_radius_delta, (m_config.maximum_move_distance + extra_speed) - (m_config.maximum_move_distance_slow + extra_slow_speed)); // Ensure that the slow movement distance can not become larger than the fast one. - if (config.layer_start_bp_radius > layer_idx && config.recommendedMinRadius(layer_idx - 1) < config.getRadius(elem.effective_radius_height + 1, elem.elephant_foot_increases)) - { + if (m_config.layer_start_bp_radius > layer_idx && m_config.recommendedMinRadius(layer_idx - 1) < m_config.getRadius(elem.effective_radius_height + 1, elem.elephant_foot_increases)) { // can guarantee elephant foot radius increase - if (ceiled_parent_radius == volumes_.ceilRadius(config.getRadius(parent->effective_radius_height + 1, parent->elephant_foot_increases + 1), parent->use_min_xy_dist)) - { - extra_speed += config.branch_radius * config.diameter_scale_bp_radius; - } + if (ceiled_parent_radius == m_volumes.ceilRadius(m_config.getRadius(parent->effective_radius_height + 1, parent->elephant_foot_increases + 1), parent->use_min_xy_dist)) + extra_speed += m_config.branch_radius * m_config.diameter_scale_bp_radius; else - { - extra_slow_speed += std::min(coord_t(config.branch_radius * config.diameter_scale_bp_radius), config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed)); - } + extra_slow_speed += std::min(coord_t(m_config.branch_radius * m_config.diameter_scale_bp_radius), m_config.maximum_move_distance - (m_config.maximum_move_distance_slow + extra_slow_speed)); } - const coord_t fast_speed = config.maximum_move_distance + extra_speed; - const coord_t slow_speed = config.maximum_move_distance_slow + extra_speed + extra_slow_speed; + const coord_t fast_speed = m_config.maximum_move_distance + extra_speed; + const coord_t slow_speed = m_config.maximum_move_distance_slow + extra_speed + extra_slow_speed; Polygons offset_slow, offset_fast; @@ -1583,54 +1611,47 @@ void TreeSupport::increaseAreas(std::unordered_map& to std::deque order; std::function insertSetting = [&](AreaIncreaseSettings settings, bool back) { - if (std::find(order.begin(), order.end(), settings) == order.end()) - { + if (std::find(order.begin(), order.end(), settings) == order.end()) { if (back) - { order.emplace_back(settings); - } else - { order.emplace_front(settings); - } } }; - const bool parent_moved_slow = elem.last_area_increase.increase_speed < config.maximum_move_distance; + const bool parent_moved_slow = elem.last_area_increase.increase_speed < m_config.maximum_move_distance; const bool avoidance_speed_mismatch = parent_moved_slow && elem.last_area_increase.type != AvoidanceType::SLOW; - if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && !mergelayer && !avoidance_speed_mismatch && (elem.distance_to_top >= config.tip_layers || parent_moved_slow)) + if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && !mergelayer && !avoidance_speed_mismatch && (elem.distance_to_top >= m_config.tip_layers || parent_moved_slow)) { // assume that the avoidance type that was best for the parent is best for me. Makes this function about 7% faster. - insertSetting(AreaIncreaseSettings(elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move), true); - insertSetting(AreaIncreaseSettings(elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, !increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move), true); + insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < m_config.maximum_move_distance ? slow_speed : fast_speed, increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); + insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < m_config.maximum_move_distance ? slow_speed : fast_speed, !increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); } // branch may still go though a hole, so a check has to be done whether the hole was already passed, and the regular avoidance can be used. if (!elem.can_use_safe_radius) { // if the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. // order.emplace_back(AvoidanceType::SLOW,!increase_radius,no_error,!use_min_radius,move); - insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, !use_min_radius, !move), true); // did we go through the hole + insertSetting({ AvoidanceType::SLOW, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we go through the hole // in many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. This CAN cause a branch to go though a hole it otherwise may have avoided. - if (elem.distance_to_top < round_up_divide(config.tip_layers, 2)) - { - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, slow_speed, increase_radius, no_error, !use_min_radius, !move), true); - } - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, !move), true); // did we manage to avoid the hole - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); + if (elem.distance_to_top < round_up_divide(m_config.tip_layers, 2)) + insertSetting({ AvoidanceType::FAST, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); + insertSetting({ AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we manage to avoid the hole + insertSetting({ AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); + insertSetting({ AvoidanceType::FAST, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); } else { - insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, !use_min_radius, move), true); + insertSetting({ AvoidanceType::SLOW, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); // while moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, which looks similar to a layer shift and can reduce stability. // as such idx have chosen to only use the user setting for radius increases as a friendly recommendation. - insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, !increase_radius, no_error, !use_min_radius, move), true); // a - if (elem.distance_to_top < config.tip_layers) + insertSetting({ AvoidanceType::SLOW, slow_speed, !increase_radius, no_error, !use_min_radius, move }, true); // a + if (elem.distance_to_top < m_config.tip_layers) { - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, slow_speed, increase_radius, no_error, !use_min_radius, move), true); + insertSetting({ AvoidanceType::FAST_SAFE, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); } - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, move), true); // b - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move), true); + insertSetting({ AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, move }, true); // b + insertSetting({ AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); } if (elem.use_min_xy_dist) @@ -1644,124 +1665,99 @@ void TreeSupport::increaseAreas(std::unordered_map& to } order = new_order; } - if (elem.to_buildplate || (elem.to_model_gracious && (parent->area->intersection(volumes_.getPlaceableAreas(radius, layer_idx)).empty()))) // error case + if (elem.to_buildplate || (elem.to_model_gracious && intersection(*parent->area, m_volumes.getPlaceableAreas(radius, layer_idx)).empty())) // error case { // it is normal that we wont be able to find a new area at some point in time if we wont be able to reach layer 0 aka have to connect with the model - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move), true); + insertSetting({ AvoidanceType::FAST, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move }, true); } if (elem.distance_to_top < elem.dont_move_until && elem.can_use_safe_radius) // only do not move when holes would be avoided in every case. - { - insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, 0, increase_radius, no_error, !use_min_radius, !move), false); // Only do not move when already in a no hole avoidance with the regular xy distance. - } + // Only do not move when already in a no hole avoidance with the regular xy distance. + insertSetting({ AvoidanceType::SLOW, 0, increase_radius, no_error, !use_min_radius, !move }, false); 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)); + bool offset_independant_faster = (radius / safe_movement_distance - (((m_config.maximum_move_distance + extra_speed) < (radius + safe_movement_distance)) ? 1 : 0)) > (round_up_divide((extra_speed + extra_slow_speed + m_config.maximum_move_distance_slow), safe_movement_distance)); for (AreaIncreaseSettings settings : order) { - if (settings.move) - { - 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.move) { + if (offset_slow.empty() && (settings.increase_speed == slow_speed || !offset_independant_faster)) { + // offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. At this point one can see that the Polygons class + // was never made for precision in the single digit micron range. + offset_slow = safeOffsetInc(*parent->area, extra_speed + extra_slow_speed + m_config.maximum_move_distance_slow, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2); } - if ((settings.increase_speed != slow_speed) && offset_fast.empty()) - { + 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(); + offset_fast = safeOffsetInc(*parent->area, extra_speed + m_config.maximum_move_distance, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 1); + else { + const coord_t delta_slow_fast = m_config.maximum_move_distance - (m_config.maximum_move_distance_slow + extra_slow_speed); + offset_fast = safeOffsetInc(offset_slow, delta_slow_fast, wall_restriction, safe_movement_distance, safe_movement_distance + radius, offset_independant_faster ? 2 : 1); } } } std::optional result; - if (!settings.no_error) // ERROR CASE - { + 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 = 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); + Polygons lines_offset = offset(to_polylines(*parent->area), scaled(0.005)); + Polygons base_error_area = union_(*parent->area, lines_offset); + result = increaseSingleArea(settings, layer_idx, parent, base_error_area, to_bp_data, to_model_data, inc_wo_collision, (m_config.maximum_move_distance + extra_speed) * 1.5, mergelayer); + BOOST_LOG_TRIVIAL(error) << + "Influence area could not be increased! Data about the Influence area: " + "Radius: " << radius << " at layer: " << layer_idx - 1 << " NextTarget: " << elem.next_height << " Distance to top: " << elem.distance_to_top << + " Elephant foot increases " << elem.elephant_foot_increases << " use_min_xy_dist " << elem.use_min_xy_dist << " to buildplate " << elem.to_buildplate << + " gracious " << elem.to_model_gracious << " safe " << elem.can_use_safe_radius << " until move " << elem.dont_move_until << " \n " + "Parent " << parent << ": Radius: " << m_config.getCollisionRadius(*parent) << " at layer: " << layer_idx << " NextTarget: " << parent->next_height << + " Distance to top: " << parent->distance_to_top << " Elephant foot increases " << parent->elephant_foot_increases << " use_min_xy_dist " << parent->use_min_xy_dist << + " to buildplate " << parent->to_buildplate << " gracious " << parent->to_model_gracious << " safe " << parent->can_use_safe_radius << " until move " << parent->dont_move_until; showError("Potentially lost branch!", true); - } - else - { + } else result = increaseSingleArea(settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); - } if (result) { elem = result.value(); - radius = config.getCollisionRadius(elem); + radius = m_config.getCollisionRadius(elem); elem.last_area_increase = settings; add = true; - bypass_merge = !settings.move || (settings.use_min_distance && elem.distance_to_top < config.tip_layers); // do not merge if the branch should not move or the priority has to be to get farther away from the model. + bypass_merge = !settings.move || (settings.use_min_distance && elem.distance_to_top < m_config.tip_layers); // do not merge if the branch should not move or the priority has to be to get farther away from the model. if (settings.move) - { elem.dont_move_until = 0; - } else - { elem.result_on_layer = parent->result_on_layer; - } elem.can_use_safe_radius = settings.type != AvoidanceType::FAST; if (!settings.use_min_distance) - { elem.use_min_xy_dist = false; - } if (!settings.no_error) - { - logError("Trying to keep area by moving faster than intended: Success \n"); - } + BOOST_LOG_TRIVIAL(error) << "Trying to keep area by moving faster than intended: Success"; break; } else if (!settings.no_error) - { - logError("Trying to keep area by moving faster than intended: FAILURE! WRONG BRANCHES LIKLY! \n"); - } + BOOST_LOG_TRIVIAL(error) << "Trying to keep area by moving faster than intended: FAILURE! WRONG BRANCHES LIKLY!"; } - if (add) - { - 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 - + if (add) { + Polygons max_influence_area = safeUnion(diff(inc_wo_collision, m_volumes.getCollision(radius, layer_idx - 1, elem.use_min_xy_dist)), safeUnion(to_bp_data, to_model_data)); // union seems useless, but some rounding errors somewhere can cause to_bp_data to be slightly bigger than it should be { std::lock_guard critical_section_newLayer(critical_sections); - if (bypass_merge) - { + if (bypass_merge) { Polygons* new_area = new Polygons(max_influence_area); SupportElement* next = new SupportElement(elem, new_area); bypass_merge_areas.emplace_back(next); - } - else - { + } else { influence_areas.emplace(elem, max_influence_area); if (elem.to_buildplate) - { to_bp_areas.emplace(elem, to_bp_data); - } - if (config.support_rests_on_model) - { + if (m_config.support_rests_on_model) to_model_areas.emplace(elem, to_model_data); - } } } } else - { parent->result_on_layer = Point(-1, -1); // If the bottom most point of a branch is set, later functions will assume that the position is valid, and ignore it. But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. A point can be set on the top most tip layer (maybe more if it should not move for a few layers). - } } }); } @@ -1770,7 +1766,9 @@ void TreeSupport::increaseAreas(std::unordered_map& to void TreeSupport::createLayerPathing(std::vector>& move_bounds) { const double data_size_inverse = 1 / double(move_bounds.size()); +#ifdef SLIC3R_TREESUPPORTS_PROGRESS double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES; +#endif // SLIC3R_TREESUPPORTS_PROGRESS auto dur_inc = std::chrono::duration_values::zero(); auto dur_merge = std::chrono::duration_values::zero(); @@ -1781,7 +1779,7 @@ void TreeSupport::createLayerPathing(std::vector>& mov LayerIndex last_merge = move_bounds.size(); bool new_element = false; - size_t max_merge_every_x_layers = std::min(std::min(5000 / (std::max(config.maximum_move_distance, coord_t(100))), 1000 / std::max(config.maximum_move_distance_slow, coord_t(20))), 3000 / config.layer_height); // Ensures at least one merge operation per 3mm height, 50 layers, 1 mm movement of slow speed or 5mm movement of fast speed (whatever is lowest). Values were guessed. + size_t max_merge_every_x_layers = std::min(std::min(5000 / (std::max(m_config.maximum_move_distance, coord_t(100))), 1000 / std::max(m_config.maximum_move_distance_slow, coord_t(20))), 3000 / m_config.layer_height); // Ensures at least one merge operation per 3mm height, 50 layers, 1 mm movement of slow speed or 5mm movement of fast speed (whatever is lowest). Values were guessed. size_t merge_every_x_layers = 1; // Calculate the influence areas for each layer below (Top down) // This is done by first increasing the influence area by the allowed movement distance, and merging them with other influence areas if possible @@ -1814,7 +1812,7 @@ void TreeSupport::createLayerPathing(std::vector>& mov bool reduced_by_merging = false; size_t count_before_merge = influence_areas.size(); // ### Calculate which influence areas overlap, and merge them into a new influence area (simplified: an intersection of influence areas that have such an intersection) - mergeInfluenceAreas(to_bp_areas, to_model_areas, influence_areas, layer_idx); + mergeInfluenceAreas(m_volumes, m_config, to_bp_areas, to_model_areas, influence_areas, layer_idx); last_merge = layer_idx; reduced_by_merging = count_before_merge > influence_areas.size(); @@ -1831,37 +1829,34 @@ void TreeSupport::createLayerPathing(std::vector>& mov new_element = !move_bounds[layer_idx - 1].empty(); // Save calculated elements to output, and allocate Polygons on heap, as they will not be changed again. - for (std::pair tup : influence_areas) - { + for (std::pair tup : influence_areas) { const SupportElement elem = tup.first; Polygons* new_area = new Polygons(safeUnion(tup.second)); SupportElement* next = new SupportElement(elem, new_area); move_bounds[layer_idx - 1].emplace(next); - if (new_area->area() < 1) - { - logError("Insert Error of Influence area on layer %lld. Origin of %lld areas. Was to bp %d\n", layer_idx - 1, elem.parents.size(), elem.to_buildplate); + if (area(*new_area) < tiny_area_threshold) { + BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.to_buildplate; TreeSupport::showError("Insert error of area after merge.\n", true); } } // Place already fully constructed elements in the output. - for (SupportElement* elem : bypass_merge_areas) - { - if (elem->area->area() < 1) - { - logError("Insert Error of Influence area bypass on layer %lld.\n", layer_idx - 1); + for (SupportElement* elem : bypass_merge_areas) { + if (area(*elem->area) < tiny_area_threshold) { + BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; TreeSupport::showError("Insert error of area after bypassing merge.\n", true); - } move_bounds[layer_idx - 1].emplace(elem); } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS progress_total += data_size_inverse * TREE_PROGRESS_AREA_CALC; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); +#endif } - 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); + BOOST_LOG_TRIVIAL(info) << "Time spent with creating influence areas' subtasks: Increasing areas " << dur_inc.count() / 1000000 << " ms merging areas: " << dur_merge.count() / 1000000 << " ms"; } @@ -1871,7 +1866,7 @@ void TreeSupport::setPointsOnAreas(const SupportElement* elem) if (elem->result_on_layer == Point(-1, -1)) { - logError("Uninitialized support element\n"); + BOOST_LOG_TRIVIAL(error) << "Uninitialized support element"; TreeSupport::showError("Uninitialized support element. A branch may be missing.\n", true); return; } @@ -1879,14 +1874,11 @@ void TreeSupport::setPointsOnAreas(const SupportElement* elem) for (SupportElement* next_elem : elem->parents) { if (next_elem->result_on_layer != Point(-1, -1)) // if the value was set somewhere else it it kept. This happens when a branch tries not to move after being unable to create a roof. - { continue; - } Point from = elem->result_on_layer; - if (!(next_elem->area->inside(from, true))) - { - PolygonUtils::moveInside(*next_elem->area, from, 0); // Move inside has edgecases (see tests) so DONT use Polygons.inside to confirm correct move, Error with distance 0 is <= 1 + if (! contains(*next_elem->area, from)) { + moveInside(*next_elem->area, from, 0); // Move inside has edgecases (see tests) so DONT use Polygons.inside to confirm correct move, Error with distance 0 is <= 1 // it is not required to check if how far this move moved a point as is can be larger than maximum_movement_distance. While this seems like a problem it may for example occur after merges. } next_elem->result_on_layer = from; @@ -1908,8 +1900,7 @@ bool TreeSupport::setToModelContact(std::vector>& move for (LayerIndex layer_check = layer_idx; check->next_height >= layer_check; layer_check++) { - if (!check->area->intersection(volumes_.getPlaceableAreas(config.getCollisionRadius(*check), layer_check)).empty()) - { + if (! intersection(*check->area, m_volumes.getPlaceableAreas(m_config.getCollisionRadius(*check), layer_check)).empty()) { set = true; last_successfull_layer = layer_check; } @@ -1929,7 +1920,7 @@ bool TreeSupport::setToModelContact(std::vector>& move { if (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL) { - logWarning("No valid placement found for to model gracious element on layer %lld: REMOVING BRANCH\n", layer_idx); + BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << layer_idx << ": REMOVING BRANCH"; TreeSupport::showError("Could not fine valid placement on model! Removing this branch...", true); for (LayerIndex layer = layer_idx; layer <= first_elem->next_height; layer++) { @@ -1940,7 +1931,7 @@ bool TreeSupport::setToModelContact(std::vector>& move } else { - logWarning("No valid placement found for to model gracious element on layer %lld\n", layer_idx); + BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << layer_idx; TreeSupport::showError("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches.", true); first_elem->to_model_gracious = false; return setToModelContact(move_bounds, first_elem, layer_idx); @@ -1956,26 +1947,22 @@ bool TreeSupport::setToModelContact(std::vector>& move // Guess a point inside the influence area, in which the branch will be placed in. Point best = checked[last_successfull_layer - layer_idx]->next_position; - if (!checked[last_successfull_layer - layer_idx]->area->inside(best, true)) - { - PolygonUtils::moveInside(*checked[last_successfull_layer - layer_idx]->area, best); - } + if (! contains(*checked[last_successfull_layer - layer_idx]->area, best)) + moveInside(*checked[last_successfull_layer - layer_idx]->area, best); checked[last_successfull_layer - layer_idx]->result_on_layer = best; - logDebug("Added gracious Support On Model Point (%lld,%lld). The current layer is %lld\n", best.x(), best.y(), last_successfull_layer); + BOOST_LOG_TRIVIAL(debug) << "Added gracious Support On Model Point (" << best.x() << "," << best.y() << "). The current layer is ", last_successfull_layer; return last_successfull_layer != layer_idx; } else // can not add graceful => just place it here and hope for the best { Point best = first_elem->next_position; - if (!first_elem->area->inside(best, true)) - { - PolygonUtils::moveInside(*first_elem->area, best); - } + if (! contains(*first_elem->area, best)) + moveInside(*first_elem->area, best); first_elem->result_on_layer = best; first_elem->to_model_gracious = false; - logDebug("Added NON gracious Support On Model Point (%lld,%lld). The current layer is %lld\n", best.x(), best.y(), layer_idx); + BOOST_LOG_TRIVIAL(debug) << "Added NON gracious Support On Model Point (" << best.x() << "," << best.y() << "). The current layer is ", layer_idx; return false; } } @@ -1984,62 +1971,45 @@ bool TreeSupport::setToModelContact(std::vector>& move void TreeSupport::createNodesFromArea(std::vector>& move_bounds) { // Initialize points on layer 0, with a "random" point in the influence area. Point is chosen based on an inaccurate estimate where the branches will split into two, but every point inside the influence area would produce a valid result. - for (SupportElement* init : move_bounds[0]) - { + for (SupportElement* init : move_bounds[0]) { Point p = init->next_position; - if (!(init->area->inside(p, true))) - { - PolygonUtils::moveInside(*init->area, p, 0); - } + if (! contains(*init->area, p)) + moveInside(*init->area, p, 0); init->result_on_layer = p; - setPointsOnAreas(init); // also set the parent nodes, as these will be required for the first iteration of the loop below } - - for (LayerIndex layer_idx = 1; layer_idx < LayerIndex(move_bounds.size()); layer_idx++) - { + for (LayerIndex layer_idx = 1; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { std::unordered_set remove; - for (SupportElement* elem : move_bounds[layer_idx]) - { + for (SupportElement* elem : move_bounds[layer_idx]) { bool removed = false; - if (elem->result_on_layer == Point(-1, -1)) // check if the resulting center point is not yet set - { - if (elem->to_buildplate || (!elem->to_buildplate && elem->distance_to_top < config.min_dtt_to_model && !elem->supports_roof)) - { - if (elem->to_buildplate) - { - logError("Uninitialized Influence area targeting (%lld,%lld) at target_height: %lld layer: %lld\n", elem->target_position.x(), elem->target_position.y(), elem->target_height, layer_idx); + // check if the resulting center point is not yet set + if (elem->result_on_layer == Point(-1, -1)) { + if (elem->to_buildplate || (!elem->to_buildplate && elem->distance_to_top < m_config.min_dtt_to_model && !elem->supports_roof)) { + if (elem->to_buildplate) { + BOOST_LOG_TRIVIAL(error) << "Uninitialized Influence area targeting " << elem->target_position.x() << "," << elem->target_position.y() << ") " + "at target_height: " << elem->target_height << " layer: " << layer_idx; TreeSupport::showError("Uninitialized support element! A branch could be missing or exist partially.", true); } remove.emplace(elem); // we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set removed = true; for (SupportElement* parent : elem->parents) - { parent->result_on_layer = Point(-1, -1); // When the roof was not able to generate downwards enough, the top elements may have not moved, and have result_on_layer already set. As this branch needs to be removed => all parents result_on_layer have to be invalidated. - } continue; - } - else - { + } else { // set the point where the branch will be placed on the model removed = setToModelContact(move_bounds, elem, layer_idx); if (removed) - { remove.emplace(elem); - } } } if (!removed) - { setPointsOnAreas(elem); // element is valid now setting points in the layer above - } } // delete all not needed support elements - for (SupportElement* del : remove) - { + for (SupportElement* del : remove) { move_bounds[layer_idx].erase(del); delete del->area; delete del; @@ -2050,60 +2020,61 @@ void TreeSupport::createNodesFromArea(std::vector>& mo void TreeSupport::generateBranchAreas(std::vector>& linear_data, std::vector>& layer_tree_polygons, const std::map& inverse_tree_order) { +#ifdef SLIC3R_TREESUPPORTS_PROGRESS 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; +#endif // SLIC3R_TREESUPPORTS_PROGRESS + 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++) - { + for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; ++ i) { const AngleRadians angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); - branch_circle.emplace_back(coord_t(cos(angle) * config.branch_radius), coord_t(sin(angle) * config.branch_radius)); + branch_circle.points.emplace_back(coord_t(cos(angle) * m_config.branch_radius), coord_t(sin(angle) * m_config.branch_radius)); } std::vector linear_inserts(linear_data.size()); + +#ifdef SLIC3R_TREESUPPORTS_PROGRESS const size_t progress_inserts_check_interval = linear_data.size() / progress_report_steps; +#endif // SLIC3R_TREESUPPORTS_PROGRESS std::mutex critical_sections; tbb::parallel_for(tbb::blocked_range(0, linear_data.size()), [&](const tbb::blocked_range &range) { - for (const size_t idx = range.begin(); idx < range.end(); ++ idx) { + for (size_t idx = range.begin(); idx < range.end(); ++ idx) { SupportElement* elem = linear_data[idx].second; - coord_t radius = config.getRadius(*elem); + coord_t radius = m_config.getRadius(*elem); bool parent_uses_min = false; 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 (!elem->skip_ovalisation) - { - if (child_elem != nullptr) - { + if (!elem->skip_ovalisation) { + 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) - { + 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)); + movement_directions.emplace_back(movement, std::max(m_config.getRadius(parent), m_config.support_line_width)); parent_uses_min |= parent->use_min_xy_dist; } } - coord_t max_speed = 0; - std::function generateArea = [&](coord_t offset) + double max_speed = 0; + std::function generateArea = [&](coord_t aoffset) { Polygons poly; - for (std::pair movement : movement_directions) - { - max_speed = std::max(max_speed, vSize(movement.first)); + for (std::pair movement : movement_directions) { + max_speed = std::max(max_speed, movement.first.cast().norm()); // Visualization: https://jsfiddle.net/0zvcq39L/2/ // Ovalizes the circle to an ellipse, that contains both old center and new target position. - double used_scale = (movement.second + offset) / (1.0 * config.branch_radius); + double used_scale = (movement.second + aoffset) / (1.0 * m_config.branch_radius); Point center_position = elem->result_on_layer + movement.first / 2; - const double moveX = movement.first.x() / (used_scale * config.branch_radius); - const double moveY = movement.first.y() / (used_scale * config.branch_radius); + const double moveX = movement.first.x() / (used_scale * m_config.branch_radius); + const double moveY = movement.first.y() / (used_scale * m_config.branch_radius); const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); double matrix[] = { @@ -2114,207 +2085,173 @@ 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 = diff(offset(union_(poly), std::min(coord_t(50), m_config.support_line_width / 4)), m_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; }; - bool fast_relative_movement = max_speed > 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[idx] = generateArea(0); - if (fast_relative_movement || config.getRadius(*elem) - config.getCollisionRadius(*elem) > config.support_line_width) - { + if (fast_relative_movement || m_config.getRadius(*elem) - m_config.getCollisionRadius(*elem) > m_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[idx].offset(-config.support_line_width / 2); - if (nozzle_path.splitIntoParts(false).size() > 1) - { + ExPolygons nozzle_path = offset_ex(linear_inserts[idx], -m_config.support_line_width / 2); + if (nozzle_path.size() > 1) { // Just try to make the area a tiny bit larger. - linear_inserts[idx] = generateArea(config.support_line_width / 2); - nozzle_path = linear_inserts[idx].offset(-config.support_line_width / 2); + linear_inserts[idx] = generateArea(m_config.support_line_width / 2); + nozzle_path = offset_ex(linear_inserts[idx], -m_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) - { + if (nozzle_path.size() > 1) { Polygons polygons_with_correct_center; - for (PolygonsPart part : nozzle_path.splitIntoParts(false)) - { - if (part.inside(elem->result_on_layer, true)) - { - polygons_with_correct_center = polygons_with_correct_center.unionPolygons(part); - } - else - { + for (const ExPolygon &part : nozzle_path) { + if (part.contains(elem->result_on_layer)) + polygons_with_correct_center = union_(polygons_with_correct_center, part); + else { // try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... Point from = elem->result_on_layer; - PolygonUtils::moveInside(part, from, 0); - if (vSize(elem->result_on_layer - from) < 25) - { - polygons_with_correct_center = polygons_with_correct_center.unionPolygons(part); - } + moveInside(part, from, 0); + if ((elem->result_on_layer - from).cast().norm() < scaled(0.025)) + polygons_with_correct_center = union_(polygons_with_correct_center, part); } } - 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(); + // Increase the area again, to ensure the nozzle path when calculated later is very similar to the one assumed above. + linear_inserts[idx] = offset(polygons_with_correct_center, m_config.support_line_width / 2); + linear_inserts[idx] = diff(linear_inserts[idx], m_volumes.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist)); } } } - if (idx % progress_inserts_check_interval == 0) - { - { - 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); - } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + if (idx % progress_inserts_check_interval == 0) { + 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 * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); } +#endif } }); // single threaded combining all elements to the right layers. ONLY COPYS DATA! for (coord_t i = 0; i < static_cast(linear_data.size()); i++) - { layer_tree_polygons[linear_data[i].first].emplace(linear_data[i].second, linear_inserts[i]); - } } void TreeSupport::smoothBranchAreas(std::vector>& layer_tree_polygons) { +#ifdef SLIC3R_TREESUPPORTS_PROGRESS double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS; - 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 +#endif // SLIC3R_TREESUPPORTS_PROGRESS + + const coord_t max_radius_change_per_layer = 1 + m_config.support_line_width / 2; // this is the upper limit a radius may change per layer. +1 to avoid rounding errors // smooth upwards - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()) - 1; layer_idx++) - { + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()) - 1; layer_idx++) { 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 tbb::parallel_for(tbb::blocked_range(0, processing.size()), [&](const tbb::blocked_range &range) { - for (const size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { + for (size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { std::pair data_pair = processing[processing_idx]; - - coord_t max_outer_wall_distance = 0; + double max_outer_wall_distance = 0; bool do_something = false; for (SupportElement* parent : data_pair.first->parents) - { - if (config.getRadius(*parent) != config.getCollisionRadius(*parent)) - { + if (m_config.getRadius(*parent) != m_config.getCollisionRadius(*parent)) { do_something = true; - max_outer_wall_distance = std::max(max_outer_wall_distance, vSize(data_pair.first->result_on_layer - parent->result_on_layer) - (config.getRadius(*data_pair.first) - config.getRadius(*parent))); + max_outer_wall_distance = std::max(max_outer_wall_distance, (data_pair.first->result_on_layer - parent->result_on_layer).cast().norm() - (m_config.getRadius(*data_pair.first) - m_config.getRadius(*parent))); } - } max_outer_wall_distance += max_radius_change_per_layer; // As this change is a bit larger than what usually appears, lost radius can be slowly reclaimed over the layers. - if (do_something) - { - Polygons max_allowed_area = data_pair.second.offset(max_outer_wall_distance); + if (do_something) { + Polygons max_allowed_area = offset(data_pair.second, float(max_outer_wall_distance)); for (SupportElement* parent : data_pair.first->parents) - { - if (config.getRadius(*parent) != config.getCollisionRadius(*parent)) - { - update_next[processing_idx].emplace_back(std::pair(parent, layer_tree_polygons[layer_idx + 1][parent].intersection(max_allowed_area))); - } - } + if (m_config.getRadius(*parent) != m_config.getCollisionRadius(*parent)) + update_next[processing_idx].emplace_back(std::pair(parent, intersection(layer_tree_polygons[layer_idx + 1][parent], max_allowed_area))); } } }); for (std::vector> data_vector : update_next) - { for (std::pair data_pair : data_vector) - { layer_tree_polygons[layer_idx + 1][data_pair.first] = data_pair.second; - } - } } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS progress_total += TREE_PROGRESS_SMOOTH_BRANCH_AREAS / 2; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); // It is just assumed that both smoothing loops together are one third of the time spent in this function. This was guessed. As the whole function is only 10%, and the smoothing is hard to predict a progress report in the loop may be not useful. + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); // It is just assumed that both smoothing loops together are one third of the time spent in this function. This was guessed. As the whole function is only 10%, and the smoothing is hard to predict a progress report in the loop may be not useful. +#endif // smooth downwards std::unordered_set updated_last_iteration; - for (LayerIndex layer_idx = layer_tree_polygons.size() - 2; layer_idx >= 0; layer_idx--) - { + for (LayerIndex layer_idx = layer_tree_polygons.size() - 2; layer_idx >= 0; layer_idx--) { 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 tbb::parallel_for(tbb::blocked_range(0, processing.size()), [&](const tbb::blocked_range &range) { - for (const size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { + for (size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { std::pair data_pair = processing[processing_idx]; bool do_something = false; Polygons max_allowed_area; - for (size_t idx = 0; idx < data_pair.first->parents.size(); idx++) - { + for (size_t idx = 0; idx < data_pair.first->parents.size(); ++ idx) { SupportElement* parent = data_pair.first->parents[idx]; coord_t max_outer_line_increase = max_radius_change_per_layer; - Polygons result = layer_tree_polygons[layer_idx + 1][parent].offset(max_outer_line_increase); + Polygons result = offset(layer_tree_polygons[layer_idx + 1][parent], max_outer_line_increase); Point direction = data_pair.first->result_on_layer - parent->result_on_layer; // move the polygons object for (auto& outer : result) - { for (Point& p : outer) - { p += direction; - } - } - max_allowed_area.add(result); - do_something = do_something || updated_last_iteration.count(parent) || config.getCollisionRadius(*parent) != config.getRadius(*parent); + append(max_allowed_area, std::move(result)); + do_something = do_something || updated_last_iteration.count(parent) || m_config.getCollisionRadius(*parent) != m_config.getRadius(*parent); } - if (do_something) - { - Polygons result = max_allowed_area.unionPolygons().intersection(data_pair.second); - if (result.area() < data_pair.second.area()) - { + if (do_something) { + Polygons result = intersection(max_allowed_area, data_pair.second); + if (area(result) < area(data_pair.second)) update_next[processing_idx] = std::pair(data_pair.first, result); - } } } }); updated_last_iteration.clear(); for (std::pair data_pair : update_next) - { - if (data_pair.first != nullptr) - { + if (data_pair.first != nullptr) { updated_last_iteration.emplace(data_pair.first); layer_tree_polygons[layer_idx][data_pair.first] = data_pair.second; } - } } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS progress_total += TREE_PROGRESS_SMOOTH_BRANCH_AREAS / 2; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); +#endif } - -void TreeSupport::dropNonGraciousAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, const std::map& inverse_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) { tbb::parallel_for(tbb::blocked_range(0, linear_data.size()), [&](const tbb::blocked_range &range) { - for (const size_t idx = range.begin(); idx < range.end(); ++ idx) { + for (size_t idx = range.begin(); idx < range.end(); ++ idx) { SupportElement* elem = linear_data[idx].second; bool non_gracious_model_contact = !elem->to_model_gracious && !inverse_tree_order.count(elem); // if a element has no child, it connects to whatever is below as no support further down for it will exist. - - if (non_gracious_model_contact) - { + if (non_gracious_model_contact) { Polygons rest_support = layer_tree_polygons[linear_data[idx].first][elem]; LayerIndex counter = 1; - while (rest_support.area() > 1 && counter < linear_data[idx].first) - { - rest_support = rest_support.difference(volumes_.getCollision(0, linear_data[idx].first - counter)); + while (area(rest_support) > tiny_area_threshold && counter < linear_data[idx].first) { + rest_support = diff(rest_support, m_volumes.getCollision(0, linear_data[idx].first - counter)); dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support); counter++; } @@ -2323,123 +2260,116 @@ void TreeSupport::dropNonGraciousAreas(std::vector& support_layer_storage, std::vector& support_roof_storage, SliceDataStorage& storage) +void TreeSupport::finalizeInterfaceAndSupportAreas(const PrintObject &print_object, std::vector& support_layer_storage, std::vector& support_roof_storage) { - InterfacePreference interface_pref = config.interface_preference; // InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; + InterfacePreference interface_pref = m_config.interface_preference; // InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; + +#ifdef SLIC3R_TREESUPPORTS_PROGRESS double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS + TREE_PROGRESS_SMOOTH_BRANCH_AREAS; +#endif // SLIC3R_TREESUPPORTS_PROGRESS // Iterate over the generated circles in parallel and clean them up. Also add support floor. std::mutex critical_sections; tbb::parallel_for(tbb::blocked_range(0, support_layer_storage.size()), [&](const tbb::blocked_range &range) { - for (const size_t layer_idx = range.begin(); layer_idx < range.end(); ++ 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. + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. + support_layer_storage[layer_idx] = union_(support_layer_storage[layer_idx]); //FIXME .smooth(50); + // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. + support_layer_storage[layer_idx] = polygons_simplify(support_layer_storage[layer_idx], std::min(scaled(0.03), double(m_config.resolution))); // Subtract support lines of the branches from the roof - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(support_roof_storage[layer_idx]); - if (!storage.support.supportLayers[layer_idx].support_roof.empty() && support_layer_storage[layer_idx].intersection(storage.support.supportLayers[layer_idx].support_roof).area() > 1) - { - switch (interface_pref) - { + storage.support.supportLayers[layer_idx].support_roof = union_(storage.support.supportLayers[layer_idx].support_roof, support_roof_storage[layer_idx]); + if (!storage.support.supportLayers[layer_idx].support_roof.empty() && + area(intersection(support_layer_storage[layer_idx], storage.support.supportLayers[layer_idx].support_roof)) > tiny_area_threshold) { + switch (interface_pref) { case InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT: - support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(storage.support.supportLayers[layer_idx].support_roof); + support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], storage.support.supportLayers[layer_idx].support_roof); break; 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]); + storage.support.supportLayers[layer_idx].support_roof = diff(storage.support.supportLayers[layer_idx].support_roof, support_layer_storage[layer_idx]); break; +//FIXME +#if 0 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); + // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. + Polygons interface_lines = offset(to_polylines( + generateSupportInfillLines(storage.support.supportLayers[layer_idx].support_roof, true, layer_idx, m_config.support_roof_line_distance)), + m_config.support_roof_line_width / 2); + support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], interface_lines); + break; } - break; 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)); - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.difference(tree_lines); // do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. + // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. + Polygons tree_lines = union_(offset(to_polylines( + generateSupportInfillLines(support_layer_storage[layer_idx], false, layer_idx, m_config.support_line_distance, true)), + m_config.support_line_width / 2)); + // do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. + storage.support.supportLayers[layer_idx].support_roof = diff(storage.support.supportLayers[layer_idx].support_roof, tree_lines); + break; } - - break; +#endif case InterfacePreference::NOTHING: break; } } // Subtract support floors from the support area and add them to the support floor instead. - if (config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) - { + if (m_config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) { Polygons floor_layer = storage.support.supportLayers[layer_idx].support_bottom; - Polygons layer_outset = support_layer_storage[layer_idx].offset(config.support_bottom_offset).difference(volumes_.getCollision(0, layer_idx, false)); + Polygons layer_outset = diff(offset(support_layer_storage[layer_idx], m_config.support_bottom_offset), m_volumes.getCollision(0, layer_idx, false)); size_t layers_below = 0; - while (layers_below <= config.support_bottom_layers) - { - // one sample at 0 layers below, another at config.support_bottom_layers. In-between samples at config.performance_interface_skip_layers distance from each other. - const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(config.z_distance_bottom_layers))); - constexpr bool no_support = false; - constexpr bool no_prime_tower = false; - floor_layer.add(layer_outset.intersection(storage.getLayerOutlines(sample_layer, no_support, no_prime_tower))); - if (layers_below < config.support_bottom_layers) - { - layers_below = std::min(layers_below + config.performance_interface_skip_layers, config.support_bottom_layers); - } + while (layers_below <= m_config.support_bottom_layers) { + // one sample at 0 layers below, another at m_config.support_bottom_layers. In-between samples at m_config.performance_interface_skip_layers distance from each other. + const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(m_config.z_distance_bottom_layers))); + //FIXME subtract the wipe tower + append(floor_layer, intersection(layer_outset, layer_overhangs(*print_object.get_layer(sample_layer)))); + if (layers_below < m_config.support_bottom_layers) + layers_below = std::min(layers_below + m_config.performance_interface_skip_layers, m_config.support_bottom_layers); else - { break; - } } - floor_layer = floor_layer.unionPolygons(); - storage.support.supportLayers[layer_idx].support_bottom = storage.support.supportLayers[layer_idx].support_bottom.unionPolygons(floor_layer); - support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(floor_layer.offset(10)); // Subtract the support floor from the normal support. + floor_layer = union_(floor_layer); + storage.support.supportLayers[layer_idx].support_bottom = union_(storage.support.supportLayers[layer_idx].support_bottom, floor_layer); + support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], offset(floor_layer, scaled(0.01))); // Subtract the support floor from the normal support. } - for (PolygonsPart part : support_layer_storage[layer_idx].splitIntoParts(true)) // Convert every part into a PolygonsPart for the support. - { - storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(part, config.support_line_width, config.support_wall_count); - } + for (const ExPolygon &part : union_ex(support_layer_storage[layer_idx])) // Convert every part into a PolygonsPart for the support. + storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(part, m_config.support_line_width, m_config.support_wall_count); +#ifdef SLIC3R_TREESUPPORTS_PROGRESS { 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); + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); } - +#endif +#if 0 { 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)); - } } +#endif } }); } -void TreeSupport::drawAreas(std::vector>& move_bounds, SliceDataStorage& storage) +void TreeSupport::drawAreas(PrintObject &print_object, std::vector> &move_bounds) { std::vector support_layer_storage(move_bounds.size()); std::vector support_roof_storage(move_bounds.size()); std::map inverese_tree_order; // in the tree structure only the parents can be accessed. Inverse this to be able to access the children. std::vector> linear_data; // All SupportElements are put into a layer independent storage to improve parallelization. Was added at a point in time where this function had performance issues. // These were fixed by creating less initial points, but i do not see a good reason to remove a working performance optimization. - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); layer_idx++) - { - for (SupportElement* elem : move_bounds[layer_idx]) - { + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { + for (SupportElement* elem : move_bounds[layer_idx]) { if ((layer_idx > 0 && ((!inverese_tree_order.count(elem) && elem->target_height == layer_idx) || (inverese_tree_order.count(elem) && inverese_tree_order[elem]->result_on_layer == Point(-1, -1))))) // we either come from nowhere at the final layer or we had invalid parents 2. should never happen but just to be sure - { continue; - } - for (SupportElement* par : elem->parents) - { - if (par->result_on_layer == Point(-1, -1)) - { - continue; - } - inverese_tree_order.emplace(par, elem); - } + if (par->result_on_layer != Point(-1, -1)) + inverese_tree_order.emplace(par, elem); linear_data.emplace_back(layer_idx, elem); } } @@ -2458,30 +2388,15 @@ void TreeSupport::drawAreas(std::vector>& move_bounds, auto t_drop = std::chrono::high_resolution_clock::now(); // single threaded combining all dropped down support areas to the right layers. ONLY COPYS DATA! for (coord_t i = 0; i < static_cast(dropped_down_areas.size()); i++) - { for (std::pair pair : dropped_down_areas[i]) - { - support_layer_storage[pair.first].add(pair.second); - } - } + append(support_layer_storage[pair.first], std::move(pair.second)); // single threaded combining all support areas to the right layers. ONLY COPYS DATA! - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()); layer_idx++) - { + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()); ++ layer_idx) for (std::pair data_pair : layer_tree_polygons[layer_idx]) - { - if (data_pair.first->missing_roof_layers > data_pair.first->distance_to_top) - { - support_roof_storage[layer_idx].add(data_pair.second); - } - else - { - support_layer_storage[layer_idx].add(data_pair.second); - } - } - } + append(data_pair.first->missing_roof_layers > data_pair.first->distance_to_top ? support_roof_storage[layer_idx] : support_layer_storage[layer_idx], std::move(data_pair.second)); - finalizeInterfaceAndSupportAreas(support_layer_storage, support_roof_storage, storage); + finalizeInterfaceAndSupportAreas(print_object, support_layer_storage, support_roof_storage); auto t_end = std::chrono::high_resolution_clock::now(); auto dur_gen_tips = 0.001 * std::chrono::duration_cast(t_generate - t_start).count(); @@ -2489,9 +2404,11 @@ void TreeSupport::drawAreas(std::vector>& move_bounds, auto dur_drop = 0.001 * std::chrono::duration_cast(t_drop - t_smooth).count(); auto dur_finalize = 0.001 * std::chrono::duration_cast(t_end - t_drop).count(); - log("Time used for drawing subfuctions: generateBranchAreas: %.3lf ms smoothBranchAreas: %.3lf ms dropNonGraciousAreas: %.3lf ms finalizeInterfaceAndSupportAreas %.3lf ms\n", dur_gen_tips, dur_smooth, dur_drop, dur_finalize); + BOOST_LOG_TRIVIAL(info) << + "Time used for drawing subfuctions: generateBranchAreas: " << dur_gen_tips << " ms " + "smoothBranchAreas: " << dur_smooth << " ms " + "dropNonGraciousAreas: " << dur_drop << " ms " + "finalizeInterfaceAndSupportAreas " << dur_finalize << " ms"; } - - } // namespace Slic3r diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 04f0eb5b1..cf976b76a 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -10,6 +10,7 @@ #define slic3r_TreeSupport_hpp #include "TreeModelVolumes.hpp" +#include "Point.hpp" #include // For combining hashes @@ -17,18 +18,19 @@ #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 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 +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + // The various stages of the process can be weighted differently in the progress bar. + // These weights are obtained experimentally using a small sample size. Sensible weights can differ drastically based on the assumed default settings and model. + #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 +#endif // SLIC3R_TREESUPPORTS_PROGRESS #define SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL false #define SUPPORT_TREE_AVOID_SUPPORT_BLOCKER true @@ -38,16 +40,15 @@ #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 Slic3r { using LayerIndex = size_t; //FIXME -class SierpinskiFillProvider; - +class Print; +class PrintObject; +class SliceDataStorage; /*! * \brief Generates a tree structure to support your models. @@ -55,7 +56,7 @@ class SierpinskiFillProvider; class TreeSupport { - public: +public: using AvoidanceType = TreeModelVolumes::AvoidanceType; enum class InterfacePreference { @@ -68,10 +69,8 @@ class TreeSupport /*! * \brief Creates an instance of the tree support generator. - * - * \param storage The data storage to get global settings from. */ - TreeSupport(const SliceDataStorage& storage); + TreeSupport(); /*! * \brief Create the areas that need support. @@ -80,7 +79,7 @@ class TreeSupport * \param storage The data storage where the mesh data is gotten from and * where the resulting support areas are stored. */ - void generateSupportAreas(SliceDataStorage& storage); + void generateSupportAreas(Print &print, const BuildVolume &build_volume, std::vector& print_object_ids); //todo Remove! Only relevant for public BETA! @@ -92,29 +91,28 @@ class TreeSupport struct AreaIncreaseSettings { - AreaIncreaseSettings() : type(AvoidanceType::FAST), increase_speed(0), increase_radius(false), no_error(false), use_min_distance(false), move(false) - { - } - - AreaIncreaseSettings(AvoidanceType type, coord_t increase_speed, bool increase_radius, bool simplify, bool use_min_distance, bool move) : type(type), increase_speed(increase_speed), increase_radius(increase_radius), no_error(simplify), use_min_distance(use_min_distance), move(move) - { - } - - AvoidanceType type; - coord_t increase_speed; - bool increase_radius; - bool no_error; - bool use_min_distance; - bool move; + AvoidanceType type { AvoidanceType::FAST }; + coord_t increase_speed { 0 }; + bool increase_radius { false }; + bool no_error { false }; + bool use_min_distance { false }; + bool move { false }; bool operator==(const AreaIncreaseSettings& other) const { - return increase_radius == other.increase_radius && increase_speed == other.increase_speed && type == other.type && no_error == other.no_error && use_min_distance == other.use_min_distance && move == other.move; + return increase_radius == other.increase_radius && increase_speed == other.increase_speed && type == other.type && + no_error == other.no_error && use_min_distance == other.use_min_distance && move == other.move; } }; struct SupportElement { - 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) + 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) { } @@ -205,7 +203,13 @@ class TreeSupport // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. - last_area_increase = AreaIncreaseSettings(std::min(first.last_area_increase.type, second.last_area_increase.type), std::min(first.last_area_increase.increase_speed, second.last_area_increase.increase_speed), first.last_area_increase.increase_radius || second.last_area_increase.increase_radius, first.last_area_increase.no_error || second.last_area_increase.no_error, first.last_area_increase.use_min_distance && second.last_area_increase.use_min_distance, first.last_area_increase.move || second.last_area_increase.move); + last_area_increase = { + std::min(first.last_area_increase.type, second.last_area_increase.type), + std::min(first.last_area_increase.increase_speed, second.last_area_increase.increase_speed), + first.last_area_increase.increase_radius || second.last_area_increase.increase_radius, + first.last_area_increase.no_error || second.last_area_increase.no_error, + first.last_area_increase.use_min_distance && second.last_area_increase.use_min_distance, + first.last_area_increase.move || second.last_area_increase.move }; } /*! @@ -326,10 +330,7 @@ class TreeSupport if (*this == other) return false; if (other.target_height != target_height) - { return other.target_height < target_height; - } - return other.target_position.x() == target_position.x() ? other.target_position.y() < target_position.y() : other.target_position.x() < target_position.x(); } @@ -379,11 +380,10 @@ class TreeSupport roof_pattern(mesh_group_settings.support_roof_pattern), support_pattern(mesh_group_settings.support_pattern), support_roof_line_width(mesh_group_settings.support_roof_line_width), - support_line_distance(mesh_group_settings.support_line_distance), + support_line_spacing(mesh_group_settings.support_line_spacing), support_bottom_offset(mesh_group_settings.support_bottom_offset), support_wall_count(mesh_group_settings.support_wall_count), - maximum_deviation(mesh_group_settings.meshfix_maximum_deviation), - maximum_resolution(mesh_group_settings.meshfix_maximum_resolution), + resolution(mesh_group_settings.resolution), support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. settings(mesh_group_settings), min_feature_size(mesh_group_settings.min_feature_size) @@ -406,28 +406,20 @@ class TreeSupport if (angles.empty()) { if (pattern == SupportMaterialInterfacePattern::smipConcentric) - { angles.push_back(0); // Concentric has no rotation. - } /* else if (pattern == SupportMaterialInterfacePattern::TRIANGLES) - { angles.push_back(90); // Triangular support interface shouldn't alternate every layer. - } */ - else - { - if (TreeSupportSettings::some_model_contains_thick_roof) - { + else { + if (TreeSupportSettings::some_model_contains_thick_roof) { // Some roofs are quite thick. // Alternate between the two kinds of diagonal: / and \ . angles.push_back(M_PI / 4.); angles.push_back(3. * M_PI / 4.); } if (angles.empty()) - { angles.push_back(M_PI / 2.); // Perpendicular to support lines. - } } } }; @@ -437,15 +429,17 @@ class TreeSupport getInterfaceAngles(support_roof_angles, roof_pattern); // 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")); - interface_preference = InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; +//FIXME this was the default +// interface_preference = InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; + interface_preference = InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE; } - private: + private: double angle; double angle_slow; std::vector known_z; - public: + public: // some static variables dependent on other meshes that are not currently processed. // Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy. inline static bool some_model_contains_thick_roof = false; @@ -565,7 +559,7 @@ class TreeSupport /*! * \brief Distance between support infill lines. */ - coord_t support_line_distance; + coord_t support_line_spacing; /*! * \brief Offset applied to the support floor area. */ @@ -577,11 +571,7 @@ class TreeSupport /* * \brief Maximum allowed deviation when simplifying. */ - coord_t maximum_deviation; - /* - * \brief Maximum allowed resolution (length of a line segment) when simplifying. The resolution is higher when this variable is smaller => Minimum size a line segment may have. - */ - coord_t maximum_resolution; + coord_t resolution; /* * \brief Distance between the lines of the roof. */ @@ -607,10 +597,10 @@ class TreeSupport 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_xy_overrides_z == other.support_xy_overrides_z && 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_xy_overrides_z == other.support_xy_overrides_z && support_line_spacing == other.support_line_spacing && support_roof_line_width == other.support_roof_line_width && // can not be set on a per-mesh basis currently, so code to enable processing different roof line width in the same iteration seems useless. support_bottom_offset == other.support_bottom_offset && support_wall_count == other.support_wall_count && support_pattern == other.support_pattern && roof_pattern == other.roof_pattern && // can not be set on a per-mesh basis currently, so code to enable processing different roof patterns in the same iteration seems useless. - support_roof_angles == other.support_roof_angles && support_infill_angles == other.support_infill_angles && increase_radius_until_radius == other.increase_radius_until_radius && support_bottom_layers == other.support_bottom_layers && layer_height == other.layer_height && z_distance_top_layers == other.z_distance_top_layers && 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 && interface_preference == other.interface_preference + support_roof_angles == other.support_roof_angles && support_infill_angles == other.support_infill_angles && increase_radius_until_radius == other.increase_radius_until_radius && support_bottom_layers == other.support_bottom_layers && layer_height == other.layer_height && z_distance_top_layers == other.z_distance_top_layers && resolution == other.resolution && // Infill generation depends on deviation and resolution. + support_roof_line_distance == other.support_roof_line_distance && interface_preference == other.interface_preference && min_feature_size == other.min_feature_size // interface_preference should be identical to ensure the tree will correctly interact with the roof. // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry #if 0 @@ -711,109 +701,7 @@ class TreeSupport } }; - private: - enum class LineStatus - { - INVALID, - TO_MODEL, - TO_MODEL_GRACIOUS, - TO_MODEL_GRACIOUS_SAFE, - TO_BP, - TO_BP_SAFE - }; - - using LineInformation = std::vector>; - - - /*! - * \brief Precalculates all avoidances, that could be required. - * - * \param storage[in] Background storage to access meshes. - * \param currently_processing_meshes[in] Indexes of all meshes that are processed in this iteration - */ - void precalculate(const SliceDataStorage& storage, std::vector currently_processing_meshes); - /*! - * \brief Converts a Polygons object representing a line into the internal format. - * - * \param polylines[in] The Polyline that will be converted. - * \param layer_idx[in] The current layer. - * \return All lines of the \p polylines object, with information for each point regarding in which avoidance it is currently valid in. - */ - std::vector convertLinesToInternal(Polygons polylines, LayerIndex layer_idx); - /*! - * \brief Converts lines in internal format into a Polygons object representing these lines. - * - * \param lines[in] The lines that will be converted. - * \return All lines of the \p lines object as a Polygons object. - */ - Polygons convertInternalToLines(std::vector lines); - /*! - * \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. - */ - std::function)> getEvaluatePointForNextLayerFunction(size_t current_layer); - /*! - * \brief Evaluates which points of some lines are not valid one layer below and which are. Assumes all points are valid on the current layer. Validity is evaluated using supplied lambda. - * - * \param lines[in] The lines that have to be evaluated. - * \param evaluatePoint[in] The function used to evaluate the points. - * \return A pair with which points are still valid in the first slot and which are not in the second slot. - */ - 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. - * - * \param poly[in] The Polygons object, of which its lines should be extended. - * \return A Polygons object with implicit line from the last vertex of a Polygon to the first one added. - */ - Polygons toPolylines(const Polygons& poly) const; - - - /*! - * \brief Returns Polylines representing the (infill) lines that will result in slicing the given area - * - * \param area[in] The area that has to be filled with infill. - * \param roof[in] Whether the roofing or regular support settings should be used. - * \param layer_idx[in] The current layer index. - * \param support_infill_distance[in] The distance that should be between the infill lines. - * \param cross_fill_provider[in] A SierpinskiFillProvider required for cross infill. - * - * \return A Polygons object that represents the resulting infill lines. - */ - Polygons generateSupportInfillLines(const Polygons& area, bool roof, LayerIndex layer_idx, coord_t support_infill_distance, SierpinskiFillProvider* cross_fill_provider = nullptr, bool include_walls = false); - - /*! - * \brief Unions two Polygons. Ensures that if the input is non empty that the output also will be non empty. - * \param first[in] The first Polygon. - * \param second[in] The second Polygon. - * \return The union of both Polygons - */ - [[nodiscard]] Polygons safeUnion(const Polygons first, const Polygons second = Polygons()) const; - - /*! - * \brief Creates a valid CrossInfillProvider - * Based on AreaSupport::precomputeCrossInfillTree, but calculates for each mesh separately - * \param mesh[in] The mesh that is currently processed. - * \param line_distance[in] The distance between the infill lines of the resulting infill - * \param line_width[in] What is the width of a line used in the infill. - * \return A valid CrossInfillProvider. Has to be freed manually to avoid a memory leak. - */ - SierpinskiFillProvider* generateCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width); - - +private: /*! * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. * @@ -823,53 +711,7 @@ class TreeSupport * \param move_bounds[out] Storage for the influence areas. * \param storage[in] Background storage, required for adding roofs. */ - 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_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. - */ - [[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; - - - /*! - * \brief Merges Influence Areas if possible. - * - * Branches which do overlap have to be merged. This helper merges all elements in input with the elements into reduced_new_layer. - * Elements in input_aabb are merged together if possible, while elements reduced_new_layer_aabb are not checked against each other. - * - * \param reduced_aabb[in,out] The already processed elements. - * \param input_aabb[in] Not yet processed elements - * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. Value is the influence area where the center of a circle of support may be placed. - * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. - * Value is the influence area where the center of a circle of support may be placed. - * \param influence_areas[in] The influence areas without avoidance removed. - * \param insert_bp_areas[out] Elements to be inserted into the main dictionary after the Helper terminates. - * \param insert_model_areas[out] Elements to be inserted into the secondary dictionary after the Helper terminates. - * \param insert_influence[out] Elements to be inserted into the dictionary containing the largest possibly valid influence area (ignoring if the area may not be there because of avoidance) - * \param erase[out] Elements that should be deleted from the above dictionaries. - * \param layer_idx[in] The Index of the current Layer. - */ - void mergeHelper(std::map& reduced_aabb, std::map& input_aabb, const std::unordered_map& to_bp_areas, const std::unordered_map& to_model_areas, const std::map& influence_areas, std::unordered_map& insert_bp_areas, std::unordered_map& insert_model_areas, std::unordered_map& insert_influence, std::vector& erase, const LayerIndex layer_idx); - /*! - * \brief Merges Influence Areas if possible. - * - * Branches which do overlap have to be merged. This manages the helper and uses a divide and conquer approach to parallelize this problem. This parallelization can at most accelerate the merging by a factor of 2. - * - * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. - * Value is the influence area where the center of a circle of support may be placed. - * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. - * Value is the influence area where the center of a circle of support may be placed. - * \param influence_areas[in] The Elements of the current Layer without avoidances removed. This is the largest possible influence area for this layer. - * Value is the influence area where the center of a circle of support may be placed. - * \param layer_idx[in] The current layer. - */ - void mergeInfluenceAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, LayerIndex layer_idx); + void generateInitialAreas(const PrintObject &print_object, std::vector> &move_bounds); /*! * \brief Checks if an influence area contains a valid subsection and returns the corresponding metadata and the new Influence area. @@ -969,7 +811,6 @@ class TreeSupport */ void dropNonGraciousAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, const std::map& inverse_tree_order); - /*! * \brief Generates Support Floor, ensures Support Roof can not cut of branches, and saves the branches as support to storage * @@ -977,7 +818,7 @@ class TreeSupport * \param support_roof_storage[in] Areas where support was replaced with roof. * \param storage[in,out] The storage where the support should be stored. */ - void finalizeInterfaceAndSupportAreas(std::vector& support_layer_storage, std::vector& support_roof_storage, SliceDataStorage& storage); + void finalizeInterfaceAndSupportAreas(const PrintObject &print_object, std::vector& support_layer_storage, std::vector& support_roof_storage); /*! * \brief Draws circles around result_on_layer points of the influence areas and applies some post processing. @@ -985,36 +826,36 @@ class TreeSupport * \param move_bounds[in] All currently existing influence areas * \param storage[in,out] The storage where the support should be stored. */ - void drawAreas(std::vector>& move_bounds, SliceDataStorage& storage); + void drawAreas(PrintObject &print_object, std::vector>& move_bounds); /*! * \brief Settings with the indexes of meshes that use these settings. * */ - std::vector>> grouped_meshes; + std::vector>> m_grouped_meshes; /*! * \brief Generator for model collision, avoidance and internal guide volumes. * */ - TreeModelVolumes volumes_; + TreeModelVolumes m_volumes; /*! * \brief Contains config settings to avoid loading them in every function. This was done to improve readability of the code. */ - TreeSupportSettings config; + TreeSupportSettings m_config; /*! * \brief The progress multiplier of all values added progress bar. * Required for the progress bar the behave as expected when areas have to be calculated multiple times */ - double progress_multiplier = 1; + double m_progress_multiplier = 1; /*! * \brief The progress offset added to all values communicated to the progress bar. * Required for the progress bar the behave as expected when areas have to be calculated multiple times */ - double progress_offset = 0; + double m_progress_offset = 0; }; @@ -1027,7 +868,7 @@ struct hash { size_t operator()(const Slic3r::TreeSupport::SupportElement& node) const { - size_t hash_node = hash()(node.target_position); + size_t hash_node = Slic3r::PointHash{}(node.target_position); boost::hash_combine(hash_node, size_t(node.target_height)); return hash_node; } From 9e6871e5b865209552efb77097e88ba9ef453a2a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 27 Jul 2022 10:41:11 +0200 Subject: [PATCH 09/29] WIP Tree supports: It compiles, but unfinished, missing pieces. --- src/libslic3r/PrintObject.cpp | 2 +- src/libslic3r/SupportMaterial.cpp | 20 +-- src/libslic3r/SupportMaterial.hpp | 2 +- src/libslic3r/TreeModelVolumes.cpp | 2 +- src/libslic3r/TreeModelVolumes.hpp | 4 +- src/libslic3r/TreeSupport.cpp | 197 ++++++++++++++++++++--------- src/libslic3r/TreeSupport.hpp | 35 ++++- 7 files changed, 181 insertions(+), 81 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 29cccd24e..030909641 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2130,7 +2130,7 @@ void PrintObject::_generate_support_material() { if (m_config.support_material_style == smsTree) { TreeSupport tree_support; - tree_support.generateSupportAreas(); + tree_support.generateSupportAreas(*this); } else { PrintObjectSupportMaterial support_material(this, m_slicing_params); support_material.generate(*this); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 330cbaf13..fc42e1f10 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -3912,16 +3912,16 @@ void modulate_extrusion_by_overlapping_layers( } void generate_support_toolpaths( - SupportLayerPtrs &support_layers, - const PrintObjectConfig &config, - const SupportParameters &support_params, - const SlicingParameters &slicing_params, - const SupportGeneratorLayersPtr &raft_layers, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &intermediate_layers, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers) + SupportLayerPtrs &support_layers, + const PrintObjectConfig &config, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers) { // loop_interface_processor with a given circle radius. LoopInterfaceProcessor loop_interface_processor(1.5 * support_params.support_material_interface_flow.scaled_width()); diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index 304252584..703ff384c 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -149,7 +149,7 @@ struct SupportParameters { // Produce the support G-code. // Used by both classic and tree supports. -static void generate_support_toolpaths( +void generate_support_toolpaths( SupportLayerPtrs &support_layers, const PrintObjectConfig &config, const SupportParameters &support_params, diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index ee208c545..6ca20cf08 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -45,7 +45,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr this->layer_height = scaled(config.layer_height.value); this->resolution = scaled(print_config.resolution.value); this->min_feature_size = scaled(config.min_feature_size.value); - this->support_angle = M_PI / 2. - config.support_angle * M_PI / 180.; + this->support_angle = M_PI / 2. - config.support_material_angle * M_PI / 180.; this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width(); this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width(); //FIXME add it to SlicingParameters and reuse in both tree and normal supports? diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 82d1a5d9d..51f748d83 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -372,8 +372,6 @@ private: calculateCollisionHolefree(std::deque{ RadiusLayerPair(key) }); } - static Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision); - /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model. * @@ -584,6 +582,8 @@ private: std::unique_ptr m_critical_progress { std::make_unique() }; }; +static Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision); + } #endif //slic3r_TreeModelVolumes_hpp diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index fb36fbe14..d519164f4 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -7,6 +7,7 @@ // CuraEngine is released under the terms of the AGPLv3 or higher. #include "TreeSupport.hpp" +#include "BuildVolume.hpp" #include "ClipperUtils.hpp" #include "Fill/Fill.hpp" #include "Layer.hpp" @@ -33,7 +34,7 @@ namespace Slic3r static constexpr const auto tiny_area_threshold = sqr(scaled(0.001)); -static [[nodiscard]] std::vector>> group_meshes(const Print &print, std::vector &print_object_ids) +static [[nodiscard]] std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) { std::vector>> grouped_meshes; @@ -162,7 +163,19 @@ LayerIndex precalculate(const Print &print, const TreeSupport::TreeSupportSettin return max_layer; } -void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_volume, std::vector &print_object_ids) +//FIXME this is an ugly wrapper interface for a single print object and a phony build volume. +void TreeSupport::generateSupportAreas(PrintObject& print_object) +{ + size_t idx = 0; + for (PrintObject *po : print_object.print()->objects()) { + if (po == &print_object) + break; + ++ idx; + } + this->generateSupportAreas(*print_object.print(), BuildVolume(Pointfs{ Vec2d{ -1000., -1000. }, Vec2d{ -1000., +1000. }, Vec2d{ +1000., +1000. }, Vec2d{ +1000., -1000. } }, 0.), { idx }); +} + +void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_volume, const std::vector &print_object_ids) { std::vector>> grouped_meshes = group_meshes(print, print_object_ids); if (grouped_meshes.empty()) @@ -198,7 +211,8 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo m_progress_multiplier = 1.0 / double(m_grouped_meshes.size()); m_progress_offset = counter == 0 ? 0 : TREE_PROGRESS_TOTAL * (double(counter) * m_progress_multiplier); #endif // SLIC3R_TREESUPPORT_PROGRESS - m_volumes = TreeModelVolumes(*print.get_object(processing.second.front()), build_volume, m_config.maximum_move_distance, m_config.maximum_move_distance_slow, processing.second.front(), m_progress_multiplier, m_progress_offset, {} /* exclude */); + PrintObject &print_object = *print.get_object(processing.second.front()); + m_volumes = TreeModelVolumes(print_object, build_volume, m_config.maximum_move_distance, m_config.maximum_move_distance_slow, processing.second.front(), m_progress_multiplier, m_progress_offset, /* additional_excluded_areas */{}); // ### Precalculate avoidances, collision etc. size_t num_support_layers = precalculate(print, processing.first, processing.second, m_volumes); @@ -208,8 +222,13 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo std::vector> move_bounds(num_support_layers); // ### Place tips of the support tree + SupportGeneratorLayersPtr bottom_contacts(num_support_layers, nullptr); + SupportGeneratorLayersPtr top_contacts(num_support_layers, nullptr); + SupportGeneratorLayersPtr top_interface_layers(num_support_layers, nullptr); + SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr); + SupportGeneratorLayerStorage layer_storage; for (size_t mesh_idx : processing.second) - generateInitialAreas(*print.get_object(mesh_idx), move_bounds); + generateInitialAreas(*print.get_object(mesh_idx), move_bounds, top_contacts, top_interface_layers, layer_storage); auto t_gen = std::chrono::high_resolution_clock::now(); // ### Propagate the influence areas downwards. @@ -221,7 +240,8 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo auto t_place = std::chrono::high_resolution_clock::now(); // ### draw these points as circles - drawAreas(*print.get_object(processing.second.front()), move_bounds); + drawAreas(*print.get_object(processing.second.front()), move_bounds, + bottom_contacts, top_contacts, intermediate_layers, layer_storage); auto t_draw = std::chrono::high_resolution_clock::now(); auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); @@ -247,6 +267,12 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo } } + // Produce the support G-code. + // Used by both classic and tree supports. + SupportGeneratorLayersPtr raft_layers, interface_layers, base_interface_layers; + generate_support_toolpaths(print_object.support_layers(), print_object.config(), SupportParameters(print_object), print_object.slicing_parameters(), + raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); + ++ counter; } @@ -759,7 +785,22 @@ static [[nodiscard]] Polygons safeOffsetInc(const Polygons& me, coord_t distance return union_(ret); } -void TreeSupport::generateInitialAreas(const PrintObject &print_object, std::vector> &move_bounds) +// Using the std::deque as an allocator. +inline SupportGeneratorLayer& layer_allocate( + std::deque& layer_storage, + SupporLayerType layer_type) +{ + layer_storage.push_back(SupportGeneratorLayer()); + layer_storage.back().layer_type = layer_type; + return layer_storage.back(); +} + +void TreeSupport::generateInitialAreas( + const PrintObject &print_object, + std::vector> &move_bounds, + SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &top_interface_layers, + SupportGeneratorLayerStorage &layer_storage) { Polygon base_circle; const int base_radius = 10; @@ -790,12 +831,12 @@ void TreeSupport::generateInitialAreas(const PrintObject &print_object, std::vec //FIXME size_t num_support_layers = print_object.layer_count(); - std::vector> already_inserted(num_support_layers - z_distance_delta); + std::vector> already_inserted(num_support_layers - z_distance_delta); std::mutex critical_sections; tbb::parallel_for(tbb::blocked_range(1, num_support_layers - z_distance_delta), [this, &print_object, &mesh_config, &mesh_group_settings, &support_params, z_distance_delta, xy_overrides_z, force_tip_to_roof, roof_enabled, support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, - &base_circle, &critical_sections, &already_inserted, + &base_circle, &critical_sections, &top_contacts, &layer_storage, &already_inserted, &move_bounds, &base_radius](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { if (! layer_has_overhangs(*print_object.get_layer(layer_idx + z_distance_delta))) @@ -883,8 +924,10 @@ void TreeSupport::generateInitialAreas(const PrintObject &print_object, std::vec added_roofs = union_(added_roofs); { std::lock_guard critical_section_storage(critical_sections); -//FIXME store top support interface areas or maybe hatch them? -// append(storage.support.supportLayers[insert_layer_idx - dtt_roof_tip].support_roof, added_roofs); + SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::TopContact); + append(l->polygons, std::move(added_roofs)); } } @@ -1023,9 +1066,13 @@ void TreeSupport::generateInitialAreas(const PrintObject &print_object, std::vec { std::lock_guard critical_section_storage(critical_sections); -//FIXME store support areas or hatch the lines? -// for (size_t idx = 0; idx < dtt_roof; idx++) -// append(storage.support.supportLayers[layer_idx - idx].support_roof, added_roofs[idx]); // will be unioned in finalizeInterfaceAndSupportAreas + for (size_t idx = 0; idx < dtt_roof; idx++) { + SupportGeneratorLayer *&l = top_contacts[layer_idx - idx]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::TopContact); + // will be unioned in finalizeInterfaceAndSupportAreas + append(l->polygons, std::move(added_roofs[idx])); + } } if (overhang_lines.empty()) { @@ -1054,8 +1101,10 @@ void TreeSupport::generateInitialAreas(const PrintObject &print_object, std::vec if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part) { // reached buildplate std::lock_guard critical_section_storage(critical_sections); -//FIXME store support roofs -// append(storage.support.supportLayers[0].support_roof, overhang_outset); + SupportGeneratorLayer*& l = top_contacts[0]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::TopContact); + append(l->polygons, std::move(overhang_outset)); } else // normal trees have to be generated addLinesAsInfluenceAreas(overhang_lines, force_tip_to_roof ? support_roof_layers - dtt_roof : 0, layer_idx - dtt_roof, dtt_roof > 0, roof_enabled ? support_roof_layers - dtt_roof : 0); } @@ -1107,7 +1156,7 @@ static unsigned int moveInside(const Polygons &polygons, Point &from, int distan ret = x; else { Vec2d abd = ab.cast(); - Vec2d p1p2 = p1 - p0; + Vec2d p1p2 = (p1 - p0).cast(); double lab = abd.norm(); double lp1p2 = p1p2.norm(); // inward direction irrespective of sign of [distance] @@ -1661,7 +1710,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to for (AreaIncreaseSettings settings : order) { new_order.emplace_back(settings); - new_order.emplace_back(settings.type, settings.increase_speed, settings.increase_radius, settings.no_error, use_min_radius, settings.move); + new_order.push_back({ settings.type, settings.increase_speed, settings.increase_radius, settings.no_error, use_min_radius, settings.move }); } order = new_order; } @@ -2260,7 +2309,15 @@ void TreeSupport::dropNonGraciousAreas( }); } -void TreeSupport::finalizeInterfaceAndSupportAreas(const PrintObject &print_object, std::vector& support_layer_storage, std::vector& support_roof_storage) +void TreeSupport::finalizeInterfaceAndSupportAreas( + const PrintObject &print_object, + std::vector &support_layer_storage, + std::vector &support_roof_storage, + + SupportGeneratorLayersPtr &bottom_contacts, + SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage) { InterfacePreference interface_pref = m_config.interface_preference; // InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; @@ -2278,46 +2335,57 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(const PrintObject &print_obje // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. support_layer_storage[layer_idx] = polygons_simplify(support_layer_storage[layer_idx], std::min(scaled(0.03), double(m_config.resolution))); // Subtract support lines of the branches from the roof - storage.support.supportLayers[layer_idx].support_roof = union_(storage.support.supportLayers[layer_idx].support_roof, support_roof_storage[layer_idx]); - if (!storage.support.supportLayers[layer_idx].support_roof.empty() && - area(intersection(support_layer_storage[layer_idx], storage.support.supportLayers[layer_idx].support_roof)) > tiny_area_threshold) { - switch (interface_pref) { - case InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT: - support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], storage.support.supportLayers[layer_idx].support_roof); - break; - case InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE: - storage.support.supportLayers[layer_idx].support_roof = diff(storage.support.supportLayers[layer_idx].support_roof, support_layer_storage[layer_idx]); - break; -//FIXME -#if 0 - case InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT: - { - // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. - Polygons interface_lines = offset(to_polylines( - generateSupportInfillLines(storage.support.supportLayers[layer_idx].support_roof, true, layer_idx, m_config.support_roof_line_distance)), - m_config.support_roof_line_width / 2); - support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], interface_lines); - break; + SupportGeneratorLayer*& support_roof = top_contacts[layer_idx]; + if (! support_roof_storage[layer_idx].empty() || support_roof != nullptr) { + if (support_roof == nullptr) { + support_roof = &layer_allocate(layer_storage, SupporLayerType::TopContact); + support_roof->polygons = union_(support_roof_storage[layer_idx]); + } else + support_roof->polygons = union_(support_roof->polygons, support_roof_storage[layer_idx]); + + if (! support_roof->polygons.empty() && + area(intersection(support_layer_storage[layer_idx], support_roof->polygons)) > tiny_area_threshold) { + switch (interface_pref) { + case InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT: + support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], support_roof->polygons); + break; + case InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE: + support_roof->polygons = diff(support_roof->polygons, support_layer_storage[layer_idx]); + break; + //FIXME + #if 0 + case InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT: + { + // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. + Polygons interface_lines = offset(to_polylines( + generateSupportInfillLines(support_roof->polygons, true, layer_idx, m_config.support_roof_line_distance)), + m_config.support_roof_line_width / 2); + support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], interface_lines); + break; + } + case InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE: + { + // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. + Polygons tree_lines = union_(offset(to_polylines( + generateSupportInfillLines(support_layer_storage[layer_idx], false, layer_idx, m_config.support_line_distance, true)), + m_config.support_line_width / 2)); + // do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. + support_roof->polygons = diff(support_roof->polygons, tree_lines); + break; + } + #endif + case InterfacePreference::NOTHING: + break; } - case InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE: - { - // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. - Polygons tree_lines = union_(offset(to_polylines( - generateSupportInfillLines(support_layer_storage[layer_idx], false, layer_idx, m_config.support_line_distance, true)), - m_config.support_line_width / 2)); - // do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. - storage.support.supportLayers[layer_idx].support_roof = diff(storage.support.supportLayers[layer_idx].support_roof, tree_lines); - break; - } -#endif - case InterfacePreference::NOTHING: - break; } } // Subtract support floors from the support area and add them to the support floor instead. if (m_config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) { - Polygons floor_layer = storage.support.supportLayers[layer_idx].support_bottom; + SupportGeneratorLayer*& support_bottom = bottom_contacts[layer_idx]; + if (support_bottom == nullptr) + support_bottom = &layer_allocate(layer_storage, SupporLayerType::BottomContact); + Polygons floor_layer = std::move(support_bottom->polygons); Polygons layer_outset = diff(offset(support_layer_storage[layer_idx], m_config.support_bottom_offset), m_volumes.getCollision(0, layer_idx, false)); size_t layers_below = 0; while (layers_below <= m_config.support_bottom_layers) { @@ -2330,13 +2398,16 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(const PrintObject &print_obje else break; } - floor_layer = union_(floor_layer); - storage.support.supportLayers[layer_idx].support_bottom = union_(storage.support.supportLayers[layer_idx].support_bottom, floor_layer); - support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], offset(floor_layer, scaled(0.01))); // Subtract the support floor from the normal support. + support_bottom->polygons = union_(floor_layer); + support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], offset(support_bottom->polygons, scaled(0.01))); // Subtract the support floor from the normal support. } - for (const ExPolygon &part : union_ex(support_layer_storage[layer_idx])) // Convert every part into a PolygonsPart for the support. - storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(part, m_config.support_line_width, m_config.support_wall_count); + { + SupportGeneratorLayer *&l = intermediate_layers[layer_idx]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::Base); + append(l->polygons, union_(support_layer_storage[layer_idx])); + } #ifdef SLIC3R_TREESUPPORTS_PROGRESS { @@ -2356,7 +2427,14 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(const PrintObject &print_obje }); } -void TreeSupport::drawAreas(PrintObject &print_object, std::vector> &move_bounds) +void TreeSupport::drawAreas( + PrintObject &print_object, + std::vector> &move_bounds, + + SupportGeneratorLayersPtr &bottom_contacts, + SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage) { std::vector support_layer_storage(move_bounds.size()); std::vector support_roof_storage(move_bounds.size()); @@ -2396,7 +2474,8 @@ void TreeSupport::drawAreas(PrintObject &print_object, std::vector data_pair : layer_tree_polygons[layer_idx]) append(data_pair.first->missing_roof_layers > data_pair.first->distance_to_top ? support_roof_storage[layer_idx] : support_layer_storage[layer_idx], std::move(data_pair.second)); - finalizeInterfaceAndSupportAreas(print_object, support_layer_storage, support_roof_storage); + finalizeInterfaceAndSupportAreas(print_object, support_layer_storage, support_roof_storage, + bottom_contacts, top_contacts, intermediate_layers, layer_storage); auto t_end = std::chrono::high_resolution_clock::now(); auto dur_gen_tips = 0.001 * std::chrono::duration_cast(t_generate - t_start).count(); diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index cf976b76a..a2fc49f01 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -48,8 +48,9 @@ using LayerIndex = size_t; //FIXME class Print; class PrintObject; -class SliceDataStorage; - +class SupportGeneratorLayer; +using SupportGeneratorLayerStorage = std::deque; +using SupportGeneratorLayersPtr = std::vector; /*! * \brief Generates a tree structure to support your models. */ @@ -70,7 +71,7 @@ public: /*! * \brief Creates an instance of the tree support generator. */ - TreeSupport(); + TreeSupport() = default; /*! * \brief Create the areas that need support. @@ -79,7 +80,8 @@ public: * \param storage The data storage where the mesh data is gotten from and * where the resulting support areas are stored. */ - void generateSupportAreas(Print &print, const BuildVolume &build_volume, std::vector& print_object_ids); + void generateSupportAreas(Print &print, const BuildVolume &build_volume, const std::vector& print_object_ids); + void generateSupportAreas(PrintObject &print_object); //todo Remove! Only relevant for public BETA! @@ -711,7 +713,11 @@ private: * \param move_bounds[out] Storage for the influence areas. * \param storage[in] Background storage, required for adding roofs. */ - void generateInitialAreas(const PrintObject &print_object, std::vector> &move_bounds); + void generateInitialAreas(const PrintObject &print_object, + std::vector> &move_bounds, + SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &top_interface_layers, + SupportGeneratorLayerStorage &layer_storage); /*! * \brief Checks if an influence area contains a valid subsection and returns the corresponding metadata and the new Influence area. @@ -818,7 +824,15 @@ private: * \param support_roof_storage[in] Areas where support was replaced with roof. * \param storage[in,out] The storage where the support should be stored. */ - void finalizeInterfaceAndSupportAreas(const PrintObject &print_object, std::vector& support_layer_storage, std::vector& support_roof_storage); + void finalizeInterfaceAndSupportAreas( + const PrintObject &print_object, + std::vector &support_layer_storage, + std::vector &support_roof_storage, + + SupportGeneratorLayersPtr &bottom_contacts, + SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage); /*! * \brief Draws circles around result_on_layer points of the influence areas and applies some post processing. @@ -826,7 +840,14 @@ private: * \param move_bounds[in] All currently existing influence areas * \param storage[in,out] The storage where the support should be stored. */ - void drawAreas(PrintObject &print_object, std::vector>& move_bounds); + void drawAreas( + PrintObject &print_object, + std::vector> &move_bounds, + + SupportGeneratorLayersPtr &bottom_contacts, + SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage); /*! * \brief Settings with the indexes of meshes that use these settings. From 5868028a7e6184d5db9bba43baab1671750773e6 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 29 Jul 2022 13:15:01 +0200 Subject: [PATCH 10/29] WIP Tree supports: It compiles and it produced first trees. --- src/libslic3r/PrintConfig.cpp | 3 +- src/libslic3r/SupportMaterial.cpp | 142 +++++++++++++------------ src/libslic3r/SupportMaterial.hpp | 9 ++ src/libslic3r/TreeModelVolumes.cpp | 65 ++++++------ src/libslic3r/TreeSupport.cpp | 165 +++++++++++++++++++---------- src/libslic3r/TreeSupport.hpp | 9 +- 6 files changed, 235 insertions(+), 158 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index bab9109b7..cd180e530 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -137,7 +137,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern) static const t_config_enum_values s_keys_map_SupportMaterialStyle { { "grid", smsGrid }, - { "snug", smsSnug } + { "snug", smsSnug }, + { "tree", smsTree } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index fc42e1f10..96b756439 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -573,71 +573,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // intermediate_layers.clear(); // interface_layers.clear(); - // Install support layers into the object. - // A support layer installed on a PrintObject has a unique print_z. - SupportGeneratorLayersPtr layers_sorted; - layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); - layers_append(layers_sorted, raft_layers); - layers_append(layers_sorted, bottom_contacts); - layers_append(layers_sorted, top_contacts); - layers_append(layers_sorted, intermediate_layers); - layers_append(layers_sorted, interface_layers); - layers_append(layers_sorted, base_interface_layers); - // Sort the layers lexicographically by a raising print_z and a decreasing height. - std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); - int layer_id = 0; - int layer_id_interface = 0; - assert(object.support_layers().empty()); - for (size_t i = 0; i < layers_sorted.size();) { - // Find the last layer with roughly the same print_z, find the minimum layer height of all. - // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - size_t j = i + 1; - coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; - // Assign an average print_z to the set of layers with nearly equal print_z. - coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); - coordf_t height_min = layers_sorted[i]->height; - bool empty = true; - // For snug supports, layers where the direction of the support interface shall change are accounted for. - size_t num_interfaces = 0; - size_t num_top_contacts = 0; - double top_contact_bottom_z = 0; - for (size_t u = i; u < j; ++u) { - SupportGeneratorLayer &layer = *layers_sorted[u]; - if (! layer.polygons.empty()) { - empty = false; - num_interfaces += one_of(layer.layer_type, support_types_interface); - if (layer.layer_type == SupporLayerType::TopContact) { - ++ num_top_contacts; - assert(num_top_contacts <= 1); - // All top contact layers sharing this print_z shall also share bottom_z. - //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); - top_contact_bottom_z = layer.bottom_z; - } - } - layer.print_z = zavg; - height_min = std::min(height_min, layer.height); - } - if (! empty) { - // Here the upper_layer and lower_layer pointers are left to null at the support layers, - // as they are never used. These pointers are candidates for removal. - bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; - size_t this_layer_id_interface = layer_id_interface; - if (this_layer_contacts_only) { - // Find a supporting layer for its interface ID. - for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) - if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { - // other_layer supports this top contact layer. Assign a different support interface direction to this layer - // from the layer that supports it. - this_layer_id_interface = other_layer.interface_id() + 1; - } - } - object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); - if (num_interfaces && ! this_layer_contacts_only) - ++ layer_id_interface; - } - i = j; - } + generate_support_layers(object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); BOOST_LOG_TRIVIAL(info) << "Support generator - Generating tool paths"; @@ -3911,6 +3847,82 @@ void modulate_extrusion_by_overlapping_layers( extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); } +void generate_support_layers( + PrintObject &object, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers) +{ + // Install support layers into the object. + // A support layer installed on a PrintObject has a unique print_z. + SupportGeneratorLayersPtr layers_sorted; + layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); + layers_append(layers_sorted, raft_layers); + layers_append(layers_sorted, bottom_contacts); + layers_append(layers_sorted, top_contacts); + layers_append(layers_sorted, intermediate_layers); + layers_append(layers_sorted, interface_layers); + layers_append(layers_sorted, base_interface_layers); + // Sort the layers lexicographically by a raising print_z and a decreasing height. + std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); + int layer_id = 0; + int layer_id_interface = 0; + assert(object.support_layers().empty()); + for (size_t i = 0; i < layers_sorted.size();) { + // Find the last layer with roughly the same print_z, find the minimum layer height of all. + // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. + size_t j = i + 1; + coordf_t zmax = layers_sorted[i]->print_z + EPSILON; + for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; + // Assign an average print_z to the set of layers with nearly equal print_z. + coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); + coordf_t height_min = layers_sorted[i]->height; + bool empty = true; + // For snug supports, layers where the direction of the support interface shall change are accounted for. + size_t num_interfaces = 0; + size_t num_top_contacts = 0; + double top_contact_bottom_z = 0; + for (size_t u = i; u < j; ++u) { + SupportGeneratorLayer &layer = *layers_sorted[u]; + if (! layer.polygons.empty()) { + empty = false; + num_interfaces += one_of(layer.layer_type, support_types_interface); + if (layer.layer_type == SupporLayerType::TopContact) { + ++ num_top_contacts; + assert(num_top_contacts <= 1); + // All top contact layers sharing this print_z shall also share bottom_z. + //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); + top_contact_bottom_z = layer.bottom_z; + } + } + layer.print_z = zavg; + height_min = std::min(height_min, layer.height); + } + if (! empty) { + // Here the upper_layer and lower_layer pointers are left to null at the support layers, + // as they are never used. These pointers are candidates for removal. + bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; + size_t this_layer_id_interface = layer_id_interface; + if (this_layer_contacts_only) { + // Find a supporting layer for its interface ID. + for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) + if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { + // other_layer supports this top contact layer. Assign a different support interface direction to this layer + // from the layer that supports it. + this_layer_id_interface = other_layer.interface_id() + 1; + } + } + object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); + if (num_interfaces && ! this_layer_contacts_only) + ++ layer_id_interface; + } + i = j; + } +} + void generate_support_toolpaths( SupportLayerPtrs &support_layers, const PrintObjectConfig &config, diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index 703ff384c..e9baa4dbd 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -147,6 +147,15 @@ struct SupportParameters { bool with_sheath; }; +void generate_support_layers( + PrintObject &object, + const SupportGeneratorLayersPtr &raft_layers, + const SupportGeneratorLayersPtr &bottom_contacts, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &intermediate_layers, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers); + // Produce the support G-code. // Used by both classic and tree supports. void generate_support_toolpaths( diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 6ca20cf08..25d5a3d35 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -80,12 +80,17 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr static Polygons calculateMachineBorderCollision(Polygon machine_border) { - Polygons machine_volume_border; // Put a border of 1m around the print volume so that we don't collide. - append(machine_volume_border, offset(machine_border, scaled(1000.))); +#if 1 + //FIXME just returning no border will let tree support legs collide with print bed boundary + return {}; +#else + //FIXME offsetting by 1000mm easily overflows int32_tr coordinate. + Polygons out = offset(machine_border, scaled(1000.), jtMiter, 1.2); machine_border.reverse(); // Makes the polygon negative so that we subtract the actual volume from the collision area. - machine_volume_border.emplace_back(std::move(machine_border)); - return machine_volume_border; + out.emplace_back(std::move(machine_border)); + return out; +#endif } TreeModelVolumes::TreeModelVolumes( @@ -545,7 +550,6 @@ void TreeModelVolumes::calculateCollision(std::deque keys) const coord_t z_distance_bottom = m_layer_outlines[outline_idx].first.support_bottom_distance; const size_t z_distance_bottom_layers = round_up_divide(z_distance_bottom, layer_height); const coord_t z_distance_top_layers = round_up_divide(m_layer_outlines[outline_idx].first.support_top_distance, layer_height); - const LayerIndex max_anti_overhang_layer = m_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 == m_current_outline_idx ? m_current_min_xy_dist : m_layer_outlines[outline_idx].first.support_xy_distance; // technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all not currently processing meshes as this delta will be added at request time. @@ -566,49 +570,48 @@ void TreeModelVolumes::calculateCollision(std::deque keys) if (size_t(layer_idx) < m_layer_outlines[outline_idx].second.size()) append(collision_areas, m_layer_outlines[outline_idx].second[layer_idx]); // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. - collision_areas = offset(union_ex(collision_areas), radius + xy_distance, ClipperLib::jtMiter, 1.2); - append(data[key], collision_areas); // if a key does not exist when it is accessed it is added! + // if a key does not exist when it is accessed it is added! + append(data[key], offset(union_ex(collision_areas), radius + xy_distance, ClipperLib::jtMiter, 1.2)); } // Add layers below, to ensure correct support_bottom_distance. Also save placeable areas of radius 0, if required for this mesh. - for (LayerIndex layer_idx = max_required_layer; layer_idx >= min_layer_bottom; -- layer_idx) { + for (int layer_idx = int(max_required_layer); layer_idx >= min_layer_bottom; -- layer_idx) { key.second = layer_idx; for (size_t layer_offset = 1; layer_offset <= z_distance_bottom_layers && layer_idx - coord_t(layer_offset) > min_layer_bottom; ++ layer_offset) append(data[key], data[RadiusLayerPair(radius, layer_idx - layer_offset)]); if (support_rests_on_this_model && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) { - data[key] = union_(data[key]); - Polygons above = data[RadiusLayerPair(radius, layer_idx + 1)]; + RadiusLayerPair key_next_layer(radius, layer_idx + 1); + //data[key] = union_(data[key]); + Polygons above = data[key_next_layer]; // just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly. - above = max_anti_overhang_layer >= layer_idx + 1 ? union_(above, m_anti_overhang[layer_idx]) : union_(above); - Polygons placeable = diff(data[key], above); - data_placeable[RadiusLayerPair(radius, layer_idx + 1)] = union_(data_placeable[RadiusLayerPair(radius, layer_idx + 1)], placeable); + //FIXME Vojtech: Why m_anti_overhang.size() > layer_idx + 1? Why +1? + above = m_anti_overhang.size() > layer_idx + 1 ? union_(above, m_anti_overhang[layer_idx]) : union_(above); + data_placeable[key_next_layer] = union_(data_placeable[key_next_layer], diff(data[key], above)); } } // Add collision layers above to ensure correct support_top_distance. - for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; layer_idx++) { + 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++) - append(data[key], data[RadiusLayerPair(radius, layer_idx + layer_offset)]); - data[key] = max_anti_overhang_layer >= layer_idx ? union_(data[key], offset(union_ex(m_anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(data[key]); + Polygons collisions = std::move(data[key]); + for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + layer_idx <= max_required_layer; ++ layer_offset) + append(collisions, data[RadiusLayerPair(radius, layer_idx + layer_offset)]); + data[key] = m_anti_overhang.size() > layer_idx ? union_(collisions, offset(union_ex(m_anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(collisions); } - for (LayerIndex layer_idx = max_required_layer; layer_idx > keys[i].second; layer_idx--) { + for (int layer_idx = int(max_required_layer); layer_idx > keys[i].second; -- layer_idx) { // all these dont have the correct z_distance_top_layers as they can still have areas above them auto it = data.find(RadiusLayerPair(radius, layer_idx)); if (it != data.end()) data.erase(it); } - for (auto pair : data) { - pair.second = simplify(pair.second, m_min_resolution); - data_outer[pair.first] = union_(data_outer[pair.first], pair.second); + for (auto pair : data) + data_outer[pair.first] = union_(data_outer[pair.first], simplify(pair.second, m_min_resolution)); + if (radius == 0) { + for (auto pair : data_placeable) + data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], simplify(pair.second, m_min_resolution)); } - if (radius == 0) - for (auto pair : data_placeable) { - pair.second = simplify(pair.second, m_min_resolution); - data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], pair.second); - } } #ifdef SLIC3R_TREESUPPORTS_PROGRESS @@ -648,7 +651,7 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke coord_t radius = key.first; coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - ceilRadius(radius, true); // this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area. - Polygons col = offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound); + Polygons col = offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound, scaled(0.01)); col = simplify(col, m_min_resolution); data[RadiusLayerPair(radius, layer_idx)] = col; } @@ -663,11 +666,11 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke static Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) { const size_t steps = std::abs(distance / max_safe_step_distance); - assert(distance * max_safe_step_distance >= 0); + assert(int64_t(distance) * int64_t(max_safe_step_distance) >= 0); ExPolygons ret = union_ex(me); for (size_t i = 0; i < steps; ++ i) - ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, 1.2), collision)); - return union_(offset(ret, distance % max_safe_step_distance, jt, 1.2), collision); + ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision)); + return union_(offset(ret, distance % max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision); } void TreeModelVolumes::calculateAvoidance(std::deque keys) @@ -768,7 +771,7 @@ void TreeModelVolumes::calculatePlaceables(std::deque keys) key.second = layer; Polygons placeable = getPlaceableAreas(0, layer); placeable = simplify(placeable, m_min_resolution); // it is faster to do this here in each thread than once in calculateCollision. - placeable = offset(union_ex(placeable), - radius); + placeable = offset(union_ex(placeable), - radius, jtMiter, 1.2); data[layer] = std::pair(key, placeable); } diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index d519164f4..993081137 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -27,7 +27,9 @@ #include +#include #include +#include namespace Slic3r { @@ -124,7 +126,7 @@ void TreeSupport::showError(std::string message, bool critical) static bool layer_has_overhangs(const Layer &layer) { for (const LayerRegion* layerm : layer.regions()) - if (layerm->slices.has(stBottom) || layerm->slices.has(stBottom)) + if (layerm->slices.has(stBottom) || layerm->slices.has(stBottomBridge)) return true; return false; } @@ -172,7 +174,7 @@ void TreeSupport::generateSupportAreas(PrintObject& print_object) break; ++ idx; } - this->generateSupportAreas(*print_object.print(), BuildVolume(Pointfs{ Vec2d{ -1000., -1000. }, Vec2d{ -1000., +1000. }, Vec2d{ +1000., +1000. }, Vec2d{ +1000., -1000. } }, 0.), { idx }); + this->generateSupportAreas(*print_object.print(), BuildVolume(Pointfs{ Vec2d{ -300., -300. }, Vec2d{ -300., +300. }, Vec2d{ +300., +300. }, Vec2d{ +300., -300. } }, 0.), { idx }); } void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_volume, const std::vector &print_object_ids) @@ -267,9 +269,17 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo } } + auto remove_undefined_layers = [](SupportGeneratorLayersPtr &layers) { + layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); + }; + remove_undefined_layers(bottom_contacts); + remove_undefined_layers(top_contacts); + remove_undefined_layers(intermediate_layers); + // Produce the support G-code. // Used by both classic and tree supports. SupportGeneratorLayersPtr raft_layers, interface_layers, base_interface_layers; + generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); generate_support_toolpaths(print_object.support_layers(), print_object.config(), SupportParameters(print_object), print_object.slicing_parameters(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); @@ -527,7 +537,7 @@ static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &in { Polylines result; for (Polyline part : input) { - if (part.empty() == 0) + if (part.empty()) continue; double len = length(part.points); @@ -537,7 +547,7 @@ static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &in { // Insert the opposite point of the first one. //FIXME pretty expensive - Polyline pl(line.points); + Polyline pl(part); pl.clip_end(len / 2); line.points.emplace_back(pl.points.back()); } @@ -673,7 +683,7 @@ static [[nodiscard]] Polylines generateSupportInfillLines( filler->layer_id = layer_idx; filler->spacing = flow.spacing(); - fill_params.density = float(roof ? support_params.interface_density : float(filler->spacing) / float(support_infill_distance)); + fill_params.density = float(roof ? support_params.interface_density : scaled(filler->spacing) / float(support_infill_distance)); fill_params.dont_adjust = true; Polylines out; @@ -722,7 +732,7 @@ static [[nodiscard]] Polygons safeUnion(const Polygons first, const Polygons sec if (result.empty()) { BOOST_LOG_TRIVIAL(debug) << "Caught an area destroying union, enlarging areas a bit."; // just take the few lines we have, and offset them a tiny bit. Needs to be offsetPolylines, as offset may aleady have problems with the area. - result = union_(offset(to_polylines(first), scaled(0.002)), offset(to_polylines(second), scaled(0.002))); + result = union_(offset(to_polylines(first), scaled(0.002), jtMiter, 1.2), offset(to_polylines(second), scaled(0.002), jtMiter, 1.2)); } } @@ -772,13 +782,16 @@ static [[nodiscard]] Polygons safeOffsetInc(const Polygons& me, coord_t distance } // offset in steps for (size_t i = 0; i < steps; i++) { - ret = diff(offset(ret, step_size, ClipperLib::jtRound), collision); + ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled(0.01)), collision); // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. if (i % 10 == 7) ret = polygons_simplify(ret, scaled(0.015)); } // offset the remainder - ret = polygons_simplify(offset(ret, distance - steps * step_size, ClipperLib::jtRound), scaled(0.015)); + float last_offset = distance - steps * step_size; + if (last_offset > SCALED_EPSILON) + ret = offset(ret, distance - steps * step_size, ClipperLib::jtRound, scaled(0.01)); + ret = polygons_simplify(ret, scaled(0.015)); if (do_final_difference) ret = diff(ret, collision); @@ -787,12 +800,36 @@ static [[nodiscard]] Polygons safeOffsetInc(const Polygons& me, coord_t distance // Using the std::deque as an allocator. inline SupportGeneratorLayer& layer_allocate( - std::deque& layer_storage, - SupporLayerType layer_type) + std::deque &layer_storage, + SupporLayerType layer_type, + const SlicingParameters &slicing_params, + size_t layer_idx) { layer_storage.push_back(SupportGeneratorLayer()); - layer_storage.back().layer_type = layer_type; - return layer_storage.back(); + SupportGeneratorLayer *layer_new = &layer_storage.back(); + layer_new->layer_type = layer_type; + layer_new->print_z = slicing_params.first_print_layer_height + std::max(0, int(layer_idx) - 1) * slicing_params.layer_height; + layer_new->height = slicing_params.layer_height; + layer_new->bottom_z = layer_idx == 0 ? 0. : layer_new->print_z - slicing_params.layer_height; + return *layer_new; +} + +inline SupportGeneratorLayer& layer_allocate( + std::deque &layer_storage, + tbb::spin_mutex& layer_storage_mutex, + SupporLayerType layer_type, + const SlicingParameters &slicing_params, + size_t layer_idx) +{ + layer_storage_mutex.lock(); + layer_storage.push_back(SupportGeneratorLayer()); + SupportGeneratorLayer *layer_new = &layer_storage.back(); + layer_storage_mutex.unlock(); + layer_new->layer_type = layer_type; + layer_new->print_z = slicing_params.first_print_layer_height + std::max(0, int(layer_idx) - 1) * slicing_params.layer_height; + layer_new->height = slicing_params.layer_height; + layer_new->bottom_z = layer_idx == 0 ? 0. : layer_new->print_z - slicing_params.layer_height; + return *layer_new; } void TreeSupport::generateInitialAreas( @@ -802,6 +839,8 @@ void TreeSupport::generateInitialAreas( SupportGeneratorLayersPtr &top_interface_layers, SupportGeneratorLayerStorage &layer_storage) { + tbb::global_control(tbb::global_control::max_allowed_parallelism, 1); + Polygon base_circle; const int base_radius = 10; for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; ++ i) { @@ -841,10 +880,13 @@ void TreeSupport::generateInitialAreas( for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { if (! layer_has_overhangs(*print_object.get_layer(layer_idx + z_distance_delta))) continue; - - Polygons relevant_forbidden = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides_z)); // take the least restrictive avoidance possible + // take the least restrictive avoidance possible + Polygons relevant_forbidden = (mesh_config.support_rests_on_model ? + (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides_z) : + m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides_z)) : + m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides_z)); // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. - relevant_forbidden = offset(union_ex(relevant_forbidden), scaled(0.005)); + relevant_forbidden = offset(union_ex(relevant_forbidden), scaled(0.005), jtMiter, 1.2); auto generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) -> Polylines { const coord_t support_infill_distance = roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance; @@ -921,13 +963,15 @@ void TreeSupport::generateInitialAreas( roof_circle.points.emplace_back(p.first + corner * mesh_config.min_radius / base_radius); added_roofs.emplace_back(roof_circle); } - added_roofs = union_(added_roofs); - { - std::lock_guard critical_section_storage(critical_sections); - SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip]; - if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact); - append(l->polygons, std::move(added_roofs)); + if (! added_roofs.empty()) { + added_roofs = union_(added_roofs); + { + std::lock_guard critical_section_storage(critical_sections); + SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), insert_layer_idx - dtt_roof_tip); + append(l->polygons, std::move(added_roofs)); + } } } @@ -945,7 +989,7 @@ void TreeSupport::generateInitialAreas( Polygons overhang_raw = layer_overhangs(*print_object.get_layer(layer_idx + z_distance_delta)); Polygons overhang_regular = safeOffsetInc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, mesh_config.min_radius * 1.75 + mesh_config.xy_min_distance, 0, 1); // offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang - Polygons remaining_overhang = intersection(diff(offset(union_ex(overhang_raw), mesh_group_settings.support_offset), offset(union_ex(overhang_regular), mesh_config.support_line_width * 0.5)), relevant_forbidden); + Polygons remaining_overhang = intersection(diff(offset(union_ex(overhang_raw), mesh_group_settings.support_offset, jtMiter, 1.2), offset(union_ex(overhang_regular), mesh_config.support_line_width * 0.5, jtMiter, 1.2)), relevant_forbidden); coord_t extra_total_offset_acc = 0; // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. @@ -1032,7 +1076,7 @@ void TreeSupport::generateInitialAreas( // here the roof is handled. If roof can not be added the branches will try to not move instead Polygons forbidden_next = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides_z));\ // prevent rounding errors down the line - forbidden_next = offset(union_ex(forbidden_next), scaled(0.005)); + forbidden_next = offset(union_ex(forbidden_next), scaled(0.005), jtMiter, 1.2); Polygons overhang_outset_next = diff(overhang_outset, forbidden_next); if (area(overhang_outset_next) < mesh_group_settings.minimum_roof_area) { // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter @@ -1066,13 +1110,14 @@ void TreeSupport::generateInitialAreas( { std::lock_guard critical_section_storage(critical_sections); - for (size_t idx = 0; idx < dtt_roof; idx++) { - SupportGeneratorLayer *&l = top_contacts[layer_idx - idx]; - if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact); - // will be unioned in finalizeInterfaceAndSupportAreas - append(l->polygons, std::move(added_roofs[idx])); - } + for (size_t idx = 0; idx < dtt_roof; ++ idx) + if (! added_roofs[idx].empty()) { + SupportGeneratorLayer *&l = top_contacts[layer_idx - idx]; + if (l == nullptr) + l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), layer_idx - idx); + // will be unioned in finalizeInterfaceAndSupportAreas + append(l->polygons, std::move(added_roofs[idx])); + } } if (overhang_lines.empty()) { @@ -1088,9 +1133,9 @@ void TreeSupport::generateInitialAreas( // I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them // will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, // as some support is better than none. - Polygons reduced_overhang_outset = offset(union_ex(overhang_outset), -mesh_config.support_line_width / 2.2); + Polygons reduced_overhang_outset = offset(union_ex(overhang_outset), -mesh_config.support_line_width / 2.2, jtMiter, 1.2); polylines = ensureMaximumDistancePolyline( - to_polylines(!reduced_overhang_outset.empty() && area(offset(diff_ex(overhang_outset, reduced_overhang_outset), std::max(mesh_config.support_line_width, connect_length))) < 1 ? + to_polylines(!reduced_overhang_outset.empty() && area(offset(diff_ex(overhang_outset, reduced_overhang_outset), std::max(mesh_config.support_line_width, connect_length), jtMiter, 1.2)) < 1 ? reduced_overhang_outset : overhang_outset), connect_length, min_support_points); @@ -1099,11 +1144,11 @@ void TreeSupport::generateInitialAreas( overhang_lines = convertLinesToInternal(m_volumes, m_config, polylines, last_insert_layer); } - if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part) { // reached buildplate + if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part && ! overhang_outset.empty()) { // reached buildplate std::lock_guard critical_section_storage(critical_sections); SupportGeneratorLayer*& l = top_contacts[0]; if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::TopContact); + l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), 0); append(l->polygons, std::move(overhang_outset)); } else // normal trees have to be generated addLinesAsInfluenceAreas(overhang_lines, force_tip_to_roof ? support_roof_layers - dtt_roof : 0, layer_idx - dtt_roof, dtt_roof > 0, roof_enabled ? support_roof_layers - dtt_roof : 0); @@ -1314,7 +1359,7 @@ static void mergeHelper( Polygons intersect = intersection(small_rad_increased_by_big_minus_small, bigger_rad.second); if (area(intersect) > tiny_area_threshold) { // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) - if (area(offset(intersect, scaled(-0.025))) <= tiny_area_threshold) // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). While 25 was guessed as enough, i did not have reason to change it. + if (area(offset(intersect, scaled(-0.025), jtMiter, 1.2)) <= tiny_area_threshold) // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). While 25 was guessed as enough, i did not have reason to change it. continue; // Do the actual merge now that the branches are confirmed to be able to intersect. @@ -1365,7 +1410,7 @@ static void mergeHelper( erase.emplace_back(reduced_check_iter->first); erase.emplace_back(influence_iter->first); - Polygons merge = diff(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound), volumes.getCollision(0, layer_idx - 1)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. + Polygons merge = diff(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound, scaled(0.01)), volumes.getCollision(0, layer_idx - 1)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. reduced_aabb.erase(reduced_check_iter->first); // this invalidates reduced_check_iter reduced_aabb.emplace(key, get_extents(merge)); @@ -1569,8 +1614,11 @@ std::optional TreeSupport::increaseSingleArea(AreaI radius = m_config.getCollisionRadius(current_elem); const coord_t foot_radius_increase = m_config.branch_radius * (std::max(m_config.diameter_scale_bp_radius - m_config.diameter_angle_scale_factor, 0.0)); - double planned_foot_increase = std::min(1.0, double(m_config.recommendedMinRadius(layer_idx - 1) - m_config.getRadius(current_elem)) / foot_radius_increase); // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, which could cause the radius to become bigger than precalculated. + // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, which could cause the radius to become bigger than precalculated. + double planned_foot_increase = std::min(1.0, double(m_config.recommendedMinRadius(layer_idx - 1) - m_config.getRadius(current_elem)) / foot_radius_increase); +//FIXME bool increase_bp_foot = planned_foot_increase > 0 && current_elem.to_buildplate; +// bool increase_bp_foot = false; if (increase_bp_foot && m_config.getRadius(current_elem) >= m_config.branch_radius && m_config.getRadius(current_elem) >= m_config.increase_radius_until_radius) if (validWithRadius(m_config.getRadius(current_elem.effective_radius_height, current_elem.elephant_foot_increases + planned_foot_increase))) { @@ -1749,7 +1797,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to if (!settings.no_error) { // ERROR CASE // if the area becomes for whatever reason something that clipper sees as a line, offset would stop working, so ensure that even if if wrongly would be a line, it still actually has an area that can be increased - Polygons lines_offset = offset(to_polylines(*parent->area), scaled(0.005)); + Polygons lines_offset = offset(to_polylines(*parent->area), scaled(0.005), jtMiter, 1.2); Polygons base_error_area = union_(*parent->area, lines_offset); result = increaseSingleArea(settings, layer_idx, parent, base_error_area, to_bp_data, to_model_data, inc_wo_collision, (m_config.maximum_move_distance + extra_speed) * 1.5, mergelayer); BOOST_LOG_TRIVIAL(error) << @@ -1832,7 +1880,7 @@ void TreeSupport::createLayerPathing(std::vector>& mov size_t merge_every_x_layers = 1; // Calculate the influence areas for each layer below (Top down) // This is done by first increasing the influence area by the allowed movement distance, and merging them with other influence areas if possible - for (LayerIndex layer_idx = move_bounds.size() - 1; layer_idx > 0; layer_idx--) + for (int layer_idx = int(move_bounds.size()) - 1; layer_idx > 0; -- layer_idx) { // merging is expensive and only parallelized to a max speedup of 2. As such it may be useful in some cases to only merge every few layers to improve performance. bool merge_this_layer = size_t(last_merge - layer_idx) >= merge_every_x_layers; @@ -2138,7 +2186,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 = diff(offset(union_(poly), std::min(coord_t(50), m_config.support_line_width / 4), jtMiter, 1.2), + m_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; }; @@ -2171,7 +2220,7 @@ void TreeSupport::generateBranchAreas(std::vectoruse_min_xy_dist)); } } @@ -2201,7 +2250,7 @@ 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 @@ -2219,7 +2268,7 @@ void TreeSupport::smoothBranchAreas(std::vectorparents) if (m_config.getRadius(*parent) != m_config.getCollisionRadius(*parent)) update_next[processing_idx].emplace_back(std::pair(parent, intersection(layer_tree_polygons[layer_idx + 1][parent], max_allowed_area))); @@ -2239,7 +2288,7 @@ void TreeSupport::smoothBranchAreas(std::vector updated_last_iteration; - for (LayerIndex layer_idx = layer_tree_polygons.size() - 2; layer_idx >= 0; layer_idx--) { + for (int layer_idx = int(layer_tree_polygons.size()) - 2; layer_idx >= 0; -- layer_idx) { 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 @@ -2253,7 +2302,7 @@ void TreeSupport::smoothBranchAreas(std::vectorparents.size(); ++ idx) { SupportElement* parent = data_pair.first->parents[idx]; coord_t max_outer_line_increase = max_radius_change_per_layer; - Polygons result = offset(layer_tree_polygons[layer_idx + 1][parent], max_outer_line_increase); + Polygons result = offset(layer_tree_polygons[layer_idx + 1][parent], max_outer_line_increase, jtMiter, 1.2); Point direction = data_pair.first->result_on_layer - parent->result_on_layer; // move the polygons object for (auto& outer : result) @@ -2326,7 +2375,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( #endif // SLIC3R_TREESUPPORTS_PROGRESS // Iterate over the generated circles in parallel and clean them up. Also add support floor. - std::mutex critical_sections; + tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(0, support_layer_storage.size()), [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { @@ -2338,7 +2387,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( SupportGeneratorLayer*& support_roof = top_contacts[layer_idx]; if (! support_roof_storage[layer_idx].empty() || support_roof != nullptr) { if (support_roof == nullptr) { - support_roof = &layer_allocate(layer_storage, SupporLayerType::TopContact); + support_roof = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::TopContact, print_object.slicing_parameters(), layer_idx); support_roof->polygons = union_(support_roof_storage[layer_idx]); } else support_roof->polygons = union_(support_roof->polygons, support_roof_storage[layer_idx]); @@ -2383,10 +2432,10 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( // Subtract support floors from the support area and add them to the support floor instead. if (m_config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) { SupportGeneratorLayer*& support_bottom = bottom_contacts[layer_idx]; - if (support_bottom == nullptr) - support_bottom = &layer_allocate(layer_storage, SupporLayerType::BottomContact); - Polygons floor_layer = std::move(support_bottom->polygons); - Polygons layer_outset = diff(offset(support_layer_storage[layer_idx], m_config.support_bottom_offset), m_volumes.getCollision(0, layer_idx, false)); + Polygons layer_outset = diff( + m_config.support_bottom_offset > 0 ? offset(support_layer_storage[layer_idx], m_config.support_bottom_offset, jtMiter, 1.2) : support_layer_storage[layer_idx], + m_volumes.getCollision(0, layer_idx, false)); + Polygons floor_layer; size_t layers_below = 0; while (layers_below <= m_config.support_bottom_layers) { // one sample at 0 layers below, another at m_config.support_bottom_layers. In-between samples at m_config.performance_interface_skip_layers distance from each other. @@ -2398,14 +2447,18 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( else break; } - support_bottom->polygons = union_(floor_layer); - support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], offset(support_bottom->polygons, scaled(0.01))); // Subtract the support floor from the normal support. + if (! floor_layer.empty()) { + if (support_bottom == nullptr) + support_bottom = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::BottomContact, print_object.slicing_parameters(), layer_idx); + support_bottom->polygons = union_(floor_layer, support_bottom->polygons); + support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], offset(support_bottom->polygons, scaled(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support. + } } - { + if (! support_layer_storage[layer_idx].empty()) { SupportGeneratorLayer *&l = intermediate_layers[layer_idx]; if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::Base); + l = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::Base, print_object.slicing_parameters(), layer_idx); append(l->polygons, union_(support_layer_storage[layer_idx])); } diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index a2fc49f01..894705a32 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -200,10 +200,11 @@ public: if (config.diameter_scale_bp_radius > 0) { coord_t foot_increase_radius = std::abs(std::max(config.getCollisionRadius(second), config.getCollisionRadius(first)) - config.getCollisionRadius(*this)); - elephant_foot_increases = foot_increase_radius / (config.branch_radius * (config.diameter_scale_bp_radius - config.diameter_angle_scale_factor)); // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. + // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch + // the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. + elephant_foot_increases = foot_increase_radius / (config.branch_radius * (config.diameter_scale_bp_radius - config.diameter_angle_scale_factor)); } - // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. last_area_increase = { std::min(first.last_area_increase.type, second.last_area_increase.type), @@ -398,9 +399,7 @@ public: // 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. if (has_to_rely_on_min_xy_dist_only) - { 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); @@ -678,7 +677,7 @@ public: */ [[nodiscard]] inline coord_t recommendedMinRadius(LayerIndex layer_idx) const { - double scale = (layer_start_bp_radius - layer_idx) * diameter_scale_bp_radius; + double scale = (layer_start_bp_radius - int(layer_idx)) * diameter_scale_bp_radius; return scale > 0 ? branch_radius + branch_radius * scale : 0; } From fa7debf49d0f8f850a64fb2a5a4cf71810612a3c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 Aug 2022 13:22:33 +0200 Subject: [PATCH 11/29] Clipper: Verify range of int32 coordinates on input. --- src/clipper/clipper.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 84109398a..6fde80906 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -602,9 +602,18 @@ bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) // ClipperBase class methods ... //------------------------------------------------------------------------------ -#ifndef CLIPPERLIB_INT32 +#ifdef CLIPPERLIB_INT32 +static inline void RangeTest(const IntPoint &pt) +{ +#ifndef NDEBUG + static constexpr const int32_t hi = 65536 * 16383; + if (pt.x() > hi || pt.y() > hi || -pt.x() > hi || -pt.y() > hi) + throw clipperException("Coordinate outside allowed range"); +#endif // NDEBUG +} +#else // CLIPPERLIB_INT32 // Called from ClipperBase::AddPath() to verify the scale of the input polygon coordinates. -inline void RangeTest(const IntPoint& Pt, bool& useFullRange) +static inline void RangeTest(const IntPoint& Pt, bool& useFullRange) { if (useFullRange) { @@ -798,7 +807,10 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b try { edges[1].Curr = pg[1]; -#ifndef CLIPPERLIB_INT32 +#ifdef CLIPPERLIB_INT32 + RangeTest(pg[0]); + RangeTest(pg[highI]); +#else RangeTest(pg[0], m_UseFullRange); RangeTest(pg[highI], m_UseFullRange); #endif // CLIPPERLIB_INT32 @@ -806,7 +818,9 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); for (int i = highI - 1; i >= 1; --i) { -#ifndef CLIPPERLIB_INT32 +#ifdef CLIPPERLIB_INT32 + RangeTest(pg[i]); +#else RangeTest(pg[i], m_UseFullRange); #endif // CLIPPERLIB_INT32 InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); From 6bf335409f64d5e628a56f439824f83b5ae516ca Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 Aug 2022 13:23:30 +0200 Subject: [PATCH 12/29] FillBase: Use logging instead of printf --- src/libslic3r/Fill/FillBase.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index a8d5d20e2..b38c5c9f8 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -21,6 +21,8 @@ #include "FillAdaptive.hpp" #include "FillLightning.hpp" +#include + // #define INFILL_DEBUG_OUTPUT namespace Slic3r { @@ -129,8 +131,8 @@ std::pair Fill::_infill_direction(const Surface *surface) const float out_angle = this->angle; if (out_angle == FLT_MAX) { - //FIXME Vojtech: Add a warning? - printf("Using undefined infill angle\n"); + assert(false); + BOOST_LOG_TRIVIAL(error) << "Using undefined infill angle"; out_angle = 0.f; } From f971c392fe59d71f2a147198e3b3b0f56c93342f Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 Aug 2022 13:36:05 +0200 Subject: [PATCH 13/29] WIP TreeSupports: Some fixes, some clang specific workarounds, some debugging code. --- src/libslic3r/SupportMaterial.cpp | 2 +- src/libslic3r/TreeModelVolumes.cpp | 8 +- src/libslic3r/TreeModelVolumes.hpp | 2 +- src/libslic3r/TreeSupport.cpp | 286 +++++++++++++++++------------ src/libslic3r/TreeSupport.hpp | 2 +- 5 files changed, 177 insertions(+), 123 deletions(-) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 96b756439..1c2b785e3 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -4105,7 +4105,7 @@ void generate_support_toolpaths( { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; - float interface_angle_delta = config.support_material_style.value == smsSnug ? + float interface_angle_delta = config.support_material_style.value == smsSnug || config.support_material_style.value == smsTree ? (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : 0; diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 25d5a3d35..57ef150d5 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -492,7 +492,7 @@ coord_t TreeModelVolumes::getRadiusNextCeil(coord_t radius, bool min_xy_dist) co return ceiled_radius; } -static inline [[nodiscard]] Polygons simplify(const Polygons &polygons, coord_t resolution) +[[nodiscard]] static inline Polygons simplify(const Polygons &polygons, coord_t resolution) { //FIXME return polygons; @@ -521,13 +521,13 @@ Polygons TreeModelVolumes::extractOutlineFromMesh(const PrintObject &print_objec LayerIndex TreeModelVolumes::getMaxCalculatedLayer(coord_t radius, const RadiusLayerPolygonCache& map) const { - LayerIndex max_layer = -1; + int max_layer = -1; // the placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist. const RadiusLayerPair key_layer_1(radius, 1); if (getArea(map, key_layer_1)) max_layer = 1; while (map.count(RadiusLayerPair(radius, max_layer + 1))) - max_layer++; + ++ max_layer; return max_layer; } @@ -559,7 +559,7 @@ void TreeModelVolumes::calculateCollision(std::deque keys) coord_t min_layer_bottom; { std::lock_guard critical_section(*m_critical_collision_cache); - min_layer_bottom = getMaxCalculatedLayer(radius, m_collision_cache) - z_distance_bottom_layers; + min_layer_bottom = getMaxCalculatedLayer(radius, m_collision_cache) - int(z_distance_bottom_layers); } if (min_layer_bottom < 0) diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 51f748d83..cd1cc9722 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -22,7 +22,7 @@ namespace Slic3r { -using LayerIndex = size_t; +using LayerIndex = int; using AngleRadians = double; class BuildVolume; diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 993081137..689e57904 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -9,6 +9,7 @@ #include "TreeSupport.hpp" #include "BuildVolume.hpp" #include "ClipperUtils.hpp" +#include "EdgeGrid.hpp" #include "Fill/Fill.hpp" #include "Layer.hpp" #include "Print.hpp" @@ -23,7 +24,9 @@ #include #include #include -#include //todo Remove! ONLY FOR PUBLIC BETA!! +#ifdef _WIN32 + #include //todo Remove! ONLY FOR PUBLIC BETA!! +#endif // _WIN32 #include @@ -34,9 +37,64 @@ namespace Slic3r { +enum class LineStatus +{ + INVALID, + TO_MODEL, + TO_MODEL_GRACIOUS, + TO_MODEL_GRACIOUS_SAFE, + TO_BP, + TO_BP_SAFE +}; + +using LineInformation = std::vector>; +using LineInformations = std::vector; + +static inline void validate_range(const Point &pt) +{ + static constexpr const int32_t hi = 65536 * 16384; + if (pt.x() > hi || pt.y() > hi || -pt.x() > hi || -pt.y() > hi) + throw ClipperLib::clipperException("Coordinate outside allowed range"); +} + +static inline void validate_range(const Points &points) +{ + for (const Point &p : points) + validate_range(p); +} + +static inline void validate_range(const MultiPoint &mp) +{ + validate_range(mp.points); +} + +static inline void validate_range(const Polygons &polygons) +{ + for (const Polygon &p : polygons) + validate_range(p); +} + +static inline void validate_range(const Polylines &polylines) +{ + for (const Polyline &p : polylines) + validate_range(p); +} + +static inline void validate_range(const LineInformation &lines) +{ + for (const auto& p : lines) + validate_range(p.first); +} + +static inline void validate_range(const LineInformations &lines) +{ + for (const LineInformation &l : lines) + validate_range(l); +} + static constexpr const auto tiny_area_threshold = sqr(scaled(0.001)); -static [[nodiscard]] std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) +static std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) { std::vector>> grouped_meshes; @@ -96,7 +154,7 @@ static [[nodiscard]] std::vector>; -using LineInformations = std::vector; - /*! * \brief Converts a Polygons object representing a line into the internal format. * @@ -317,7 +364,7 @@ using LineInformations = std::vector; * \param layer_idx[in] The current layer. * \return All lines of the \p polylines object, with information for each point regarding in which avoidance it is currently valid in. */ -static [[nodiscard]] LineInformations convertLinesToInternal( +[[nodiscard]] static LineInformations convertLinesToInternal( const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, const Polylines &polylines, LayerIndex layer_idx) { @@ -349,6 +396,7 @@ static [[nodiscard]] LineInformations convertLinesToInternal( } } + validate_range(result); return result; } @@ -358,7 +406,7 @@ static [[nodiscard]] LineInformations convertLinesToInternal( * \param lines[in] The lines that will be converted. * \return All lines of the \p lines object as a Polygons object. */ -static [[nodiscard]] Polylines convertInternalToLines(LineInformations lines) +[[nodiscard]] static Polylines convertInternalToLines(LineInformations lines) { Polylines result; for (LineInformation line : lines) { @@ -367,6 +415,7 @@ static [[nodiscard]] Polylines convertInternalToLines(LineInformations lines) path.points.emplace_back(point_data.first); result.emplace_back(std::move(path)); } + validate_range(result); return result; } @@ -376,7 +425,7 @@ static [[nodiscard]] Polylines convertInternalToLines(LineInformations lines) * \param current_layer[in] The layer on which the point lies, point and its status. * \return whether the point is valid. */ -static [[nodiscard]] bool evaluatePointForNextLayerFunction( +[[nodiscard]] static bool evaluatePointForNextLayerFunction( const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, size_t current_layer, std::pair &p) { @@ -400,7 +449,7 @@ static [[nodiscard]] bool evaluatePointForNextLayerFunction( * \return A pair with which points are still valid in the first slot and which are not in the second slot. */ template -static [[nodiscard]] std::pair splitLines(LineInformations lines, EvaluatePointFn evaluatePoint) +[[nodiscard]] static std::pair splitLines(LineInformations lines, EvaluatePointFn evaluatePoint) { // assumes all Points on the current line are valid @@ -442,87 +491,58 @@ static [[nodiscard]] std::pair splitLines(Li set_free.emplace_back(resulting_line); } } + validate_range(keep); + validate_range(set_free); return std::pair>>, std::vector>>>(keep, set_free); } -/*! - * A point within a polygon and the index of which segment in the polygon the point lies on. - */ - //from CURA -struct GivenDistPoint +// Ported from CURA's PolygonUtils::getNextPointWithDistance() +// Sample a next point at distance "dist" from start_pt on polyline segment (start_idx, start_idx + 1). +// Returns sample point and start index of its segment on polyline if such sample exists. +static std::optional> polyline_sample_next_point_at_distance(const Points &polyline, const Point &start_pt, size_t start_idx, double dist) { - Point location; //!< Result location - int pos; //!< Index to the first point in the polygon of the line segment on which the result was found -}; + const double dist2 = sqr(dist); + const auto dist2i = int64_t(dist2); + static constexpr const auto eps = scaled(0.01); -//from CURA -static bool getNextPointWithDistance(Point from, double dist, Points &poly, int start_idx, int poly_start_idx, GivenDistPoint& result) -{ - Point prev_poly_point = poly[(start_idx + poly_start_idx) % poly.size()]; - - for (unsigned int prev_idx = start_idx; prev_idx < poly.size(); ++ prev_idx) - { - // last checked segment is between last point in poly and poly[0]... - int next_idx = (prev_idx + 1 + poly_start_idx) % poly.size(); - const Point &next_poly_point = poly[next_idx]; - if ((next_poly_point - from).cast().norm() >= dist) { - /* - * x r - * p.---------+---+------------.n - * L| / - * | / dist - * |/ - * f. - * - * f=from - * p=prev_poly_point - * n=next_poly_point - * x= f projected on pn - * r=result point at distance [dist] from f - */ - - Point pn = next_poly_point - prev_poly_point; - - static constexpr const double eps = scaled(0.1); - if (pn.cast().squaredNorm() < sqr(eps)) { // when precision is limited - Point middle = (next_poly_point + prev_poly_point) / 2; - double dist_to_middle = (from - middle).cast().norm(); - if (dist_to_middle - dist < eps && dist_to_middle - dist > -eps) { - result.location = middle; - result.pos = prev_idx; - return true; - } else { - prev_poly_point = next_poly_point; + for (size_t i = start_idx + 1; i < polyline.size(); ++ i) { + const Point p1 = polyline[i]; + if ((p1 - start_pt).cast().squaredNorm() >= dist2i) { + // The end point is outside the circle with center "start_pt" and radius "dist". + const Point p0 = polyline[i - 1]; + Vec2d v = (p1 - p0).cast(); + double l2v = v.squaredNorm(); + if (l2v < sqr(eps)) { + // Very short segment. + Point c = (p0 + p1) / 2; + if (std::abs((start_pt - c).cast().norm() - dist) < eps) + return std::pair{ c, i - 1 }; + else continue; + } + Vec2d p0f = (start_pt - p0).cast(); + // Foot point of start_pt into v. + Vec2d foot_pt = v * (p0f.dot(v) / l2v); + // Vector from foot point of "start_pt" to "start_pt". + Vec2d xf = p0f - foot_pt; + // Squared distance of "start_pt" from the ray (p0, p1). + double l2_from_line = xf.squaredNorm(); + double det = dist2 - l2_from_line; + + if (det > - SCALED_EPSILON) { + // The ray (p0, p1) touches or intersects a circle centered at "start_pt" with radius "dist". + // Distance of the circle intersection point from the foot point. + double dist_circle_intersection = std::sqrt(std::max(0., det)); + if ((v - foot_pt).cast().norm() > dist_circle_intersection) { + // Intersection of the circle with the segment (p0, p1) is on the right side (close to p1) from the foot point. + Point p = p0 + (foot_pt + v * (dist_circle_intersection / sqrt(l2v))).cast(); + validate_range(p); + return std::pair{ p, i - 1 }; } } - - Point pf = from - prev_poly_point; - Point px = (pn.cast() * (pf.cast().dot(pn.cast()) / pn.cast().squaredNorm())).cast(); - Point xf = pf - px; - - if (xf.cast().norm() >= dist) { // line lies wholly further than pn - prev_poly_point = next_poly_point; - continue; - } - - double xr_dist = std::sqrt(dist * dist - xf.cast().squaredNorm()); // inverse Pythagoras - - if ((pn - px).cast().norm() - xr_dist < scaled(1.)) { // r lies beyond n - prev_poly_point = next_poly_point; - continue; - } - - Point xr = (pn * (xr_dist / pn.cast().norm())).cast(); - Point pr = px + xr; - - result.location = prev_poly_point + pr; - result.pos = prev_idx; - return true; } - prev_poly_point = next_poly_point; } - return false; + return {}; } /*! @@ -533,7 +553,7 @@ static bool getNextPointWithDistance(Point from, double dist, Points &poly, int * \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. */ -static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &input, double distance, size_t min_points) +[[nodiscard]] static Polylines ensureMaximumDistancePolyline(const Polylines &input, double distance, size_t min_points) { Polylines result; for (Polyline part : input) { @@ -583,28 +603,27 @@ static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &in if (min_points > 1 || (part[0] - part[optimal_end_index]).cast().norm() > current_distance) line.points.emplace_back(part[optimal_end_index]); size_t current_index = 0; - GivenDistPoint next_point; - coord_t next_distance = current_distance; + std::optional> next_point; + double next_distance = current_distance; // Get points so that at least min_points are added and they each are current_distance away from each other. If that is impossible, decrease current_distance a bit. // The input are lines, that means that the line from the last to the first vertex does not have to exist, so exclude all points that are on this line! - while (getNextPointWithDistance(current_point, next_distance, part.points, current_index, 0, next_point) && next_point.pos < coord_t(part.size())) - { + while ((next_point = polyline_sample_next_point_at_distance(part.points, current_point, current_index, next_distance))) { // Not every point that is distance away, is valid, as it may be much closer to another point. This is especially the case when the overhang is very thin. // So this ensures that the points are actually a certain distance from each other. // This assurance is only made on a per polygon basis, as different but close polygon may not be able to use support below the other polygon. double min_distance_to_existing_point = std::numeric_limits::max(); for (Point p : line) - min_distance_to_existing_point = std::min(min_distance_to_existing_point, (p - next_point.location).cast().norm()); + min_distance_to_existing_point = std::min(min_distance_to_existing_point, (p - next_point->first).cast().norm()); if (min_distance_to_existing_point >= current_distance) { // viable point was found. Add to possible result. - line.points.emplace_back(next_point.location); - current_point = next_point.location; - current_index = next_point.pos; + line.points.emplace_back(next_point->first); + current_point = next_point->first; + current_index = next_point->second; next_distance = current_distance; } else { - if (current_point == next_point.location) { + if (current_point == next_point->first) { // In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here... - BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in getNextPointWithDistance. This is expected to happen if the distance (currently " << next_distance << + BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance << ") is smaller than 100"; TreeSupport::showError("Encountered issue while placing tips. Some tips may be missing.", true); if (next_distance > 2 * current_distance) @@ -615,8 +634,8 @@ static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &in } // if the point was too close, the next possible viable point is at least distance-min_distance_to_existing_point away from the one that was just checked. next_distance = std::max(current_distance - min_distance_to_existing_point, scaled(0.1)); - current_point = next_point.location; - current_index = next_point.pos; + current_point = next_point->first; + current_index = next_point->second; } } current_distance *= 0.9; @@ -624,6 +643,7 @@ static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &in } result.emplace_back(std::move(line)); } + validate_range(result); return result; } @@ -637,8 +657,8 @@ static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &in * * \return A Polygons object that represents the resulting infill lines. */ -static [[nodiscard]] Polylines generateSupportInfillLines( - const Polygons &area, const SupportParameters &support_params, +[[nodiscard]] static Polylines generateSupportInfillLines( + const Polygons &polygon, const SupportParameters &support_params, bool roof, LayerIndex layer_idx, coord_t support_infill_distance) { #if 0 @@ -667,34 +687,61 @@ static [[nodiscard]] Polylines generateSupportInfillLines( int divisor = static_cast(angles.size()); int index = ((layer_idx % divisor) + divisor) % divisor; const AngleRadians fill_angle = angles[index]; - Infill roof_computation(pattern, true /* zig_zaggify_infill */, connect_polygons, area, + Infill roof_computation(pattern, true /* zig_zaggify_infill */, connect_polygons, polygon, roof ? config.support_roof_line_width : config.support_line_width, support_infill_distance, support_roof_overlap, infill_multiplier, fill_angle, z, support_shift, config.resolution, wall_line_count, infill_origin, perimeter_gaps, connected_zigzags, use_endpieces, false /* skip_some_zags */, zag_skip_count, pocket_size); - Polygons areas; + Polygons polygons; Polygons lines; - roof_computation.generate(toolpaths, areas, lines, config.settings); - append(lines, to_polylines(areas)); + roof_computation.generate(toolpaths, polygons, lines, config.settings); + append(lines, to_polylines(polygons)); return lines; #else +#ifdef _WIN32 + if (! BoundingBox(Point::new_scale(-170., -170.), Point::new_scale(170., 170.)).contains(get_extents(polygon))) + ::MessageBoxA(nullptr, "TreeSupport infill kravsky", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); +#endif // _WIN32 + const Flow &flow = roof ? support_params.support_material_interface_flow : support_params.support_material_flow; std::unique_ptr filler = std::unique_ptr(Fill::new_from_type(roof ? support_params.interface_fill_pattern : support_params.base_fill_pattern)); FillParams fill_params; filler->layer_id = layer_idx; filler->spacing = flow.spacing(); + filler->angle = roof ? + //fixme support_layer.interface_id() instead of layer_idx + (support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) : + support_params.base_angle; + fill_params.density = float(roof ? support_params.interface_density : scaled(filler->spacing) / float(support_infill_distance)); fill_params.dont_adjust = true; Polylines out; - for (ExPolygon &expoly : union_ex(area)) { + for (ExPolygon &expoly : union_ex(polygon)) { // The surface type does not matter. + assert(area(expoly) > 0.); +#ifdef _WIN32 + if (area(expoly) <= 0.) + ::MessageBoxA(nullptr, "TreeSupport infill negative area", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); +#endif // _WIN32 + assert(intersecting_edges(expoly).empty()); +#ifdef _WIN32 + if (! intersecting_edges(expoly).empty()) + ::MessageBoxA(nullptr, "TreeSupport infill self intersections", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); +#endif // _WIN32 Surface surface(stInternal, std::move(expoly)); try { - append(out, filler->fill_surface(&surface, fill_params)); + Polylines pl = filler->fill_surface(&surface, fill_params); + assert(pl.empty() || get_extents(surface.expolygon).inflated(SCALED_EPSILON).contains(get_extents(pl))); +#ifdef _WIN32 + if (! pl.empty() && ! get_extents(surface.expolygon).inflated(SCALED_EPSILON).contains(get_extents(pl))) + ::MessageBoxA(nullptr, "TreeSupport infill failure", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); +#endif // _WIN32 + append(out, std::move(pl)); } catch (InfillFailedException &) { } } + validate_range(out); return out; #endif } @@ -705,7 +752,7 @@ static [[nodiscard]] Polylines generateSupportInfillLines( * \param second[in] The second Polygon. * \return The union of both Polygons */ -static [[nodiscard]] Polygons safeUnion(const Polygons first, const Polygons second = Polygons()) +[[nodiscard]] static Polygons safeUnion(const Polygons first, const Polygons second = Polygons()) { // unionPolygons can slowly remove Polygons under certain circumstances, because of rounding issues (Polygons that have a thin area). // This does not cause a problem when actually using it on large areas, but as influence areas (representing centerpoints) can be very thin, this does occur so this ugly workaround is needed @@ -748,7 +795,7 @@ static [[nodiscard]] Polygons safeUnion(const Polygons first, const Polygons sec * \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. */ -static [[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) +[[nodiscard]] static Polygons safeOffsetInc(const Polygons& me, coord_t distance, const Polygons& collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset) { bool do_final_difference = last_step_offset_without_check == 0; Polygons ret = safeUnion(me); // ensure sane input @@ -913,6 +960,7 @@ void TreeSupport::generateInitialAreas( 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_z, dont_move_until, roof, safe_radius, force_tip_to_roof, skip_ovalisation); elem->area = new Polygons(); + validate_range(circle); elem->area->emplace_back(std::move(circle)); move_bounds[insert_layer].emplace(elem); } @@ -921,6 +969,7 @@ void TreeSupport::generateInitialAreas( auto addLinesAsInfluenceAreas = [&](LineInformations lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, bool supports_roof, size_t dont_move_until) { + validate_range(lines); // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible size_t dtt_roof_tip; for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; dtt_roof_tip++) @@ -1026,6 +1075,7 @@ void TreeSupport::generateInitialAreas( res_line.emplace_back(p, LineStatus::INVALID); overhang_lines.emplace_back(res_line); } + validate_range(overhang_lines); } for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) { @@ -1037,6 +1087,7 @@ void TreeSupport::generateInitialAreas( std::pair split = splitLines(overhang_lines, evaluatePoint); // keep all lines that are invalid overhang_lines = split.first; LineInformations fresh_valid_points = convertLinesToInternal(m_volumes, m_config, convertInternalToLines(split.second), layer_idx - lag_ctr); // set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. + validate_range(fresh_valid_points); addLinesAsInfluenceAreas(fresh_valid_points, (force_tip_to_roof && lag_ctr <= support_roof_layers) ? support_roof_layers : 0, layer_idx - lag_ctr, false, roof_enabled ? support_roof_layers : 0); } @@ -1841,6 +1892,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to { std::lock_guard critical_section_newLayer(critical_sections); if (bypass_merge) { + validate_range(max_influence_area); Polygons* new_area = new Polygons(max_influence_area); SupportElement* next = new SupportElement(elem, new_area); bypass_merge_areas.emplace_back(next); @@ -1928,6 +1980,8 @@ void TreeSupport::createLayerPathing(std::vector>& mov // Save calculated elements to output, and allocate Polygons on heap, as they will not be changed again. for (std::pair tup : influence_areas) { const SupportElement elem = tup.first; + validate_range(tup.second); + validate_range(safeUnion(tup.second)); Polygons* new_area = new Polygons(safeUnion(tup.second)); SupportElement* next = new SupportElement(elem, new_area); move_bounds[layer_idx - 1].emplace(next); diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 894705a32..ecbc0c04c 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -43,7 +43,7 @@ namespace Slic3r { -using LayerIndex = size_t; +using LayerIndex = int; //FIXME class Print; From 167125a813f41ea44f777d43366e3e6349e0b539 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 Aug 2022 13:39:20 +0200 Subject: [PATCH 14/29] Fixed disabling of TBB parallelization on newer TBB --- src/libslic3r/utils.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 371b8eb11..d4fa30b4f 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -129,7 +129,9 @@ void disable_multi_threading() { // Disable parallelization so the Shiny profiler works #ifdef TBB_HAS_GLOBAL_CONTROL - tbb::global_control(tbb::global_control::max_allowed_parallelism, 1); + { + static tbb::global_control gc(tbb::global_control::max_allowed_parallelism, 1); + } #else // TBB_HAS_GLOBAL_CONTROL static tbb::task_scheduler_init *tbb_init = new tbb::task_scheduler_init(1); UNUSED(tbb_init); From fd0c84319e6847a97fcef389a241c455785997df Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 2 Aug 2022 14:09:44 +0200 Subject: [PATCH 15/29] WIP Tree Supports: Fixed one integer overflow. --- src/libslic3r/TreeSupport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 689e57904..f14f6b343 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -592,7 +592,7 @@ static std::optional> polyline_sample_next_point_at_dis } std::rotate(part.begin(), part.begin() + optimal_start_index, part.end() - 1); part[part.size() - 1] = part[0]; // restore that property that this polyline ends where it started. - optimal_end_index = (optimal_end_index - optimal_start_index + part.size() - 1) % (part.size() - 1); + optimal_end_index = (part.size() + optimal_end_index - optimal_start_index - 1) % (part.size() - 1); } while (line.size() < min_points && current_distance >= scaled(0.1)) From b0af5524557d81087d89aae95b64e6e01204624a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 3 Aug 2022 09:40:30 +0200 Subject: [PATCH 16/29] WIP TreeSupports: Little optimization - don't use std::function, don't make unnecessary copies of Polygons --- src/libslic3r/TreeSupport.cpp | 42 ++++++++++++++++++----------------- src/libslic3r/TreeSupport.hpp | 3 ++- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index f14f6b343..cf74d2ab8 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -928,12 +928,15 @@ void TreeSupport::generateInitialAreas( if (! layer_has_overhangs(*print_object.get_layer(layer_idx + z_distance_delta))) continue; // take the least restrictive avoidance possible - Polygons relevant_forbidden = (mesh_config.support_rests_on_model ? - (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides_z) : - m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides_z)) : - m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides_z)); - // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. - relevant_forbidden = offset(union_ex(relevant_forbidden), scaled(0.005), jtMiter, 1.2); + Polygons relevant_forbidden; + { + const Polygons &relevant_forbidden_raw = (mesh_config.support_rests_on_model ? + (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides_z) : + m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides_z)) : + m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides_z)); + // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. + relevant_forbidden = offset(union_ex(relevant_forbidden_raw), scaled(0.005), jtMiter, 1.2); + } auto generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) -> Polylines { const coord_t support_infill_distance = roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance; @@ -974,8 +977,7 @@ void TreeSupport::generateInitialAreas( 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) - { + auto evaluateRoofWillGenerate = [&](std::pair p) { //FIXME Vojtech: The circle is just shifted, it has a known size, the infill should fit all the time! #if 0 Polygon roof_circle; @@ -1080,9 +1082,9 @@ void TreeSupport::generateInitialAreas( for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) { // get least restricted avoidance for layer_idx-lag_ctr - Polygons relevant_forbidden_below = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, false, !xy_overrides_z)); + const Polygons &relevant_forbidden_below = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, false, !xy_overrides_z)); // it is not required to offset the forbidden area here as the points wont change: If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. - std::function)> evaluatePoint = [&](std::pair p) { return contains(relevant_forbidden_below, p.first); }; + auto evaluatePoint = [&](std::pair p) { return contains(relevant_forbidden_below, p.first); }; std::pair split = splitLines(overhang_lines, evaluatePoint); // keep all lines that are invalid overhang_lines = split.first; @@ -1125,9 +1127,12 @@ void TreeSupport::generateInitialAreas( if (roof_allowed_for_this_part) { for (dtt_roof = 0; dtt_roof < support_roof_layers && layer_idx - dtt_roof >= 1; dtt_roof++) { // here the roof is handled. If roof can not be added the branches will try to not move instead - Polygons forbidden_next = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides_z));\ - // prevent rounding errors down the line - forbidden_next = offset(union_ex(forbidden_next), scaled(0.005), jtMiter, 1.2); + Polygons forbidden_next; + { + const Polygons &forbidden_next_raw = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides_z));\ + // prevent rounding errors down the line + forbidden_next = offset(union_ex(forbidden_next_raw), scaled(0.005), jtMiter, 1.2); + } Polygons overhang_outset_next = diff(overhang_outset, forbidden_next); if (area(overhang_outset_next) < mesh_group_settings.minimum_roof_area) { // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter @@ -1632,8 +1637,7 @@ std::optional TreeSupport::increaseSingleArea(AreaI check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; if (settings.increase_radius && area(check_layer_data) > tiny_area_threshold) { - std::function validWithRadius = [&](coord_t next_radius) - { + auto validWithRadius = [&](coord_t next_radius) { if (m_volumes.ceilRadius(next_radius, settings.use_min_distance) <= m_volumes.ceilRadius(radius, settings.use_min_distance)) return true; @@ -1707,7 +1711,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to SupportElement elem(parent); // also increases dtt - Polygons wall_restriction = m_volumes.getWallRestriction(m_config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist); // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. + const Polygons &wall_restriction = m_volumes.getWallRestriction(m_config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist); // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. Polygons to_bp_data, to_model_data; coord_t radius = m_config.getCollisionRadius(elem); @@ -1757,8 +1761,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to // Determine in which order configurations are checked if they result in a valid influence area. Check will stop if a valid area is found std::deque order; - std::function insertSetting = [&](AreaIncreaseSettings settings, bool back) - { + auto insertSetting = [&](AreaIncreaseSettings settings, bool back) { if (std::find(order.begin(), order.end(), settings) == order.end()) { if (back) order.emplace_back(settings); @@ -2213,8 +2216,7 @@ void TreeSupport::generateBranchAreas(std::vector generateArea = [&](coord_t aoffset) - { + auto generateArea = [&](coord_t aoffset) { Polygons poly; for (std::pair movement : movement_directions) { diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index ecbc0c04c..221bf3a9b 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -403,7 +403,8 @@ public: xy_distance = std::max(xy_distance, xy_min_distance); - std::function&, SupportMaterialInterfacePattern)> getInterfaceAngles = [&](std::vector& angles, SupportMaterialInterfacePattern pattern) { // (logic) from getInterfaceAngles in FFFGcodeWriter. + // (logic) from getInterfaceAngles in FFFGcodeWriter. + auto getInterfaceAngles = [&](std::vector& angles, SupportMaterialInterfacePattern pattern) { if (angles.empty()) { if (pattern == SupportMaterialInterfacePattern::smipConcentric) From af7108f7921d13d45731ab96ae4bfca57285c484 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 15 Aug 2022 08:41:24 +0200 Subject: [PATCH 17/29] WIP Tree Supports: Bunch of fixes and optimizations --- src/libslic3r/SupportMaterial.cpp | 58 +++--- src/libslic3r/SupportMaterial.hpp | 28 +-- src/libslic3r/TreeModelVolumes.cpp | 111 ++++------- src/libslic3r/TreeModelVolumes.hpp | 8 +- src/libslic3r/TreeSupport.cpp | 300 ++++++++++++++++++++++------- src/libslic3r/TreeSupport.hpp | 12 +- 6 files changed, 330 insertions(+), 187 deletions(-) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 1c2b785e3..340449ec8 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -61,7 +61,7 @@ namespace Slic3r { //#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 #define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. -#ifdef SLIC3R_DEBUG +#if 1 //#ifdef SLIC3R_DEBUG const char* support_surface_type_to_color_name(const SupporLayerType surface_type) { switch (surface_type) { @@ -543,7 +543,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled. // There is also a 1st intermediate layer containing bases of support columns. // Inflate the bases of the support columns and create the raft base under the object. - SupportGeneratorLayersPtr raft_layers = this->generate_raft_base(object, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); + SupportGeneratorLayersPtr raft_layers = generate_raft_base(object, m_support_params, m_slicing_params, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); #ifdef SLIC3R_DEBUG for (const SupportGeneratorLayer *l : interface_layers) @@ -573,6 +573,9 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // intermediate_layers.clear(); // interface_layers.clear(); +#ifdef SLIC3R_DEBUG + SupportGeneratorLayersPtr layers_sorted = +#endif // SLIC3R_DEBUG generate_support_layers(object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); BOOST_LOG_TRIVIAL(info) << "Support generator - Generating tool paths"; @@ -585,7 +588,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. int j = i + 1; coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - bool empty = true; + bool empty = layers_sorted[i]->polygons.empty(); for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) if (!layers_sorted[j]->polygons.empty()) empty = false; @@ -615,7 +618,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. int j = i + 1; coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - bool empty = true; + bool empty = layers_sorted[i]->polygons.empty(); for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) if (! layers_sorted[j]->polygons.empty()) empty = false; @@ -2866,13 +2869,15 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; } -SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base( - const PrintObject &object, +SupportGeneratorLayersPtr generate_raft_base( + const PrintObject &object, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, const SupportGeneratorLayersPtr &top_contacts, const SupportGeneratorLayersPtr &interface_layers, const SupportGeneratorLayersPtr &base_interface_layers, const SupportGeneratorLayersPtr &base_layers, - SupportGeneratorLayerStorage &layer_storage) const + SupportGeneratorLayerStorage &layer_storage) { // If there is brim to be generated, calculate the trimming regions. Polygons brim; @@ -2904,22 +2909,22 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base( } // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. - const float inflate_factor_fine = float(scale_((m_slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); + const float inflate_factor_fine = float(scale_((slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); SupportGeneratorLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); - if (contacts != nullptr && contacts->print_z > std::max(m_slicing_params.first_print_layer_height, m_slicing_params.raft_contact_top_z) + EPSILON) + if (contacts != nullptr && contacts->print_z > std::max(slicing_params.first_print_layer_height, slicing_params.raft_contact_top_z) + EPSILON) // This is not the raft contact layer. contacts = nullptr; - if (interfaces != nullptr && interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) + if (interfaces != nullptr && interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) // This is not the raft column base layer. interfaces = nullptr; - if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) + if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) // This is not the raft column base layer. base_interfaces = nullptr; - if (columns_base != nullptr && columns_base->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) + if (columns_base != nullptr && columns_base->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON) // This is not the raft interface layer. columns_base = nullptr; @@ -2934,7 +2939,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base( // Output vector. SupportGeneratorLayersPtr raft_layers; - if (m_slicing_params.raft_layers() > 1) { + if (slicing_params.raft_layers() > 1) { Polygons base; Polygons columns; if (columns_base != nullptr) { @@ -2951,30 +2956,30 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base( // Do not add the raft contact layer, only add the raft layers below the contact layer. // Insert the 1st layer. { - SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, (m_slicing_params.base_raft_layers > 0) ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface); + SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, (slicing_params.base_raft_layers > 0) ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface); raft_layers.push_back(&new_layer); - new_layer.print_z = m_slicing_params.first_print_layer_height; - new_layer.height = m_slicing_params.first_print_layer_height; + new_layer.print_z = slicing_params.first_print_layer_height; + new_layer.height = slicing_params.first_print_layer_height; new_layer.bottom_z = 0.; new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base; } // Insert the base layers. - for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) { + for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { coordf_t print_z = raft_layers.back()->print_z; SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, SupporLayerType::RaftBase); raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + m_slicing_params.base_raft_layer_height; - new_layer.height = m_slicing_params.base_raft_layer_height; + new_layer.print_z = print_z + slicing_params.base_raft_layer_height; + new_layer.height = slicing_params.base_raft_layer_height; new_layer.bottom_z = print_z; new_layer.polygons = base; } // Insert the interface layers. - for (size_t i = 1; i < m_slicing_params.interface_raft_layers; ++ i) { + for (size_t i = 1; i < slicing_params.interface_raft_layers; ++ i) { coordf_t print_z = raft_layers.back()->print_z; SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, SupporLayerType::RaftInterface); raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + m_slicing_params.interface_raft_layer_height; - new_layer.height = m_slicing_params.interface_raft_layer_height; + new_layer.print_z = print_z + slicing_params.interface_raft_layer_height; + new_layer.height = slicing_params.interface_raft_layer_height; new_layer.bottom_z = print_z; new_layer.polygons = interface_polygons; //FIXME misusing contact_polygons for support columns. @@ -2982,12 +2987,12 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base( } } else { if (columns_base != nullptr) { - // Expand the bases of the support columns in the 1st layer. + // Expand the bases of the support columns in the 1st layer. Polygons &raft = columns_base->polygons; - Polygons trimming = offset(m_object->layers().front()->lslices, (float)scale_(m_support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); + Polygons trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (inflate_factor_1st_layer > SCALED_EPSILON) { // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. - auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width()))); + auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width()))); float step = inflate_factor_1st_layer / nsteps; for (int i = 0; i < nsteps; ++ i) raft = diff(expand(raft, step), trimming); @@ -3847,7 +3852,7 @@ void modulate_extrusion_by_overlapping_layers( extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); } -void generate_support_layers( +SupportGeneratorLayersPtr generate_support_layers( PrintObject &object, const SupportGeneratorLayersPtr &raft_layers, const SupportGeneratorLayersPtr &bottom_contacts, @@ -3921,6 +3926,7 @@ void generate_support_layers( } i = j; } + return layers_sorted; } void generate_support_toolpaths( diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp index e9baa4dbd..1e6d12f80 100644 --- a/src/libslic3r/SupportMaterial.hpp +++ b/src/libslic3r/SupportMaterial.hpp @@ -147,7 +147,20 @@ struct SupportParameters { bool with_sheath; }; -void generate_support_layers( +// Generate raft layers, also expand the 1st support layer +// in case there is no raft layer to improve support adhesion. +SupportGeneratorLayersPtr generate_raft_base( + const PrintObject &object, + const SupportParameters &support_params, + const SlicingParameters &slicing_params, + const SupportGeneratorLayersPtr &top_contacts, + const SupportGeneratorLayersPtr &interface_layers, + const SupportGeneratorLayersPtr &base_interface_layers, + const SupportGeneratorLayersPtr &base_layers, + SupportGeneratorLayerStorage &layer_storage); + +// returns sorted layers +SupportGeneratorLayersPtr generate_support_layers( PrintObject &object, const SupportGeneratorLayersPtr &raft_layers, const SupportGeneratorLayersPtr &bottom_contacts, @@ -170,6 +183,9 @@ void generate_support_toolpaths( const SupportGeneratorLayersPtr &interface_layers, const SupportGeneratorLayersPtr &base_interface_layers); +void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers); +void export_print_z_polygons_and_extrusions_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers, SupportLayer& support_layer); + // This class manages raft and supports for a single PrintObject. // Instantiated by Slic3r::Print::Object->_support_material() // This class is instantiated before the slicing starts as Object.pm will query @@ -226,16 +242,6 @@ private: SupportGeneratorLayersPtr &intermediate_layers, const std::vector &layer_support_areas) const; - // Generate raft layers, also expand the 1st support layer - // in case there is no raft layer to improve support adhesion. - SupportGeneratorLayersPtr generate_raft_base( - const PrintObject &object, - const SupportGeneratorLayersPtr &top_contacts, - const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers, - const SupportGeneratorLayersPtr &base_layers, - SupportGeneratorLayerStorage &layer_storage) const; - // Turn some of the base layers into base interface layers. // For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base // extruder to improve adhesion of the soluble filament to the base. diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index 57ef150d5..c94edca72 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -43,7 +43,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr } this->layer_height = scaled(config.layer_height.value); - this->resolution = scaled(print_config.resolution.value); + this->resolution = scaled(print_config.gcode_resolution.value); this->min_feature_size = scaled(config.min_feature_size.value); this->support_angle = M_PI / 2. - config.support_material_angle * M_PI / 180.; this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width(); @@ -127,12 +127,6 @@ TreeModelVolumes::TreeModelVolumes( } m_current_outline_idx = mesh_to_layeroutline_idx[current_mesh_idx]; - m_support_rests_on_model = false; - m_min_resolution = std::numeric_limits::max(); - for (auto data_pair : m_layer_outlines) { - m_support_rests_on_model |= ! data_pair.first.support_material_buildplate_only; - m_min_resolution = std::min(m_min_resolution, data_pair.first.resolution); - } #else { m_anti_overhang = print_object.slice_support_blockers(); @@ -141,13 +135,23 @@ TreeModelVolumes::TreeModelVolumes( m_layer_outlines.emplace_back(mesh_settings, std::vector{}); m_current_outline_idx = 0; std::vector &outlines = m_layer_outlines.front().second; - outlines.reserve(print_object.layer_count()); - for (const Layer *layer : print_object.layers()) - outlines.emplace_back(to_polygons(expolygons_simplify(layer->lslices, mesh_settings.resolution))); + outlines.assign(print_object.layer_count(), Polygons{}); + tbb::parallel_for(tbb::blocked_range(0, print_object.layer_count(), std::min(1, std::max(16, print_object.layer_count() / (8 * tbb::this_task_arena::max_concurrency())))), + [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) + outlines[layer_idx] = to_polygons(expolygons_simplify(print_object.get_layer(layer_idx)->lslices, mesh_settings.resolution)); + }); } #endif - const TreeSupport::TreeSupportSettings &config = m_layer_outlines[m_current_outline_idx].first; + m_support_rests_on_model = false; + m_min_resolution = std::numeric_limits::max(); + for (auto data_pair : m_layer_outlines) { + m_support_rests_on_model |= ! data_pair.first.support_material_buildplate_only; + m_min_resolution = std::min(m_min_resolution, data_pair.first.resolution); + } + + const TreeSupport::TreeSupportSettings config{ m_layer_outlines[m_current_outline_idx].first }; if (! config.support_xy_overrides_z) { m_current_min_xy_dist = config.xy_min_distance; if (TreeSupport::TreeSupportSettings::has_to_rely_on_min_xy_dist_only) @@ -492,12 +496,6 @@ coord_t TreeModelVolumes::getRadiusNextCeil(coord_t radius, bool min_xy_dist) co return ceiled_radius; } -[[nodiscard]] static inline Polygons simplify(const Polygons &polygons, coord_t resolution) -{ - //FIXME - return polygons; -} - #if 0 Polygons TreeModelVolumes::extractOutlineFromMesh(const PrintObject &print_object, LayerIndex layer_idx) const { @@ -535,13 +533,13 @@ void TreeModelVolumes::calculateCollision(std::deque keys) { tbb::parallel_for(tbb::blocked_range(0, keys.size()), [&](const tbb::blocked_range &range) { - for (size_t i = range.begin(); i < range.end(); ++ i) { - coord_t radius = keys[i].first; + for (size_t i = range.begin(); i != range.end(); ++ i) { + const coord_t radius = keys[i].first; + const size_t layer_idx = keys[i].second; RadiusLayerPair key(radius, 0); RadiusLayerPolygonCache data_outer; RadiusLayerPolygonCache data_placeable_outer; - for (size_t outline_idx = 0; outline_idx < m_layer_outlines.size(); outline_idx++) - { + for (size_t outline_idx = 0; outline_idx < m_layer_outlines.size(); ++outline_idx) { RadiusLayerPolygonCache data; RadiusLayerPolygonCache data_placeable; @@ -550,7 +548,7 @@ void TreeModelVolumes::calculateCollision(std::deque keys) const coord_t z_distance_bottom = m_layer_outlines[outline_idx].first.support_bottom_distance; const size_t z_distance_bottom_layers = round_up_divide(z_distance_bottom, layer_height); const coord_t z_distance_top_layers = round_up_divide(m_layer_outlines[outline_idx].first.support_top_distance, layer_height); - const LayerIndex max_required_layer = keys[i].second + std::max(coord_t(1), z_distance_top_layers); + const LayerIndex max_required_layer = layer_idx + std::max(coord_t(1), z_distance_top_layers); const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : m_layer_outlines[outline_idx].first.support_xy_distance; // technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all not currently processing meshes as this delta will be added at request time. // avoiding this would require saving each collision for each outline_idx separately. @@ -564,6 +562,7 @@ void TreeModelVolumes::calculateCollision(std::deque keys) if (min_layer_bottom < 0) min_layer_bottom = 0; + //FIXME parallel_for for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; layer_idx++) { key.second = layer_idx; Polygons collision_areas = m_machine_border; @@ -607,10 +606,10 @@ void TreeModelVolumes::calculateCollision(std::deque keys) } for (auto pair : data) - data_outer[pair.first] = union_(data_outer[pair.first], simplify(pair.second, m_min_resolution)); + data_outer[pair.first] = union_(data_outer[pair.first], polygons_simplify(pair.second, m_min_resolution)); if (radius == 0) { for (auto pair : data_placeable) - data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], simplify(pair.second, m_min_resolution)); + data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], polygons_simplify(pair.second, m_min_resolution)); } } @@ -651,8 +650,7 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke coord_t radius = key.first; coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - ceilRadius(radius, true); // this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area. - Polygons col = offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound, scaled(0.01)); - col = simplify(col, m_min_resolution); + Polygons col = polygons_simplify(offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution), m_min_resolution); data[RadiusLayerPair(radius, layer_idx)] = col; } @@ -662,17 +660,6 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke }); } -// 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 -static Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) -{ - const size_t steps = std::abs(distance / max_safe_step_distance); - assert(int64_t(distance) * int64_t(max_safe_step_distance) >= 0); - ExPolygons ret = union_ex(me); - for (size_t i = 0; i < steps; ++ i) - ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision)); - return union_(offset(ret, distance % max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision); -} - void TreeModelVolumes::calculateAvoidance(std::deque keys) { // For every RadiusLayer pair there are 3 avoidances that have to be calculate, calculated in the same paralell_for loop for better paralellisation. @@ -695,9 +682,7 @@ void TreeModelVolumes::calculateAvoidance(std::deque keys) continue; const coord_t offset_speed = slow ? m_max_move_slow : m_max_move; - const coord_t max_step_move = std::max(1.9 * radius, m_current_min_xy_dist * 1.9); RadiusLayerPair key(radius, 0); - Polygons latest_avoidance; LayerIndex start_layer; { std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_slow : holefree ? m_critical_avoidance_cache_holefree : m_critical_avoidance_cache)); @@ -707,19 +692,19 @@ void TreeModelVolumes::calculateAvoidance(std::deque keys) BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; 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. - + // Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1 + start_layer = std::max(start_layer, LayerIndex(1)); + // minDist as the delta was already added, also avoidance for layer 0 will return the collision. + Polygons latest_avoidance = getAvoidance(radius, start_layer - 1, type, false, true); // ### main loop doing the calculation - for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) { + for (LayerIndex layer = start_layer; layer <= max_required_layer; ++ layer) { key.second = layer; - Polygons col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ? + const Polygons &col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ? getCollisionHolefree(radius, layer, true) : getCollision(radius, layer, true); - latest_avoidance = safeOffset(latest_avoidance, -offset_speed, ClipperLib::jtRound, -max_step_move, col); - latest_avoidance = simplify(latest_avoidance, m_min_resolution); + latest_avoidance = polygons_simplify(union_(offset(union_ex(latest_avoidance), -offset_speed, ClipperLib::jtRound, m_min_resolution), col), m_min_resolution); data[layer] = std::pair(key, latest_avoidance); } @@ -770,7 +755,7 @@ void TreeModelVolumes::calculatePlaceables(std::deque keys) for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) { key.second = layer; Polygons placeable = getPlaceableAreas(0, layer); - placeable = simplify(placeable, m_min_resolution); // it is faster to do this here in each thread than once in calculateCollision. + placeable = polygons_simplify(placeable, m_min_resolution); // it is faster to do this here in each thread than once in calculateCollision. placeable = offset(union_ex(placeable), - radius, jtMiter, 1.2); data[layer] = std::pair(key, placeable); } @@ -814,8 +799,6 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque key getPlaceableAreas(radius, max_required_layer); // ensuring Placeableareas are calculated const coord_t offset_speed = slow ? m_max_move_slow : m_max_move; - const coord_t max_step_move = std::max(1.9 * radius, m_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); @@ -829,27 +812,17 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque key BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; continue; } + // Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1 start_layer = std::max(start_layer, LayerIndex(1)); - 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. - + // minDist as the delta was already added, also avoidance for layer 0 will return the collision. + Polygons latest_avoidance = getAvoidance(radius, start_layer - 1, type, true, true); // ### main loop doing the calculation - for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) - { + for (LayerIndex layer = start_layer; layer <= max_required_layer; ++ layer) { key.second = layer; - Polygons col = getCollision(radius, layer, true); - - if ((slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree) - { - col = getCollisionHolefree(radius, layer, true); - } - else - { - col = getCollision(radius, layer, true); - } - - latest_avoidance = diff(safeOffset(latest_avoidance, -offset_speed, ClipperLib::jtRound, -max_step_move, col), getPlaceableAreas(radius, layer)); - - latest_avoidance = simplify(latest_avoidance, m_min_resolution); + const Polygons &col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ? + getCollisionHolefree(radius, layer, true) : + getCollision(radius, layer, true); + latest_avoidance = polygons_simplify(diff(union_(offset(union_ex(latest_avoidance), -offset_speed, ClipperLib::jtRound, m_min_resolution), col), getPlaceableAreas(radius, layer)), m_min_resolution); data[layer] = std::pair(key, latest_avoidance); } @@ -929,12 +902,12 @@ void TreeModelVolumes::calculateWallRestrictions(std::deque key key.second = layer_idx; LayerIndex layer_idx_below = layer_idx - 1; Polygons wall_restriction = intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx_below, true)); // radius contains m_current_min_xy_dist_delta already if required - wall_restriction = simplify(wall_restriction, m_min_resolution); + wall_restriction = polygons_simplify(wall_restriction, m_min_resolution); data.emplace(key, wall_restriction); if (m_current_min_xy_dist_delta > 0) { Polygons wall_restriction_min = intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx_below, true)); - wall_restriction = simplify(wall_restriction_min, m_min_resolution); + wall_restriction = polygons_simplify(wall_restriction_min, m_min_resolution); data_min.emplace(key, wall_restriction_min); } } diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index cd1cc9722..c8b986a00 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -30,7 +30,7 @@ class PrintObject; struct TreeSupportMeshGroupSettings { TreeSupportMeshGroupSettings() = default; - TreeSupportMeshGroupSettings(const PrintObject &print_object); + explicit TreeSupportMeshGroupSettings(const PrintObject &print_object); /*********************************************************************/ /* Print parameters, not support specific: */ @@ -153,7 +153,7 @@ struct TreeSupportMeshGroupSettings { // Tree Support Branch Distance // How far apart the branches need to be when they touch the model. Making this distance small will cause // the tree support to touch the model at more points, causing better overhang but making support harder to remove. - coord_t support_tree_branch_distance { scaled(50.) }; + coord_t support_tree_branch_distance { scaled(1.) }; // Tree Support Branch Diameter // The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this. // minimum: 0.001, minimum warning: support_line_width * 2 @@ -204,7 +204,7 @@ class TreeModelVolumes { public: TreeModelVolumes() = default; - TreeModelVolumes(const PrintObject &print_object, const BuildVolume &build_volume, + explicit TreeModelVolumes(const PrintObject &print_object, const BuildVolume &build_volume, coord_t max_move, coord_t max_move_slow, size_t current_mesh_idx, double progress_multiplier, double progress_offset, const std::vector &additional_excluded_areas = {}); TreeModelVolumes(TreeModelVolumes&&) = default; TreeModelVolumes& operator=(TreeModelVolumes&&) = default; @@ -582,7 +582,7 @@ private: std::unique_ptr m_critical_progress { std::make_unique() }; }; -static Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision); +Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision); } diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index cf74d2ab8..dec094c6c 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -16,6 +16,7 @@ #include "MultiPoint.hpp" #include "Polygon.hpp" #include "Polyline.hpp" +#include "MutablePolygon.hpp" #include "SupportMaterial.hpp" #include @@ -92,6 +93,76 @@ static inline void validate_range(const LineInformations &lines) validate_range(l); } +static inline void clip_for_diff(const Polygon &src, const BoundingBox &bbox, Polygon &out) +{ + out.clear(); + const size_t cnt = src.points.size(); + if (cnt < 3) + return; + + enum class Side { + Left = 1, + Right = 2, + Top = 4, + Bottom = 8 + }; + + auto sides = [bbox](const Point &p) { + return int(p.x() < bbox.min.x()) * int(Side::Left) + + int(p.x() > bbox.max.x()) * int(Side::Right) + + int(p.y() < bbox.min.y()) * int(Side::Bottom) + + int(p.y() > bbox.max.y()) * int(Side::Top); + }; + + int sides_prev = sides(src.points.back()); + int sides_this = sides(src.points.front()); + const size_t last = cnt - 1; + for (size_t i = 0; i < last; ++ i) { + int sides_next = sides(src.points[i + 1]); + if (// This point is inside. Take it. + sides_this == 0 || + // Either this point is outside and previous or next is inside, or + // the edge possibly cuts corner of the bounding box. + (sides_prev & sides_this & sides_next) == 0) { + out.points.emplace_back(src.points[i]); + sides_prev = sides_this; + } else { + // All the three points (this, prev, next) are outside at the same side. + // Ignore this point. + } + sides_this = sides_next; + } + // For the last point, if src is completely outside bbox, then out.points will be empty. Just use the first point instead. + int sides_next = sides(out.points.empty() ? src.points.front() : out.points.front()); + if (// The last point is inside. Take it. + sides_this == 0 || + // Either this point is outside and previous or next is inside, or + // the edge possibly cuts corner of the bounding box. + (sides_prev & sides_this & sides_next) == 0) + out.points.emplace_back(src.points.back()); +} + +[[nodiscard]] static inline Polygon clip_for_diff(const Polygon &src, const BoundingBox &bbox) +{ + Polygon out; + clip_for_diff(src, bbox, out); + return out; +} + +[[nodiscard]] static inline Polygons clip_for_diff(const Polygons &src, const BoundingBox &bbox) +{ + Polygons out; + out.reserve(src.size()); + for (const Polygon &p : src) + out.emplace_back(clip_for_diff(p, bbox)); + return out; +} + +[[nodiscard]] static inline Polygons diff_clipped(const Polygons &src, const Polygons &clipping) +{ + return diff(src, clip_for_diff(clipping, get_extents(src).inflated(SCALED_EPSILON))); +} + static constexpr const auto tiny_area_threshold = sqr(scaled(0.001)); static std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) @@ -121,7 +192,7 @@ static std::vectorprint_z + EPSILON; + bool empty = layers_sorted[i]->polygons.empty(); + for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) + if (!layers_sorted[j]->polygons.empty()) + empty = false; + if (!empty) { + export_print_z_polygons_to_svg( + debug_out_path("support-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), + layers_sorted.data() + i, j - i); + export_print_z_polygons_and_extrusions_to_svg( + debug_out_path("support-w-fills-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), + layers_sorted.data() + i, j - i, + *print_object.support_layers()[layer_id]); + ++layer_id; + } + i = j; + } + } +#endif /* SLIC3R_DEBUG */ + ++ counter; } @@ -713,7 +818,7 @@ static std::optional> polyline_sample_next_point_at_dis (support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) : support_params.base_angle; - fill_params.density = float(roof ? support_params.interface_density : scaled(filler->spacing) / float(support_infill_distance)); + fill_params.density = float(roof ? support_params.interface_density : scaled(filler->spacing) / (scaled(filler->spacing) + float(support_infill_distance))); fill_params.dont_adjust = true; Polylines out; @@ -786,6 +891,19 @@ static std::optional> polyline_sample_next_point_at_dis return result; } +// ensures offsets are only done in sizes with a max step size per offset while adding the collision offset after each step, this ensures that areas cannot glitch through walls defined by the collision when offsetting to fast +[[nodiscard]] Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) +{ + const size_t steps = std::abs(distance / max_safe_step_distance); + assert(int64_t(distance) * int64_t(max_safe_step_distance) >= 0); + ExPolygons ret = union_ex(me); + Polygons collision_trimmed = clip_for_diff(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); + + for (size_t i = 0; i < steps; ++ i) + ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision_trimmed)); + return union_(offset(ret, distance % max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision_trimmed); +} + /*! * \brief Offsets (increases the area of) a polygons object in multiple steps to ensure that it does not lag through over a given obstacle. * \param me[in] Polygons object that has to be offset. @@ -799,12 +917,21 @@ static std::optional> polyline_sample_next_point_at_dis { bool do_final_difference = last_step_offset_without_check == 0; Polygons ret = safeUnion(me); // ensure sane input + + // Trim the collision polygons with the region of interest for diff() efficiency. + Polygons collision_trimmed_buffer; + auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons& { + if (collision_trimmed_buffer.empty() && ! collision.empty()) + collision_trimmed_buffer = clip_for_diff(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); + return collision_trimmed_buffer; + }; + if (distance == 0) - return do_final_difference ? diff(ret, collision) : union_(ret); + return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); if (safe_step_size < 0 || last_step_offset_without_check < 0) { BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!"; TreeSupport::showError("Negative offset distance... How did you manage this ?", true); - return do_final_difference ? diff(ret, collision) : union_(ret); + return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); } coord_t step_size = safe_step_size; @@ -829,7 +956,7 @@ static std::optional> polyline_sample_next_point_at_dis } // offset in steps for (size_t i = 0; i < steps; i++) { - ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled(0.01)), collision); + ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled(0.01)), collision_trimmed()); // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. if (i % 10 == 7) ret = polygons_simplify(ret, scaled(0.015)); @@ -841,10 +968,23 @@ static std::optional> polyline_sample_next_point_at_dis ret = polygons_simplify(ret, scaled(0.015)); if (do_final_difference) - ret = diff(ret, collision); + ret = diff(ret, collision_trimmed()); return union_(ret); } +static inline SupportGeneratorLayer& layer_initialize( + SupportGeneratorLayer &layer_new, + const SupporLayerType layer_type, + const SlicingParameters &slicing_params, + const size_t layer_idx) +{ + layer_new.layer_type = layer_type; + layer_new.print_z = slicing_params.object_print_z_min + slicing_params.first_object_layer_height + layer_idx * slicing_params.layer_height; + layer_new.height = layer_idx == 0 ? slicing_params.first_object_layer_height : slicing_params.layer_height; + layer_new.bottom_z = layer_idx == 0 ? slicing_params.object_print_z_min : layer_new.print_z - layer_new.height; + return layer_new; +} + // Using the std::deque as an allocator. inline SupportGeneratorLayer& layer_allocate( std::deque &layer_storage, @@ -852,13 +992,9 @@ inline SupportGeneratorLayer& layer_allocate( const SlicingParameters &slicing_params, size_t layer_idx) { + //FIXME take raft into account. layer_storage.push_back(SupportGeneratorLayer()); - SupportGeneratorLayer *layer_new = &layer_storage.back(); - layer_new->layer_type = layer_type; - layer_new->print_z = slicing_params.first_print_layer_height + std::max(0, int(layer_idx) - 1) * slicing_params.layer_height; - layer_new->height = slicing_params.layer_height; - layer_new->bottom_z = layer_idx == 0 ? 0. : layer_new->print_z - slicing_params.layer_height; - return *layer_new; + return layer_initialize(layer_storage.back(), layer_type, slicing_params, layer_idx); } inline SupportGeneratorLayer& layer_allocate( @@ -868,15 +1004,9 @@ inline SupportGeneratorLayer& layer_allocate( const SlicingParameters &slicing_params, size_t layer_idx) { - layer_storage_mutex.lock(); + tbb::spin_mutex::scoped_lock lock(layer_storage_mutex); layer_storage.push_back(SupportGeneratorLayer()); - SupportGeneratorLayer *layer_new = &layer_storage.back(); - layer_storage_mutex.unlock(); - layer_new->layer_type = layer_type; - layer_new->print_z = slicing_params.first_print_layer_height + std::max(0, int(layer_idx) - 1) * slicing_params.layer_height; - layer_new->height = slicing_params.layer_height; - layer_new->bottom_z = layer_idx == 0 ? 0. : layer_new->print_z - slicing_params.layer_height; - return *layer_new; + return layer_initialize(layer_storage.back(), layer_type, slicing_params, layer_idx); } void TreeSupport::generateInitialAreas( @@ -886,16 +1016,14 @@ void TreeSupport::generateInitialAreas( SupportGeneratorLayersPtr &top_interface_layers, SupportGeneratorLayerStorage &layer_storage) { - tbb::global_control(tbb::global_control::max_allowed_parallelism, 1); - Polygon base_circle; - const int base_radius = 10; + const auto base_radius = scaled(0.01); for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; ++ i) { const AngleRadians angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); base_circle.points.emplace_back(coord_t(cos(angle) * base_radius), coord_t(sin(angle) * base_radius)); } - TreeSupportSettings mesh_config(print_object); TreeSupportMeshGroupSettings mesh_group_settings(print_object); + TreeSupportSettings mesh_config{ mesh_group_settings }; SupportParameters support_params(print_object); const size_t z_distance_delta = mesh_config.z_distance_top_layers + 1; // To ensure z_distance_top_layers are left empty between the overhang (zeroth empty layer), the support has to be added z_distance_top_layers+1 layers below @@ -906,7 +1034,7 @@ void TreeSupport::generateInitialAreas( return; #endif - const coord_t connect_length = (mesh_config.support_line_width * 100. / mesh_group_settings.support_tree_top_rate) + std::max(2 * mesh_config.min_radius - 1.0 * mesh_config.support_line_width, 0.0); + const coord_t connect_length = (mesh_config.support_line_width * 100. / mesh_group_settings.support_tree_top_rate) + std::max(2. * mesh_config.min_radius - 1.0 * mesh_config.support_line_width, 0.0); const coord_t circle_length_to_half_linewidth_change = mesh_config.min_radius < mesh_config.support_line_width ? mesh_config.min_radius / 2 : sqrt(sqr(mesh_config.min_radius) - sqr(mesh_config.min_radius - mesh_config.support_line_width / 2)); // 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 extra_outset = std::max(coord_t(0), mesh_config.min_radius - mesh_config.support_line_width) + (xy_overrides_z ? 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. const size_t support_roof_layers = mesh_group_settings.support_roof_enable ? (mesh_group_settings.support_roof_height + mesh_config.layer_height / 2) / mesh_config.layer_height : 0; @@ -1191,7 +1319,7 @@ void TreeSupport::generateInitialAreas( // as some support is better than none. Polygons reduced_overhang_outset = offset(union_ex(overhang_outset), -mesh_config.support_line_width / 2.2, jtMiter, 1.2); polylines = ensureMaximumDistancePolyline( - to_polylines(!reduced_overhang_outset.empty() && area(offset(diff_ex(overhang_outset, reduced_overhang_outset), std::max(mesh_config.support_line_width, connect_length), jtMiter, 1.2)) < 1 ? + to_polylines(!reduced_overhang_outset.empty() && area(offset(diff_ex(overhang_outset, reduced_overhang_outset), std::max(mesh_config.support_line_width, connect_length), jtMiter, 1.2)) < sqr(scaled(0.001)) ? reduced_overhang_outset : overhang_outset), connect_length, min_support_points); @@ -1466,7 +1594,7 @@ static void mergeHelper( erase.emplace_back(reduced_check_iter->first); erase.emplace_back(influence_iter->first); - Polygons merge = diff(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound, scaled(0.01)), volumes.getCollision(0, layer_idx - 1)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. + Polygons merge = diff_clipped(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound, scaled(0.01)), volumes.getCollision(0, layer_idx - 1)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. reduced_aabb.erase(reduced_check_iter->first); // this invalidates reduced_check_iter reduced_aabb.emplace(key, get_extents(merge)); @@ -1547,7 +1675,7 @@ static void mergeInfluenceAreas( for (size_t idx = range.begin(); idx < range.end(); ++ idx) { // +=2 as in the beginning only uneven buckets will be filled size_t bucket_idx = 2 * idx + 1; - for (const std::pair& input_pair : buckets_area[bucket_idx]) + for (const std::pair& input_pair : buckets_area[bucket_idx]) buckets_aabb[bucket_idx].emplace(input_pair.first, get_extents(input_pair.second).inflated(config.getRadius(input_pair.first))); } }); @@ -1577,11 +1705,11 @@ static void mergeInfluenceAreas( to_model_areas.erase(del); influence_areas.erase(del); } - for (const std::pair &tup : insert_main[i / 2]) + for (const std::pair &tup : insert_main[i / 2]) to_bp_areas.emplace(std::move(tup)); - for (const std::pair &tup : insert_secondary[i / 2]) + for (const std::pair &tup : insert_secondary[i / 2]) to_model_areas.emplace(std::move(tup)); - for (const std::pair &tup : insert_influence[i / 2]) + for (const std::pair &tup : insert_influence[i / 2]) influence_areas.emplace(std::move(tup)); } @@ -1614,7 +1742,7 @@ std::optional TreeSupport::increaseSingleArea(AreaI increased = *parent->area; if (mergelayer || current_elem.to_buildplate) { - to_bp_data = safeUnion(diff(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); + to_bp_data = safeUnion(diff_clipped(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); if (! current_elem.to_buildplate && area(to_bp_data) > tiny_area_threshold) { // mostly happening in the tip, but with merges one should check every time, just to be sure. current_elem.to_buildplate = true; // sometimes nodes that can reach the buildplate are marked as cant reach, tainting subtrees. This corrects it. @@ -1623,14 +1751,14 @@ std::optional TreeSupport::increaseSingleArea(AreaI } if (m_config.support_rests_on_model) { if (mergelayer || current_elem.to_model_gracious) - to_model_data = safeUnion(diff(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); + to_model_data = safeUnion(diff_clipped(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); if (!current_elem.to_model_gracious) { if (mergelayer && area(to_model_data) >= tiny_area_threshold) { current_elem.to_model_gracious = true; BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong non gracious value on layer " << layer_idx - 1 << " targeting " << current_elem.target_height << " with radius " << radius; } else - to_model_data = safeUnion(diff(increased, m_volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance))); + to_model_data = safeUnion(diff_clipped(increased, m_volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance))); } } @@ -1643,10 +1771,10 @@ std::optional TreeSupport::increaseSingleArea(AreaI Polygons to_bp_data_2; if (current_elem.to_buildplate) - to_bp_data_2 = diff(increased, m_volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)); // regular union as output will not be used later => this area should always be a subset of the safeUnion one (i think) + to_bp_data_2 = diff_clipped(increased, m_volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)); // regular union as output will not be used later => this area should always be a subset of the safeUnion one (i think) Polygons to_model_data_2; if (m_config.support_rests_on_model && !current_elem.to_buildplate) - to_model_data_2 = diff(increased, + to_model_data_2 = diff_clipped(increased, current_elem.to_model_gracious ? m_volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : m_volumes.getCollision(next_radius, layer_idx - 1, settings.use_min_distance)); @@ -1683,9 +1811,9 @@ std::optional TreeSupport::increaseSingleArea(AreaI if (ceil_radius_before != m_volumes.ceilRadius(radius, settings.use_min_distance)) { if (current_elem.to_buildplate) - to_bp_data = safeUnion(diff(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); + to_bp_data = safeUnion(diff_clipped(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); if (m_config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) - to_model_data = safeUnion(diff(increased, + to_model_data = safeUnion(diff_clipped(increased, current_elem.to_model_gracious ? m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : m_volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance) @@ -1854,8 +1982,12 @@ void TreeSupport::increaseAreas(std::unordered_map& to Polygons lines_offset = offset(to_polylines(*parent->area), scaled(0.005), jtMiter, 1.2); Polygons base_error_area = union_(*parent->area, lines_offset); result = increaseSingleArea(settings, layer_idx, parent, base_error_area, to_bp_data, to_model_data, inc_wo_collision, (m_config.maximum_move_distance + extra_speed) * 1.5, mergelayer); - BOOST_LOG_TRIVIAL(error) << - "Influence area could not be increased! Data about the Influence area: " +#ifdef TREE_SUPPORT_SHOW_ERRORS + BOOST_LOG_TRIVIAL(error) +#else // TREE_SUPPORT_SHOW_ERRORS + BOOST_LOG_TRIVIAL(warning) +#endif // TREE_SUPPORT_SHOW_ERRORS + << "Influence area could not be increased! Data about the Influence area: " "Radius: " << radius << " at layer: " << layer_idx - 1 << " NextTarget: " << elem.next_height << " Distance to top: " << elem.distance_to_top << " Elephant foot increases " << elem.elephant_foot_increases << " use_min_xy_dist " << elem.use_min_xy_dist << " to buildplate " << elem.to_buildplate << " gracious " << elem.to_model_gracious << " safe " << elem.can_use_safe_radius << " until move " << elem.dont_move_until << " \n " @@ -1883,7 +2015,12 @@ void TreeSupport::increaseAreas(std::unordered_map& to if (!settings.use_min_distance) elem.use_min_xy_dist = false; if (!settings.no_error) - BOOST_LOG_TRIVIAL(error) << "Trying to keep area by moving faster than intended: Success"; +#ifdef TREE_SUPPORT_SHOW_ERRORS + BOOST_LOG_TRIVIAL(error) +#else // TREE_SUPPORT_SHOW_ERRORS + BOOST_LOG_TRIVIAL(info) +#endif // TREE_SUPPORT_SHOW_ERRORS + << "Trying to keep area by moving faster than intended: Success"; break; } else if (!settings.no_error) @@ -1891,7 +2028,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to } if (add) { - Polygons max_influence_area = safeUnion(diff(inc_wo_collision, m_volumes.getCollision(radius, layer_idx - 1, elem.use_min_xy_dist)), safeUnion(to_bp_data, to_model_data)); // union seems useless, but some rounding errors somewhere can cause to_bp_data to be slightly bigger than it should be + Polygons max_influence_area = safeUnion(diff_clipped(inc_wo_collision, m_volumes.getCollision(radius, layer_idx - 1, elem.use_min_xy_dist)), safeUnion(to_bp_data, to_model_data)); // union seems useless, but some rounding errors somewhere can cause to_bp_data to be slightly bigger than it should be { std::lock_guard critical_section_newLayer(critical_sections); if (bypass_merge) { @@ -1981,8 +2118,8 @@ void TreeSupport::createLayerPathing(std::vector>& mov new_element = !move_bounds[layer_idx - 1].empty(); // Save calculated elements to output, and allocate Polygons on heap, as they will not be changed again. - for (std::pair tup : influence_areas) { - const SupportElement elem = tup.first; + for (const std::pair &tup : influence_areas) { + const SupportElement &elem = tup.first; validate_range(tup.second); validate_range(safeUnion(tup.second)); Polygons* new_area = new Polygons(safeUnion(tup.second)); @@ -2172,7 +2309,10 @@ void TreeSupport::createNodesFromArea(std::vector>& mo } } -void TreeSupport::generateBranchAreas(std::vector>& linear_data, std::vector>& layer_tree_polygons, const std::map& inverse_tree_order) +void TreeSupport::generateBranchAreas( + std::vector> &linear_data, + std::vector> &layer_tree_polygons, + const std::map &inverse_tree_order) { #ifdef SLIC3R_TREESUPPORTS_PROGRESS double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC; @@ -2186,7 +2326,7 @@ void TreeSupport::generateBranchAreas(std::vector linear_inserts(linear_data.size()); - + #ifdef SLIC3R_TREESUPPORTS_PROGRESS const size_t progress_inserts_check_interval = linear_data.size() / progress_report_steps; #endif // SLIC3R_TREESUPPORTS_PROGRESS @@ -2195,28 +2335,30 @@ void TreeSupport::generateBranchAreas(std::vector(0, linear_data.size()), [&](const tbb::blocked_range &range) { for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - - SupportElement* elem = linear_data[idx].second; - coord_t radius = m_config.getRadius(*elem); - bool parent_uses_min = false; - SupportElement* child_elem = inverse_tree_order.count(elem) ? inverse_tree_order.at(elem) : nullptr; + const LayerIndex layer_idx = linear_data[idx].first; + const SupportElement *elem = linear_data[idx].second; + const auto it_elem = inverse_tree_order.find(const_cast(elem)); + const SupportElement* child_elem = it_elem == inverse_tree_order.end() ? nullptr : it_elem->second; + const coord_t radius = m_config.getRadius(*elem); + bool parent_uses_min = false; // Calculate multiple ovalized circles, to connect with every parent and child. Also generate regular circle for the current layer. Merge all these into one area. std::vector> movement_directions{ std::pair(Point(0, 0), radius) }; if (!elem->skip_ovalisation) { if (child_elem != nullptr) { - Point movement = (child_elem->result_on_layer - elem->result_on_layer); + const 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(m_config.getRadius(parent), m_config.support_line_width)); + for (SupportElement *parent : elem->parents) { + const Point movement = parent->result_on_layer - elem->result_on_layer; + movement_directions.emplace_back(movement, std::max(m_config.getRadius(*parent), m_config.support_line_width)); parent_uses_min |= parent->use_min_xy_dist; } } double max_speed = 0; - auto generateArea = [&](coord_t aoffset) { + auto generateArea = [&volumes = m_volumes, layer_idx, elem, &branch_circle, branch_radius = m_config.branch_radius, support_line_width = m_config.support_line_width, &movement_directions, &max_speed, parent_uses_min]( + coord_t aoffset) { Polygons poly; for (std::pair movement : movement_directions) { @@ -2224,10 +2366,10 @@ void TreeSupport::generateBranchAreas(std::vectorresult_on_layer + movement.first / 2; - const double moveX = movement.first.x() / (used_scale * m_config.branch_radius); - const double moveY = movement.first.y() / (used_scale * m_config.branch_radius); + const double moveX = movement.first.x() / (used_scale * branch_radius); + const double moveY = movement.first.y() / (used_scale * branch_radius); const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); double matrix[] = { @@ -2242,8 +2384,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 = diff_clipped(offset(union_(poly), std::min(coord_t(50), support_line_width / 4), jtMiter, 1.2), + volumes.getCollision(0, layer_idx, parent_uses_min || elem->use_min_xy_dist)); // There seem to be some rounding errors, causing a branch to be a tiny bit further away from the model that it has to be. This can cause the tip to be slightly further away front the overhang (x/y wise) than optimal. This fixes it, and for every other part, 0.05mm will not be noticed. return poly; }; @@ -2277,7 +2419,7 @@ void TreeSupport::generateBranchAreas(std::vectoruse_min_xy_dist)); + linear_inserts[idx] = diff_clipped(linear_inserts[idx], m_volumes.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist)); } } } @@ -2405,7 +2547,7 @@ void TreeSupport::dropNonGraciousAreas( Polygons rest_support = layer_tree_polygons[linear_data[idx].first][elem]; LayerIndex counter = 1; while (area(rest_support) > tiny_area_threshold && counter < linear_data[idx].first) { - rest_support = diff(rest_support, m_volumes.getCollision(0, linear_data[idx].first - counter)); + rest_support = diff_clipped(rest_support, m_volumes.getCollision(0, linear_data[idx].first - counter)); dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support); counter++; } @@ -2436,7 +2578,9 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. - support_layer_storage[layer_idx] = union_(support_layer_storage[layer_idx]); //FIXME .smooth(50); + support_layer_storage[layer_idx] = smooth_outward(union_(support_layer_storage[layer_idx]), m_config.support_line_width); //FIXME was .smooth(50); + //smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : + // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. support_layer_storage[layer_idx] = polygons_simplify(support_layer_storage[layer_idx], std::min(scaled(0.03), double(m_config.resolution))); // Subtract support lines of the branches from the roof @@ -2488,7 +2632,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( // Subtract support floors from the support area and add them to the support floor instead. if (m_config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) { SupportGeneratorLayer*& support_bottom = bottom_contacts[layer_idx]; - Polygons layer_outset = diff( + Polygons layer_outset = diff_clipped( m_config.support_bottom_offset > 0 ? offset(support_layer_storage[layer_idx], m_config.support_bottom_offset, jtMiter, 1.2) : support_layer_storage[layer_idx], m_volumes.getCollision(0, layer_idx, false)); Polygons floor_layer; @@ -2507,7 +2651,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( if (support_bottom == nullptr) support_bottom = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::BottomContact, print_object.slicing_parameters(), layer_idx); support_bottom->polygons = union_(floor_layer, support_bottom->polygons); - support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], offset(support_bottom->polygons, scaled(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support. + support_layer_storage[layer_idx] = diff_clipped(support_layer_storage[layer_idx], offset(support_bottom->polygons, scaled(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support. } } @@ -2579,9 +2723,21 @@ void TreeSupport::drawAreas( append(support_layer_storage[pair.first], std::move(pair.second)); // single threaded combining all support areas to the right layers. ONLY COPYS DATA! - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()); ++ layer_idx) - for (std::pair data_pair : layer_tree_polygons[layer_idx]) - append(data_pair.first->missing_roof_layers > data_pair.first->distance_to_top ? support_roof_storage[layer_idx] : support_layer_storage[layer_idx], std::move(data_pair.second)); + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()); ++ layer_idx) { + auto &this_layer_tree_polygons = layer_tree_polygons[layer_idx]; + auto &this_roofs = support_roof_storage[layer_idx]; + auto &this_layers = support_layer_storage[layer_idx]; + size_t cnt_roofs = 0; + size_t cnt_layers = 0; + for (const std::pair &data_pair : this_layer_tree_polygons) + ++ (data_pair.first->missing_roof_layers > data_pair.first->distance_to_top ? cnt_roofs : cnt_layers); + this_roofs.reserve(this_roofs.size() + cnt_roofs); + this_layers.reserve(this_layers.size() + cnt_layers); + for (const std::pair &data_pair : this_layer_tree_polygons) { + auto &src = const_cast(data_pair.second); + std::move(std::begin(src), std::end(src), std::back_inserter(data_pair.first->missing_roof_layers > data_pair.first->distance_to_top ? this_roofs : this_layers)); + } + } finalizeInterfaceAndSupportAreas(print_object, support_layer_storage, support_roof_storage, bottom_contacts, top_contacts, intermediate_layers, layer_storage); diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 221bf3a9b..3449a0f75 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -16,6 +16,8 @@ #include "BoundingBox.hpp" +// #define TREE_SUPPORT_SHOW_ERRORS + #define SUPPORT_TREE_CIRCLE_RESOLUTION 25 // The number of vertices in each circle. #ifdef SLIC3R_TREESUPPORTS_PROGRESS @@ -108,7 +110,7 @@ public: struct SupportElement { - SupportElement( + explicit SupportElement( coord_t distance_to_top, size_t target_height, Point target_position, bool to_buildplate, bool to_model_gracious, bool use_min_xy_dist, size_t dont_move_until, bool supports_roof, bool can_use_safe_radius, bool force_tips_to_roof, bool skip_ovalisation) : target_height(target_height), target_position(target_position), next_position(target_position), next_height(target_height), effective_radius_height(distance_to_top), @@ -119,7 +121,7 @@ public: } - SupportElement(const SupportElement& elem, Polygons* newArea = nullptr) + explicit SupportElement(const SupportElement& elem, Polygons* newArea = nullptr) : // copy constructor with possibility to set a new area target_height(elem.target_height), target_position(elem.target_position), @@ -149,7 +151,7 @@ public: * \brief Create a new Element for one layer below the element of the pointer supplied. */ - SupportElement(SupportElement* element_above) + explicit SupportElement(SupportElement* element_above) : target_height(element_above->target_height), target_position(element_above->target_position), next_position(element_above->next_position), @@ -174,7 +176,7 @@ public: } // 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, const TreeSupportSettings& config) : next_position(next_position), next_height(next_height), area(nullptr), increased_to_model_radius(increased_to_model_radius), use_min_xy_dist(first.use_min_xy_dist || second.use_min_xy_dist), supports_roof(first.supports_roof || second.supports_roof), dont_move_until(std::max(first.dont_move_until, second.dont_move_until)), can_use_safe_radius(first.can_use_safe_radius || second.can_use_safe_radius), missing_roof_layers(std::min(first.missing_roof_layers, second.missing_roof_layers)), skip_ovalisation(false) + explicit SupportElement(const SupportElement& first, const SupportElement& second, size_t next_height, Point next_position, coord_t increased_to_model_radius, const TreeSupportSettings& config) : next_position(next_position), next_height(next_height), area(nullptr), increased_to_model_radius(increased_to_model_radius), use_min_xy_dist(first.use_min_xy_dist || second.use_min_xy_dist), supports_roof(first.supports_roof || second.supports_roof), dont_move_until(std::max(first.dont_move_until, second.dont_move_until)), can_use_safe_radius(first.can_use_safe_radius || second.can_use_safe_radius), missing_roof_layers(std::min(first.missing_roof_layers, second.missing_roof_layers)), skip_ovalisation(false) { if (first.target_height > second.target_height) @@ -353,7 +355,7 @@ public: { TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupport class. - TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings) + explicit TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings) : angle(mesh_group_settings.support_tree_angle), angle_slow(mesh_group_settings.support_tree_angle_slow), support_line_width(mesh_group_settings.support_line_width), From 0cdc4826155333b5a5a914f3e760e06269045ac4 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 15 Aug 2022 08:42:25 +0200 Subject: [PATCH 18/29] Optimization: Replaced lrint() with round() as lrint() cannot be inlined, at least not on MSVC. --- src/libslic3r/GCodeWriter.cpp | 4 ++-- src/libslic3r/Point.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index c5279c0f5..c2caf5766 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -23,8 +23,8 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) bool use_mach_limits = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware || print_config.gcode_flavor.value == gcfRepRapFirmware; - m_max_acceleration = std::lrint((use_mach_limits && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ? - print_config.machine_max_acceleration_extruding.values.front() : 0); + m_max_acceleration = static_cast(std::round((use_mach_limits && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ? + print_config.machine_max_acceleration_extruding.values.front() : 0)); } void GCodeWriter::set_extruders(std::vector extruder_ids) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 77d99d14a..7fb04530f 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -141,9 +141,9 @@ public: Point() : Vec2crd(0, 0) {} Point(int32_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {} Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} - Point(double x, double y) : Vec2crd(coord_t(lrint(x)), coord_t(lrint(y))) {} + Point(double x, double y) : Vec2crd(coord_t(std::round(x)), coord_t(std::round(y))) {} Point(const Point &rhs) { *this = rhs; } - explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(rhs.y()))) {} + explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {} // This constructor allows you to construct Point from Eigen expressions template Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} From 85e9ae75bba58ce6fb42de6aa523431d68048efc Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 15 Aug 2022 10:16:16 +0200 Subject: [PATCH 19/29] WIP Tree Supports: Enabled support enforcers / blockers --- src/libslic3r/TreeSupport.cpp | 73 +++++++++++++++++++++++++++++------ src/libslic3r/TreeSupport.hpp | 7 +++- 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index dec094c6c..1a725ec65 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -272,13 +272,47 @@ static bool layer_has_overhangs(const Layer &layer) return out; } +[[nodiscard]] static const std::vector generate_overhangs(const PrintObject &print_object) +{ + std::vector out(print_object.layer_count(), Polygons{}); + + const bool support_auto = print_object.config().support_material_auto.value; + std::vector enforcers_layers{ print_object.slice_support_enforcers() }; + std::vector blockers_layers{ print_object.slice_support_blockers() }; + print_object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers_layers); + print_object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers); + + tbb::parallel_for(tbb::blocked_range(1, out.size()), + [&print_object, &enforcers_layers, &blockers_layers, support_auto, &out](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) + if (layer_has_overhangs(*print_object.get_layer(layer_idx))) { + Polygons overhangs; + const bool has_enforcers = ! enforcers_layers.empty() && ! enforcers_layers[layer_idx].empty(); + if (support_auto) { + overhangs = layer_overhangs(*print_object.get_layer(layer_idx)); + if (! blockers_layers.empty() && ! blockers_layers[layer_idx].empty()) + // Has some support blockers at this layer, apply them to the overhangs. + // Enforcers have priority over blockers: Clip blockers by enforcers. + overhangs = diff(overhangs, has_enforcers ? diff(blockers_layers[layer_idx], enforcers_layers[layer_idx]) : blockers_layers[layer_idx]); + } else if (has_enforcers) { + // Has some support enforcers at this layer, apply them to the overhangs. + overhangs = intersection(layer_overhangs(*print_object.get_layer(layer_idx)), enforcers_layers[layer_idx]); + } else + continue; + out[layer_idx] = std::move(overhangs); + } + }); + + return out; +} + /*! * \brief Precalculates all avoidances, that could be required. * * \param storage[in] Background storage to access meshes. * \param currently_processing_meshes[in] Indexes of all meshes that are processed in this iteration */ -LayerIndex precalculate(const Print &print, const TreeSupport::TreeSupportSettings &config, const std::vector &object_ids, TreeModelVolumes &volumes) +static [[nodiscard]] LayerIndex precalculate(const Print &print, const std::vector &overhangs, const TreeSupport::TreeSupportSettings &config, const std::vector &object_ids, TreeModelVolumes &volumes) { // calculate top most layer that is relevant for support LayerIndex max_layer = 0; @@ -286,7 +320,7 @@ LayerIndex precalculate(const Print &print, const TreeSupport::TreeSupportSettin const PrintObject &print_object = *print.get_object(object_id); int max_support_layer_id = 0; for (int layer_id = 1; layer_id < print_object.layer_count(); ++ layer_id) - if (layer_has_overhangs(*print_object.get_layer(layer_id))) + if (! overhangs[layer_id].empty()) max_support_layer_id = layer_id; max_layer = std::max(max_support_layer_id - int(config.z_distance_top_layers), 0); } @@ -310,6 +344,9 @@ void TreeSupport::generateSupportAreas(PrintObject& print_object) void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_volume, const std::vector &print_object_ids) { + g_showed_critical_error = false; + g_showed_performance_warning = false; + std::vector>> grouped_meshes = group_meshes(print, print_object_ids); if (grouped_meshes.empty()) return; @@ -347,8 +384,15 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo PrintObject &print_object = *print.get_object(processing.second.front()); m_volumes = TreeModelVolumes(print_object, build_volume, m_config.maximum_move_distance, m_config.maximum_move_distance_slow, processing.second.front(), m_progress_multiplier, m_progress_offset, /* additional_excluded_areas */{}); + //FIXME generating overhangs just for the furst mesh of the group. + assert(processing.second.size() == 1); + std::vector overhangs = generate_overhangs(*print.get_object(processing.second.front())); + // ### Precalculate avoidances, collision etc. - size_t num_support_layers = precalculate(print, processing.first, processing.second, m_volumes); + size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, m_volumes); + if (num_support_layers == 0) + continue; + auto t_precalc = std::chrono::high_resolution_clock::now(); // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in drawAreas @@ -360,8 +404,9 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo SupportGeneratorLayersPtr top_interface_layers(num_support_layers, nullptr); SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr); SupportGeneratorLayerStorage layer_storage; + for (size_t mesh_idx : processing.second) - generateInitialAreas(*print.get_object(mesh_idx), move_bounds, top_contacts, top_interface_layers, layer_storage); + generateInitialAreas(*print.get_object(mesh_idx), overhangs, move_bounds, top_contacts, top_interface_layers, layer_storage); auto t_gen = std::chrono::high_resolution_clock::now(); // ### Propagate the influence areas downwards. @@ -373,7 +418,7 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo auto t_place = std::chrono::high_resolution_clock::now(); // ### draw these points as circles - drawAreas(*print.get_object(processing.second.front()), move_bounds, + drawAreas(*print.get_object(processing.second.front()), overhangs, move_bounds, bottom_contacts, top_contacts, intermediate_layers, layer_storage); auto t_draw = std::chrono::high_resolution_clock::now(); @@ -1011,6 +1056,7 @@ inline SupportGeneratorLayer& layer_allocate( void TreeSupport::generateInitialAreas( const PrintObject &print_object, + const std::vector &overhangs, std::vector> &move_bounds, SupportGeneratorLayersPtr &top_contacts, SupportGeneratorLayersPtr &top_interface_layers, @@ -1049,11 +1095,12 @@ void TreeSupport::generateInitialAreas( std::mutex critical_sections; tbb::parallel_for(tbb::blocked_range(1, num_support_layers - z_distance_delta), - [this, &print_object, &mesh_config, &mesh_group_settings, &support_params, z_distance_delta, xy_overrides_z, force_tip_to_roof, roof_enabled, support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, + [this, &print_object, &overhangs, &mesh_config, &mesh_group_settings, &support_params, + z_distance_delta, xy_overrides_z, force_tip_to_roof, roof_enabled, support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, &base_circle, &critical_sections, &top_contacts, &layer_storage, &already_inserted, &move_bounds, &base_radius](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - if (! layer_has_overhangs(*print_object.get_layer(layer_idx + z_distance_delta))) + if (overhangs[layer_idx + z_distance_delta].empty()) continue; // take the least restrictive avoidance possible Polygons relevant_forbidden; @@ -1165,7 +1212,7 @@ void TreeSupport::generateInitialAreas( // 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) std::vector> overhang_processing; - Polygons overhang_raw = layer_overhangs(*print_object.get_layer(layer_idx + z_distance_delta)); + const Polygons &overhang_raw = overhangs[layer_idx + z_distance_delta]; Polygons overhang_regular = safeOffsetInc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, mesh_config.min_radius * 1.75 + mesh_config.xy_min_distance, 0, 1); // offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang Polygons remaining_overhang = intersection(diff(offset(union_ex(overhang_raw), mesh_group_settings.support_offset, jtMiter, 1.2), offset(union_ex(overhang_regular), mesh_config.support_line_width * 0.5, jtMiter, 1.2)), relevant_forbidden); @@ -1226,7 +1273,7 @@ void TreeSupport::generateInitialAreas( Polygons overhang_roofs; if (roof_enabled) { static constexpr const coord_t support_roof_offset = 0; - overhang_roofs = safeOffsetInc(layer_overhangs(*print_object.get_layer(layer_idx + z_distance_delta)), support_roof_offset, relevant_forbidden, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, 0, 1); + overhang_roofs = safeOffsetInc(overhangs[layer_idx + z_distance_delta], support_roof_offset, relevant_forbidden, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, 0, 1); remove_small(overhang_roofs, mesh_group_settings.minimum_roof_area); overhang_regular = diff(overhang_regular, overhang_roofs); for (ExPolygon &roof_part : union_ex(overhang_roofs)) @@ -2558,6 +2605,7 @@ void TreeSupport::dropNonGraciousAreas( void TreeSupport::finalizeInterfaceAndSupportAreas( const PrintObject &print_object, + const std::vector &overhangs, std::vector &support_layer_storage, std::vector &support_roof_storage, @@ -2641,7 +2689,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( // one sample at 0 layers below, another at m_config.support_bottom_layers. In-between samples at m_config.performance_interface_skip_layers distance from each other. const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(m_config.z_distance_bottom_layers))); //FIXME subtract the wipe tower - append(floor_layer, intersection(layer_outset, layer_overhangs(*print_object.get_layer(sample_layer)))); + append(floor_layer, intersection(layer_outset, overhangs[sample_layer])); if (layers_below < m_config.support_bottom_layers) layers_below = std::min(layers_below + m_config.performance_interface_skip_layers, m_config.support_bottom_layers); else @@ -2681,7 +2729,8 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( } void TreeSupport::drawAreas( - PrintObject &print_object, + PrintObject &print_object, + const std::vector &overhangs, std::vector> &move_bounds, SupportGeneratorLayersPtr &bottom_contacts, @@ -2739,7 +2788,7 @@ void TreeSupport::drawAreas( } } - finalizeInterfaceAndSupportAreas(print_object, support_layer_storage, support_roof_storage, + finalizeInterfaceAndSupportAreas(print_object, overhangs, support_layer_storage, support_roof_storage, bottom_contacts, top_contacts, intermediate_layers, layer_storage); auto t_end = std::chrono::high_resolution_clock::now(); diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 3449a0f75..1514c2e58 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -16,7 +16,7 @@ #include "BoundingBox.hpp" -// #define TREE_SUPPORT_SHOW_ERRORS + #define TREE_SUPPORT_SHOW_ERRORS #define SUPPORT_TREE_CIRCLE_RESOLUTION 25 // The number of vertices in each circle. @@ -716,7 +716,8 @@ private: * \param storage[in] Background storage, required for adding roofs. */ void generateInitialAreas(const PrintObject &print_object, - std::vector> &move_bounds, + const std::vector &overhangs, + std::vector> &move_bounds, SupportGeneratorLayersPtr &top_contacts, SupportGeneratorLayersPtr &top_interface_layers, SupportGeneratorLayerStorage &layer_storage); @@ -828,6 +829,7 @@ private: */ void finalizeInterfaceAndSupportAreas( const PrintObject &print_object, + const std::vector &overhangs, std::vector &support_layer_storage, std::vector &support_roof_storage, @@ -844,6 +846,7 @@ private: */ void drawAreas( PrintObject &print_object, + const std::vector &overhangs, std::vector> &move_bounds, SupportGeneratorLayersPtr &bottom_contacts, From 42b546ae9cc1e89ccc569b63c88aef61d96200d8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 16 Aug 2022 11:54:30 +0200 Subject: [PATCH 20/29] WIP TreeSupports: Refactored TreeModelVolumes for clarity --- src/libslic3r/TreeModelVolumes.cpp | 291 +++++++---------------------- src/libslic3r/TreeModelVolumes.hpp | 191 ++++++++++--------- src/libslic3r/TreeSupport.cpp | 83 ++++---- src/libslic3r/TreeSupport.hpp | 31 +-- src/libslic3r/Utils.hpp | 8 + 5 files changed, 233 insertions(+), 371 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index c94edca72..c07669fe0 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -16,6 +16,7 @@ #include "Point.hpp" #include "Print.hpp" #include "PrintConfig.hpp" +#include "Utils.hpp" #include @@ -282,21 +283,9 @@ void TreeModelVolumes::precalculate(coord_t max_layer) BOOST_LOG_TRIVIAL(info) << "Precalculating collision took" << dur_col << " ms. Precalculating avoidance took " << dur_avo << " ms."; } -/*! - * \brief Checks a cache for a given RadiusLayerPair and returns it if it is found - * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. - * \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found) - */ -std::optional> getArea(const TreeModelVolumes::RadiusLayerPolygonCache& cache, const TreeModelVolumes::RadiusLayerPair& key) -{ - const auto it = cache.find(key); - return it == cache.end() ? std::optional>{} : std::optional>{ it->second }; -} - const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const { coord_t orig_radius = radius; - std::optional> result; if (!min_xy_dist) radius += m_current_min_xy_dist_delta; @@ -304,11 +293,7 @@ const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_ if (orig_radius != 0) radius = ceilRadius(radius); RadiusLayerPair key{ radius, layer_idx }; - - { - std::lock_guard critical_section_support_max_layer_nr(*m_critical_avoidance_cache); - result = getArea(m_collision_cache, key); - } + std::optional> result = m_collision_cache.getArea(key); if (result) return result.value().get(); if (m_precalculated) { @@ -322,18 +307,15 @@ const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_ const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const { coord_t orig_radius = radius; - std::optional> result; if (!min_xy_dist) radius += m_current_min_xy_dist_delta; if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta) return getCollision(orig_radius, layer_idx, min_xy_dist); + // not needed as the radius should already be adjusted. + // radius = ceilRadius(radius); RadiusLayerPair key{ radius, layer_idx }; - - { - std::lock_guard critical_section_support_max_layer_nr(*m_critical_collision_cache_holefree); - result = getArea(m_collision_cache_holefree, key); - } + std::optional> result = m_collision_cache_holefree.getArea(key); if (result) return result.value().get(); if (m_precalculated) { @@ -351,61 +333,44 @@ const Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_ coord_t orig_radius = radius; - std::optional> result; - if (!min_xy_dist) radius += m_current_min_xy_dist_delta; radius = ceilRadius(radius); - if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta && type == AvoidanceType::FAST_SAFE) // no holes anymore by definition at this request - type = AvoidanceType::FAST; + if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta && type == AvoidanceType::FastSafe) // no holes anymore by definition at this request + type = AvoidanceType::Fast; const RadiusLayerPair key{ radius, layer_idx }; - const RadiusLayerPolygonCache* cache_ptr = nullptr; - std::mutex* mutex_ptr; - if (!to_model && type == AvoidanceType::FAST) { + const RadiusLayerPolygonCache *cache_ptr = nullptr; + if (!to_model && type == AvoidanceType::Fast) { cache_ptr = &m_avoidance_cache; - mutex_ptr = m_critical_avoidance_cache.get(); - } else if (!to_model && type == AvoidanceType::SLOW) { + } else if (!to_model && type == AvoidanceType::Slow) { cache_ptr = &m_avoidance_cache_slow; - mutex_ptr = m_critical_avoidance_cache_slow.get(); - } else if (!to_model && type == AvoidanceType::FAST_SAFE) { - cache_ptr = &m_avoidance_cache_hole; - mutex_ptr = m_critical_avoidance_cache_holefree.get(); - } else if (to_model && type == AvoidanceType::FAST) { + } else if (!to_model && type == AvoidanceType::FastSafe) { + cache_ptr = &m_avoidance_cache_holefree; + } else if (to_model && type == AvoidanceType::Fast) { cache_ptr = &m_avoidance_cache_to_model; - mutex_ptr = m_critical_avoidance_cache_to_model.get(); - } else if (to_model && type == AvoidanceType::SLOW) { + } else if (to_model && type == AvoidanceType::Slow) { cache_ptr = &m_avoidance_cache_to_model_slow; - mutex_ptr = m_critical_avoidance_cache_to_model_slow.get(); - } else if (to_model && type == AvoidanceType::FAST_SAFE) { - cache_ptr = &m_avoidance_cache_hole_to_model; - mutex_ptr = m_critical_avoidance_cache_holefree_to_model.get(); + } else if (to_model && type == AvoidanceType::FastSafe) { + cache_ptr = &m_avoidance_cache_holefree_to_model; } else { BOOST_LOG_TRIVIAL(error) << "Invalid Avoidance Request"; TreeSupport::showError("Invalid Avoidance Request.\n", true); } + std::optional> result = cache_ptr->getArea(key); + if (result) + return result.value().get(); + if (to_model) { - { - std::lock_guard critical_section(*mutex_ptr); - result = getArea(*cache_ptr, key); - } - if (result) - return result.value().get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(warning) << "Had to calculate Avoidance to model at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Avoidance(to model) requested.", false); } const_cast(this)->calculateAvoidanceToModel(key); } else { - { - std::lock_guard critical_section(*mutex_ptr); - result = getArea(*cache_ptr, key); - } - if (result) - return result.value().get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(warning) << "Had to calculate Avoidance at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Avoidance(to buildplate) requested.", false); @@ -415,120 +380,47 @@ const Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_ return getAvoidance(orig_radius, layer_idx, type, to_model, min_xy_dist); // retrive failed and correct result was calculated. Now it has to be retrived. } -const Polygons& TreeModelVolumes::getPlaceableAreas(coord_t radius, LayerIndex layer_idx) const +const Polygons& TreeModelVolumes::getPlaceableAreas(coord_t orig_radius, LayerIndex layer_idx) const { - std::optional> result; - const coord_t orig_radius = radius; - radius = ceilRadius(radius); - RadiusLayerPair key{ radius, layer_idx }; - - { - std::lock_guard critical_section(*m_critical_placeable_areas_cache); - result = getArea(m_placeable_areas_cache, key); - } + const coord_t radius = ceilRadius(orig_radius); + std::optional> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); if (result) return result.value().get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(warning) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Placeable areas requested.", false); } - if (radius != 0) - const_cast(this)->calculatePlaceables(key); + if (orig_radius > 0) + const_cast(this)->calculatePlaceables({ radius, layer_idx }); else getCollision(0, layer_idx, true); return getPlaceableAreas(orig_radius, layer_idx); } -const Polygons& TreeModelVolumes::getWallRestriction(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const +const Polygons& TreeModelVolumes::getWallRestriction(coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const { if (layer_idx == 0) // Should never be requested as there will be no going below layer 0 ..., but just to be sure some semi-sane catch. Alternative would be empty Polygon. - return getCollision(radius, layer_idx, min_xy_dist); + return getCollision(orig_radius, layer_idx, min_xy_dist); - coord_t orig_radius = radius; min_xy_dist = min_xy_dist && m_current_min_xy_dist_delta > 0; - std::optional> result; - - radius = ceilRadius(radius); - const RadiusLayerPair key{ radius, layer_idx }; - - const RadiusLayerPolygonCache* cache_ptr = min_xy_dist ? &m_wall_restrictions_cache_min : &m_wall_restrictions_cache; - - if (min_xy_dist) { - { - std::lock_guard critical_section(*m_critical_wall_restrictions_cache_min); - result = getArea(*cache_ptr, key); - } - if (result) - return result.value().get(); - if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate Wall restricions at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Wall restriction of minimum xy distance requested ).", false); - } - } else { - { - std::lock_guard critical_section(*m_critical_wall_restrictions_cache); - result = getArea(*cache_ptr, key); - } - if (result) - return result.value().get(); - if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate Wall restricions at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Wall restriction requested ).", false); - } + coord_t radius = ceilRadius(orig_radius); + std::optional> result = + (min_xy_dist ? m_wall_restrictions_cache_min : m_wall_restrictions_cache).getArea({ radius, layer_idx }); + if (result) + return result.value().get(); + if (m_precalculated) { + BOOST_LOG_TRIVIAL(warning) << "Had to calculate Wall restricions at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; + TreeSupport::showError( + min_xy_dist ? + "Not precalculated Wall restriction of minimum xy distance requested )." : + "Not precalculated Wall restriction requested )." + , false); } - const_cast(this)->calculateWallRestrictions(key); + const_cast(this)->calculateWallRestrictions({ radius, layer_idx }); return getWallRestriction(orig_radius, layer_idx, min_xy_dist); // Retrieve failed and correct result was calculated. Now it has to be retrieved. } -coord_t TreeModelVolumes::ceilRadius(coord_t radius, bool min_xy_dist) const -{ - if (! min_xy_dist) - radius += m_current_min_xy_dist_delta; - return ceilRadius(radius); -} - -coord_t TreeModelVolumes::getRadiusNextCeil(coord_t radius, bool min_xy_dist) const -{ - coord_t ceiled_radius = ceilRadius(radius, min_xy_dist); - if (! min_xy_dist) - ceiled_radius -= m_current_min_xy_dist_delta; - return ceiled_radius; -} - -#if 0 -Polygons TreeModelVolumes::extractOutlineFromMesh(const PrintObject &print_object, LayerIndex layer_idx) const -{ - constexpr bool external_polys_only = false; - Polygons total; - - // similar to SliceDataStorage.getLayerOutlines but only for one mesh instead of for everyone - - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) - return Polygons(); - - const SliceLayer& layer = mesh.layers[layer_idx]; - - layer.getOutlines(total, external_polys_only); - if (mesh.settings.get("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) - total = union_(total, layer.openPolyLines.offsetPolyLine(100)); - coord_t resolution = mesh.settings.get("resolution"); - return simplify(total, resolution); -} -#endif - -LayerIndex TreeModelVolumes::getMaxCalculatedLayer(coord_t radius, const RadiusLayerPolygonCache& map) const -{ - int max_layer = -1; - // the placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist. - const RadiusLayerPair key_layer_1(radius, 1); - if (getArea(map, key_layer_1)) - max_layer = 1; - while (map.count(RadiusLayerPair(radius, max_layer + 1))) - ++ max_layer; - return max_layer; -} - void TreeModelVolumes::calculateCollision(std::deque keys) { tbb::parallel_for(tbb::blocked_range(0, keys.size()), @@ -537,11 +429,11 @@ void TreeModelVolumes::calculateCollision(std::deque keys) const coord_t radius = keys[i].first; const size_t layer_idx = keys[i].second; RadiusLayerPair key(radius, 0); - RadiusLayerPolygonCache data_outer; - RadiusLayerPolygonCache data_placeable_outer; + RadiusLayerPolygonCacheData data_outer; + RadiusLayerPolygonCacheData data_placeable_outer; for (size_t outline_idx = 0; outline_idx < m_layer_outlines.size(); ++outline_idx) { - RadiusLayerPolygonCache data; - RadiusLayerPolygonCache data_placeable; + RadiusLayerPolygonCacheData data; + RadiusLayerPolygonCacheData data_placeable; const coord_t layer_height = m_layer_outlines[outline_idx].first.layer_height; const bool support_rests_on_this_model = ! m_layer_outlines[outline_idx].first.support_material_buildplate_only; @@ -554,12 +446,7 @@ void TreeModelVolumes::calculateCollision(std::deque keys) // avoiding this would require saving each collision for each outline_idx separately. // and later for each avoidance... But avoidance calculation has to be for the whole scene and can NOT be done for each outline_idx separately and combined later. // so avoiding this inaccuracy seems infeasible as it would require 2x the avoidance calculations => 0.5x the performance. - coord_t min_layer_bottom; - { - std::lock_guard critical_section(*m_critical_collision_cache); - min_layer_bottom = getMaxCalculatedLayer(radius, m_collision_cache) - int(z_distance_bottom_layers); - } - + coord_t min_layer_bottom = m_collision_cache.getMaxCalculatedLayer(radius) - int(z_distance_bottom_layers); if (min_layer_bottom < 0) min_layer_bottom = 0; //FIXME parallel_for @@ -623,14 +510,9 @@ void TreeModelVolumes::calculateCollision(std::deque keys) } #endif - { - std::lock_guard critical_section(*m_critical_collision_cache); - m_collision_cache.insert(data_outer.begin(), data_outer.end()); - } - if (radius == 0) { - std::lock_guard critical_section(*m_critical_placeable_areas_cache); - m_placeable_areas_cache.insert(data_placeable_outer.begin(), data_placeable_outer.end()); - } + m_collision_cache.insert(std::move(data_outer)); + if (radius == 0) + m_placeable_areas_cache.insert(std::move(data_placeable_outer)); } }); } @@ -644,7 +526,7 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke tbb::parallel_for(tbb::blocked_range(0, max_layer + 1), [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - RadiusLayerPolygonCache data; + RadiusLayerPolygonCacheData data; for (RadiusLayerPair key : keys) { // Logically increase the collision by m_increase_until_radius coord_t radius = key.first; @@ -654,8 +536,7 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke data[RadiusLayerPair(radius, layer_idx)] = col; } - std::lock_guard critical_section(*m_critical_collision_cache_holefree); - m_collision_cache_holefree.insert(data.begin(), data.end()); + m_collision_cache_holefree.insert(std::move(data)); } }); } @@ -663,7 +544,7 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque ke void TreeModelVolumes::calculateAvoidance(std::deque keys) { // For every RadiusLayer pair there are 3 avoidances that have to be calculate, calculated in the same paralell_for loop for better paralellisation. - const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; + const std::vector all_types = { AvoidanceType::Slow, AvoidanceType::FastSafe, AvoidanceType::Fast }; tbb::parallel_for(tbb::blocked_range(0, keys.size() * 3), [&, keys, all_types](const tbb::blocked_range &range) { for (size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { @@ -671,8 +552,8 @@ void TreeModelVolumes::calculateAvoidance(std::deque keys) { size_t type_idx = iter_idx % all_types.size(); AvoidanceType type = all_types[type_idx]; - const bool slow = type == AvoidanceType::SLOW; - const bool holefree = type == AvoidanceType::FAST_SAFE; + const bool slow = type == AvoidanceType::Slow; + const bool holefree = type == AvoidanceType::FastSafe; coord_t radius = keys[key_idx].first; LayerIndex max_required_layer = keys[key_idx].second; @@ -683,11 +564,7 @@ void TreeModelVolumes::calculateAvoidance(std::deque keys) const coord_t offset_speed = slow ? m_max_move_slow : m_max_move; RadiusLayerPair key(radius, 0); - LayerIndex start_layer; - { - std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_slow : holefree ? m_critical_avoidance_cache_holefree : m_critical_avoidance_cache)); - start_layer = 1 + getMaxCalculatedLayer(radius, slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_hole : m_avoidance_cache); - } + LayerIndex start_layer = 1 + (slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_holefree : m_avoidance_cache).getMaxCalculatedLayer(radius); if (start_layer > max_required_layer) { BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; continue; @@ -718,10 +595,7 @@ void TreeModelVolumes::calculateAvoidance(std::deque keys) } #endif - { - std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_slow : holefree ? m_critical_avoidance_cache_holefree : m_critical_avoidance_cache)); - (slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_hole : m_avoidance_cache).insert(data.begin(), data.end()); - } + (slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_holefree : m_avoidance_cache).insert(std::move(data)); } } }); @@ -737,11 +611,7 @@ void TreeModelVolumes::calculatePlaceables(std::deque keys) std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); RadiusLayerPair key(radius, 0); - LayerIndex start_layer; - { - std::lock_guard critical_section(*m_critical_placeable_areas_cache); - start_layer = 1 + getMaxCalculatedLayer(radius, m_placeable_areas_cache); - } + LayerIndex start_layer = 1 + m_placeable_areas_cache.getMaxCalculatedLayer(radius); if (start_layer > max_required_layer) { BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; continue; @@ -770,10 +640,7 @@ void TreeModelVolumes::calculatePlaceables(std::deque keys) } #endif - { - std::lock_guard critical_section(*m_critical_placeable_areas_cache); - m_placeable_areas_cache.insert(data.begin(), data.end()); - } + m_placeable_areas_cache.insert(std::move(data)); } }); } @@ -781,15 +648,15 @@ void TreeModelVolumes::calculatePlaceables(std::deque keys) void TreeModelVolumes::calculateAvoidanceToModel(std::deque keys) { // For every RadiusLayer pair there are 3 avoidances that have to be calculated, calculated in the same parallel_for loop for better parallelization. - const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; + const std::vector all_types = { AvoidanceType::Slow, AvoidanceType::FastSafe, AvoidanceType::Fast }; tbb::parallel_for(tbb::blocked_range(0, keys.size() * 3), [&, keys, all_types](const tbb::blocked_range &range) { for (size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { size_t key_idx = iter_idx / 3; size_t type_idx = iter_idx % all_types.size(); AvoidanceType type = all_types[type_idx]; - bool slow = type == AvoidanceType::SLOW; - bool holefree = type == AvoidanceType::FAST_SAFE; + bool slow = type == AvoidanceType::Slow; + bool holefree = type == AvoidanceType::FastSafe; coord_t radius = keys[key_idx].first; LayerIndex max_required_layer = keys[key_idx].second; @@ -802,12 +669,7 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque key std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); RadiusLayerPair key(radius, 0); - LayerIndex start_layer; - - { - std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_to_model_slow : holefree ? m_critical_avoidance_cache_holefree_to_model : m_critical_avoidance_cache_to_model)); - start_layer = 1 + getMaxCalculatedLayer(radius, slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_hole_to_model : m_avoidance_cache_to_model); - } + LayerIndex start_layer = 1 + (slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_holefree_to_model : m_avoidance_cache_to_model).getMaxCalculatedLayer(radius); if (start_layer > max_required_layer) { BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; continue; @@ -836,15 +698,11 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque key } #endif - { - std::lock_guard critical_section(*(slow ? m_critical_avoidance_cache_to_model_slow : holefree ? m_critical_avoidance_cache_holefree_to_model : m_critical_avoidance_cache_to_model)); - (slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_hole_to_model : m_avoidance_cache_to_model).insert(data.begin(), data.end()); - } + (slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_holefree_to_model : m_avoidance_cache_to_model).insert(std::move(data)); } }); } - void TreeModelVolumes::calculateWallRestrictions(std::deque keys) { // Wall restrictions are mainly important when they represent actual walls that are printed, and not "just" the configured z_distance, because technically valid placement is no excuse for moving through a wall. @@ -886,15 +744,9 @@ void TreeModelVolumes::calculateWallRestrictions(std::deque key for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { coord_t radius = keys[key_idx].first; RadiusLayerPair key(radius, 0); - coord_t min_layer_bottom; - RadiusLayerPolygonCache data; - RadiusLayerPolygonCache data_min; - - { - std::lock_guard critical_section(*m_critical_wall_restrictions_cache); - min_layer_bottom = getMaxCalculatedLayer(radius, m_wall_restrictions_cache); - } - + RadiusLayerPolygonCacheData data; + RadiusLayerPolygonCacheData data_min; + coord_t min_layer_bottom = m_wall_restrictions_cache.getMaxCalculatedLayer(radius); if (min_layer_bottom < 1) min_layer_bottom = 1; @@ -904,21 +756,14 @@ void TreeModelVolumes::calculateWallRestrictions(std::deque key Polygons wall_restriction = intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx_below, true)); // radius contains m_current_min_xy_dist_delta already if required wall_restriction = polygons_simplify(wall_restriction, m_min_resolution); data.emplace(key, wall_restriction); - if (m_current_min_xy_dist_delta > 0) - { + if (m_current_min_xy_dist_delta > 0) { Polygons wall_restriction_min = intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx_below, true)); wall_restriction = polygons_simplify(wall_restriction_min, m_min_resolution); data_min.emplace(key, wall_restriction_min); } } - { - std::lock_guard critical_section(*m_critical_wall_restrictions_cache); - m_wall_restrictions_cache.insert(data.begin(), data.end()); - } - { - std::lock_guard critical_section(*m_critical_wall_restrictions_cache_min); - m_wall_restrictions_cache_min.insert(data_min.begin(), data_min.end()); - } + m_wall_restrictions_cache.insert(std::move(data)); + m_wall_restrictions_cache_min.insert(std::move(data_min)); } }); } diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index c8b986a00..5f4b66772 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -23,7 +23,6 @@ namespace Slic3r { using LayerIndex = int; -using AngleRadians = double; class BuildVolume; class PrintObject; @@ -52,7 +51,7 @@ struct TreeSupportMeshGroupSettings { // Support Overhang Angle // The minimum angle of overhangs for which support is added. At a value of 0° all overhangs are supported, 90° will not provide any support. - AngleRadians support_angle { 50. * M_PI / 180. }; + double support_angle { 50. * M_PI / 180. }; // Support Line Width // Width of a single support structure line. coord_t support_line_width { scaled(0.4) }; @@ -92,7 +91,7 @@ struct TreeSupportMeshGroupSettings { // A list of integer line directions to use. Elements from the list are used sequentially as the layers progress and when the end // of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained // in square brackets. Default is an empty list which means use the default angle 0 degrees. - std::vector support_infill_angles {}; + std::vector support_infill_angles {}; // Enable Support Roof // Generate a dense slab of material between the top of support and the model. This will create a skin between the model and support. bool support_roof_enable { false }; @@ -106,7 +105,7 @@ struct TreeSupportMeshGroupSettings { // and when the end of the list is reached, it starts at the beginning again. The list items are separated // by commas and the whole list is contained in square brackets. Default is an empty list which means // use the default angles (alternates between 45 and 135 degrees if interfaces are quite thick or 90 degrees). - std::vector support_roof_angles {}; + std::vector support_roof_angles {}; // Support Roof Pattern (aka top interface) // The pattern with which the roofs of the support are printed. SupportMaterialInterfacePattern support_roof_pattern { smipAuto }; @@ -144,12 +143,12 @@ struct TreeSupportMeshGroupSettings { // Tree Support Maximum Branch Angle // The maximum angle of the branches, when the branches have to avoid the model. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach. // minimum: 0, minimum warning: 20, maximum: 89, maximum warning": 85 - AngleRadians support_tree_angle { 60. * M_PI / 180. }; + double support_tree_angle { 60. * M_PI / 180. }; // Tree Support Branch Diameter Angle // The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. // A bit of an angle can increase stability of the tree support. // minimum: 0, maximum: 89.9999, maximum warning: 15 - AngleRadians support_tree_branch_diameter_angle { 5. * M_PI / 180. }; + double support_tree_branch_diameter_angle { 5. * M_PI / 180. }; // Tree Support Branch Distance // How far apart the branches need to be when they touch the model. Making this distance small will cause // the tree support to touch the model at more points, causing better overhang but making support harder to remove. @@ -166,7 +165,7 @@ struct TreeSupportMeshGroupSettings { // Tree Support Preferred Branch Angle // The preferred angle of the branches, when they do not have to avoid the model. Use a lower angle to make them more vertical and more stable. Use a higher angle for branches to merge faster. // minimum: 0, minimum warning: 10, maximum: support_tree_angle, maximum warning: support_tree_angle-1 - AngleRadians support_tree_angle_slow { 50. * M_PI / 180. }; + double support_tree_angle_slow { 50. * M_PI / 180. }; // Tree Support Diameter Increase To Model // The most the diameter of a branch that has to connect to the model may increase by merging with branches that could reach the buildplate. // Increasing this reduces print time, but increases the area of support that rests on model @@ -195,17 +194,13 @@ struct TreeSupportMeshGroupSettings { //enum support_interface_priority { support_lines_overwrite_interface_area }; }; -inline coord_t round_up_divide(const coord_t dividend, const coord_t divisor) //!< Return dividend divided by divisor rounded to the nearest integer -{ - return (dividend + divisor - 1) / divisor; -} - class TreeModelVolumes { public: TreeModelVolumes() = default; explicit TreeModelVolumes(const PrintObject &print_object, const BuildVolume &build_volume, - coord_t max_move, coord_t max_move_slow, size_t current_mesh_idx, double progress_multiplier, double progress_offset, const std::vector &additional_excluded_areas = {}); + coord_t max_move, coord_t max_move_slow, size_t current_mesh_idx, double progress_multiplier, + double progress_offset, const std::vector &additional_excluded_areas = {}); TreeModelVolumes(TreeModelVolumes&&) = default; TreeModelVolumes& operator=(TreeModelVolumes&&) = default; @@ -214,17 +209,16 @@ public: enum class AvoidanceType { - SLOW, - FAST_SAFE, - FAST + Slow, + FastSafe, + Fast }; /*! - * \brief Precalculate avoidances and collisions up to this layer. - * - * This uses knowledge about branch angle to only calculate avoidances and collisions that could actually be needed. - * Not calling this will cause the class to lazily calculate avoidances and collisions as needed, which will be a lot slower on systems with more then one or two cores! + * \brief Precalculate avoidances and collisions up to max_layer. * + * Knowledge about branch angle is used to only calculate avoidances and collisions that may actually be needed. + * Not calling precalculate() will cause the class to lazily calculate avoidances and collisions as needed, which will be a lot slower on systems with more then one or two cores! */ void precalculate(coord_t max_layer); @@ -239,20 +233,7 @@ public: * \param min_xy_dist Is the minimum xy distance used. * \return Polygons object */ - const Polygons& getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist = false) const; - - /*! - * \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed. - * - * The result is a 2D area that would cause nodes of given radius to - * collide with the model or be inside a hole. - * A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall. - * \param radius The radius of the node of interest - * \param layer_idx The layer of interest - * \param min_xy_dist Is the minimum xy distance used. - * \return Polygons object - */ - const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist = false) const; + const Polygons& getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const; /*! * \brief Provides the areas that have to be avoided by the tree's branches @@ -266,12 +247,12 @@ public: * * \param radius The radius of the node of interest * \param layer_idx The layer of interest - * \param slow Is the propagation with the maximum move distance slow required. + * \param type Is the propagation with the maximum move distance slow required. * \param to_model Does the avoidance allow good connections with the model. * \param min_xy_dist is the minimum xy distance used. * \return Polygons object */ - const Polygons& getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model = false, bool min_xy_dist = false) const; + const Polygons& getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) const; /*! * \brief Provides the area represents all areas on the model where the branch does completely fit on the given layer. * \param radius The radius of the node of interest @@ -296,7 +277,9 @@ public: * \param min_xy_dist is the minimum xy distance used. * \return The rounded radius */ - coord_t ceilRadius(coord_t radius, bool min_xy_dist) const; + coord_t ceilRadius(coord_t radius, bool min_xy_dist) const { + return this->ceilRadius(min_xy_dist ? radius : radius + m_current_min_xy_dist_delta); + } /*! * \brief Round \p radius upwards to the maximum that would still round up to the same value as the provided one. * @@ -304,16 +287,84 @@ public: * \param min_xy_dist is the minimum xy distance used. * \return The maximum radius, resulting in the same rounding. */ - coord_t getRadiusNextCeil(coord_t radius, bool min_xy_dist) const; + coord_t getRadiusNextCeil(coord_t radius, bool min_xy_dist) const { + return min_xy_dist ? + this->ceilRadius(radius) : + this->ceilRadius(radius + m_current_min_xy_dist_delta) - m_current_min_xy_dist_delta; + } private: /*! * \brief Convenience typedef for the keys to the caches */ - using RadiusLayerPair = std::pair; - using RadiusLayerPolygonCache = std::unordered_map>; + using RadiusLayerPair = std::pair; + using RadiusLayerPolygonCacheData = std::unordered_map>; + class RadiusLayerPolygonCache { + public: + RadiusLayerPolygonCache() = default; + RadiusLayerPolygonCache(RadiusLayerPolygonCache &&rhs) : data(std::move(rhs.data)) {} + RadiusLayerPolygonCache& operator=(RadiusLayerPolygonCache &&rhs) { data = std::move(rhs.data); return *this; } - friend std::optional> getArea(const TreeModelVolumes::RadiusLayerPolygonCache &cache, const TreeModelVolumes::RadiusLayerPair &key); + RadiusLayerPolygonCache(const RadiusLayerPolygonCache&) = delete; + RadiusLayerPolygonCache& operator=(const RadiusLayerPolygonCache&) = delete; + + void insert(RadiusLayerPolygonCacheData &&in) { + std::lock_guard guard(this->mutex); + for (auto& d : in) + this->data.emplace(d.first, std::move(d.second)); + } + void insert(std::vector> && in) { + std::lock_guard guard(this->mutex); + for (auto& d : in) + this->data.emplace(d.first, std::move(d.second)); + } + /*! + * \brief Checks a cache for a given RadiusLayerPair and returns it if it is found + * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. + * \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found) + */ + std::optional> getArea(const TreeModelVolumes::RadiusLayerPair &key) const { + std::lock_guard guard(this->mutex); + const auto it = this->data.find(key); + return it == this->data.end() ? + std::optional>{} : std::optional>{ it->second }; + } + /*! + * \brief Get the highest already calculated layer in the cache. + * \param radius The radius for which the highest already calculated layer has to be found. + * \param map The cache in which the lookup is performed. + * + * \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found) + */ + LayerIndex getMaxCalculatedLayer(coord_t radius) const { + std::lock_guard guard(this->mutex); + int max_layer = -1; + // the placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist. + if (this->data.find({ radius, 1 }) != this->data.end()) + max_layer = 1; + while (this->data.count(TreeModelVolumes::RadiusLayerPair(radius, max_layer + 1)) > 0) + ++ max_layer; + return max_layer; + } + + private: + RadiusLayerPolygonCacheData data; + mutable std::mutex mutex; + }; + + + /*! + * \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed. + * + * The result is a 2D area that would cause nodes of given radius to + * collide with the model or be inside a hole. + * A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall. + * \param radius The radius of the node of interest + * \param layer_idx The layer of interest + * \param min_xy_dist Is the minimum xy distance used. + * \return Polygons object + */ + const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const; /*! * \brief Round \p radius upwards to either a multiple of m_radius_sample_resolution or a exponentially increasing value @@ -322,14 +373,6 @@ private: */ coord_t ceilRadius(coord_t radius) const; - /*! - * \brief Extracts the relevant outline from a mesh - * \param[in] mesh The mesh which outline will be extracted - * \param layer_idx The layer which should be extracted from the mesh - * \return Polygons object representing the outline - */ -// Polygons extractOutlineFromMesh(const PrintObject &print_object, LayerIndex layer_idx) const; - /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. * @@ -449,15 +492,6 @@ private: calculateWallRestrictions(std::deque{ RadiusLayerPair(key) }); } - /*! - * \brief Get the highest already calculated layer in the cache. - * \param radius The radius for which the highest already calculated layer has to be found. - * \param map The cache in which the lookup is performed. - * - * \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found) - */ - LayerIndex getMaxCalculatedLayer(coord_t radius, const RadiusLayerPolygonCache& map) const; - /*! * \brief The maximum distance that the center point of a tree branch may move in consecutive layers if it has to avoid the model. */ @@ -531,59 +565,40 @@ private: */ coord_t m_radius_0; - /*! * \brief Caches for the collision, avoidance and areas on the model where support can be placed safely * at given radius and layer indices. */ - RadiusLayerPolygonCache m_collision_cache; - std::unique_ptr m_critical_collision_cache { std::make_unique() }; - - RadiusLayerPolygonCache m_collision_cache_holefree; - std::unique_ptr m_critical_collision_cache_holefree { std::make_unique() }; - - RadiusLayerPolygonCache m_avoidance_cache; - std::unique_ptr m_critical_avoidance_cache { std::make_unique() }; - - RadiusLayerPolygonCache m_avoidance_cache_slow; - std::unique_ptr m_critical_avoidance_cache_slow { std::make_unique() }; - - RadiusLayerPolygonCache m_avoidance_cache_to_model; - std::unique_ptr m_critical_avoidance_cache_to_model { std::make_unique() }; - - RadiusLayerPolygonCache m_avoidance_cache_to_model_slow; - std::unique_ptr m_critical_avoidance_cache_to_model_slow { std::make_unique() }; - - RadiusLayerPolygonCache m_placeable_areas_cache; - std::unique_ptr m_critical_placeable_areas_cache { std::make_unique() }; + RadiusLayerPolygonCache m_collision_cache; + RadiusLayerPolygonCache m_collision_cache_holefree; + RadiusLayerPolygonCache m_avoidance_cache; + RadiusLayerPolygonCache m_avoidance_cache_slow; + RadiusLayerPolygonCache m_avoidance_cache_to_model; + RadiusLayerPolygonCache m_avoidance_cache_to_model_slow; + RadiusLayerPolygonCache m_placeable_areas_cache; /*! * \brief Caches to avoid holes smaller than the radius until which the radius is always increased, as they are free of holes. * Also called safe avoidances, as they are safe regarding not running into holes. */ - RadiusLayerPolygonCache m_avoidance_cache_hole; - std::unique_ptr m_critical_avoidance_cache_holefree { std::make_unique() }; - - RadiusLayerPolygonCache m_avoidance_cache_hole_to_model; - std::unique_ptr m_critical_avoidance_cache_holefree_to_model { std::make_unique() }; + RadiusLayerPolygonCache m_avoidance_cache_holefree; + RadiusLayerPolygonCache m_avoidance_cache_holefree_to_model; /*! * \brief Caches to represent walls not allowed to be passed over. */ RadiusLayerPolygonCache m_wall_restrictions_cache; - std::unique_ptr m_critical_wall_restrictions_cache { std::make_unique() }; // A different cache for min_xy_dist as the maximal safe distance an influence area can be increased(guaranteed overlap of two walls in consecutive layer) // is much smaller when min_xy_dist is used. This causes the area of the wall restriction to be thinner and as such just using the min_xy_dist wall // restriction would be slower. RadiusLayerPolygonCache m_wall_restrictions_cache_min; - std::unique_ptr m_critical_wall_restrictions_cache_min = std::make_unique(); +#ifdef SLIC3R_TREESUPPORTS_PROGRESS std::unique_ptr m_critical_progress { std::make_unique() }; +#endif // SLIC3R_TREESUPPORTS_PROGRESS }; -Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision); - } #endif //slic3r_TreeModelVolumes_hpp diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 1a725ec65..2d7debea9 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -525,13 +525,13 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo for (const Polyline &line : polylines) { LineInformation res_line; for (Point p : line) { - if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST_SAFE, false, !xy_overrides_z), p)) + if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FastSafe, false, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_BP_SAFE); - else if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST, false, !xy_overrides_z), p)) + else if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::Fast, false, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_BP); - else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST_SAFE, true, !xy_overrides_z), p)) + else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FastSafe, true, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS_SAFE); - else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FAST, true, !xy_overrides_z), p)) + else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::Fast, true, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS); else if (config.support_rests_on_model && ! contains(volumes.getCollision(config.getRadius(0), layer_idx, !xy_overrides_z), p)) res_line.emplace_back(p, LineStatus::TO_MODEL); @@ -580,12 +580,12 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo size_t current_layer, std::pair &p) { using AvoidanceType = TreeSupport::AvoidanceType; - if (! contains(volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, false, !config.support_xy_overrides_z), p.first)) + if (! contains(volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FastSafe : AvoidanceType::Fast, false, !config.support_xy_overrides_z), p.first)) return true; if (config.support_rests_on_model && (p.second != LineStatus::TO_BP && p.second != LineStatus::TO_BP_SAFE)) return ! contains( p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? - volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, true, !config.support_xy_overrides_z) : + volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FastSafe : AvoidanceType::Fast, true, !config.support_xy_overrides_z) : volumes.getCollision(config.getRadius(0), current_layer - 1, !config.support_xy_overrides_z), p.first); return false; @@ -936,19 +936,6 @@ static std::optional> polyline_sample_next_point_at_dis return result; } -// ensures offsets are only done in sizes with a max step size per offset while adding the collision offset after each step, this ensures that areas cannot glitch through walls defined by the collision when offsetting to fast -[[nodiscard]] Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) -{ - const size_t steps = std::abs(distance / max_safe_step_distance); - assert(int64_t(distance) * int64_t(max_safe_step_distance) >= 0); - ExPolygons ret = union_ex(me); - Polygons collision_trimmed = clip_for_diff(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); - - for (size_t i = 0; i < steps; ++ i) - ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision_trimmed)); - return union_(offset(ret, distance % max_safe_step_distance, jt, jt == jtRound ? scaled(0.01) : 1.2), collision_trimmed); -} - /*! * \brief Offsets (increases the area of) a polygons object in multiple steps to ensure that it does not lag through over a given obstacle. * \param me[in] Polygons object that has to be offset. @@ -1065,7 +1052,7 @@ void TreeSupport::generateInitialAreas( Polygon base_circle; const auto base_radius = scaled(0.01); for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; ++ i) { - const AngleRadians angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); + const double angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); base_circle.points.emplace_back(coord_t(cos(angle) * base_radius), coord_t(sin(angle) * base_radius)); } TreeSupportMeshGroupSettings mesh_group_settings(print_object); @@ -1106,9 +1093,9 @@ void TreeSupport::generateInitialAreas( Polygons relevant_forbidden; { const Polygons &relevant_forbidden_raw = (mesh_config.support_rests_on_model ? - (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides_z) : + (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides_z)) : - m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides_z)); + m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, false, !xy_overrides_z)); // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. relevant_forbidden = offset(union_ex(relevant_forbidden_raw), scaled(0.005), jtMiter, 1.2); } @@ -1257,7 +1244,7 @@ void TreeSupport::generateInitialAreas( for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) { // get least restricted avoidance for layer_idx-lag_ctr - const Polygons &relevant_forbidden_below = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::FAST, false, !xy_overrides_z)); + const Polygons &relevant_forbidden_below = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, false, !xy_overrides_z)); // it is not required to offset the forbidden area here as the points wont change: If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. auto evaluatePoint = [&](std::pair p) { return contains(relevant_forbidden_below, p.first); }; @@ -1304,7 +1291,11 @@ void TreeSupport::generateInitialAreas( // here the roof is handled. If roof can not be added the branches will try to not move instead Polygons forbidden_next; { - const Polygons &forbidden_next_raw = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides_z));\ + const Polygons &forbidden_next_raw = mesh_config.support_rests_on_model ? + (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? + m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::Fast, true, !xy_overrides_z) : + m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides_z)) : + m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::Fast, false, !xy_overrides_z); // prevent rounding errors down the line forbidden_next = offset(union_ex(forbidden_next_raw), scaled(0.005), jtMiter, 1.2); } @@ -1314,11 +1305,11 @@ void TreeSupport::generateInitialAreas( size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; if (dtt_roof != 0) { // Produce support head points supporting an interface layer: First produce the interface lines, then sample them. - overhang_lines = convertLinesToInternal(m_volumes, m_config, ensureMaximumDistancePolyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); + overhang_lines = convertLinesToInternal(m_volumes, m_config, + ensureMaximumDistancePolyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); overhang_lines = splitLines(overhang_lines, [this, layer_idx, dtt_before](std::pair &p){ return evaluatePointForNextLayerFunction(m_volumes, m_config, layer_idx - dtt_before, p); }).first; } - break; } added_roofs[dtt_roof] = overhang_outset; @@ -1641,7 +1632,7 @@ static void mergeHelper( erase.emplace_back(reduced_check_iter->first); erase.emplace_back(influence_iter->first); - Polygons merge = diff_clipped(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound, scaled(0.01)), volumes.getCollision(0, layer_idx - 1)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. + Polygons merge = diff_clipped(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound, scaled(0.01)), volumes.getCollision(0, layer_idx - 1, false)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. reduced_aabb.erase(reduced_check_iter->first); // this invalidates reduced_check_iter reduced_aabb.emplace(key, get_extents(merge)); @@ -1692,7 +1683,7 @@ static void mergeInfluenceAreas( // max_bucket_count is input_size/min_elements_per_bucket round down to the next 2^n. // The rounding to 2^n is to ensure improved performance, as every iteration two buckets will be merged, halving the amount of buckets. // If halving would cause an uneven count, e.g. 3 Then bucket 0 and 1 would have to be merged, and in the next iteration the last remaining buckets. This is assumed to not be optimal performance-wise. - const size_t max_bucket_count = std::pow(2, std::floor(std::log(round_up_divide(input_size, min_elements_per_bucket)))); + const size_t max_bucket_count = std::pow(2, std::floor(std::log(round_up_divide(input_size, min_elements_per_bucket)))); int bucket_count = std::min(max_bucket_count, num_threads); // do not use more buckets than available threads. // To achieve that every element in a bucket is already correctly merged with other elements in this bucket @@ -1946,7 +1937,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to }; const bool parent_moved_slow = elem.last_area_increase.increase_speed < m_config.maximum_move_distance; - const bool avoidance_speed_mismatch = parent_moved_slow && elem.last_area_increase.type != AvoidanceType::SLOW; + const bool avoidance_speed_mismatch = parent_moved_slow && elem.last_area_increase.type != AvoidanceType::Slow; if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && !mergelayer && !avoidance_speed_mismatch && (elem.distance_to_top >= m_config.tip_layers || parent_moved_slow)) { // assume that the avoidance type that was best for the parent is best for me. Makes this function about 7% faster. @@ -1957,27 +1948,27 @@ void TreeSupport::increaseAreas(std::unordered_map& to if (!elem.can_use_safe_radius) { // if the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. - // order.emplace_back(AvoidanceType::SLOW,!increase_radius,no_error,!use_min_radius,move); - insertSetting({ AvoidanceType::SLOW, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we go through the hole + // order.emplace_back(AvoidanceType::Slow,!increase_radius,no_error,!use_min_radius,move); + insertSetting({ AvoidanceType::Slow, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we go through the hole // in many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. This CAN cause a branch to go though a hole it otherwise may have avoided. - if (elem.distance_to_top < round_up_divide(m_config.tip_layers, 2)) - insertSetting({ AvoidanceType::FAST, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); - insertSetting({ AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we manage to avoid the hole - insertSetting({ AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); - insertSetting({ AvoidanceType::FAST, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); + if (elem.distance_to_top < round_up_divide(m_config.tip_layers, size_t(2))) + insertSetting({ AvoidanceType::Fast, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); + insertSetting({ AvoidanceType::FastSafe, fast_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we manage to avoid the hole + insertSetting({ AvoidanceType::FastSafe, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); + insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); } else { - insertSetting({ AvoidanceType::SLOW, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); + insertSetting({ AvoidanceType::Slow, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); // while moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, which looks similar to a layer shift and can reduce stability. // as such idx have chosen to only use the user setting for radius increases as a friendly recommendation. - insertSetting({ AvoidanceType::SLOW, slow_speed, !increase_radius, no_error, !use_min_radius, move }, true); // a + insertSetting({ AvoidanceType::Slow, slow_speed, !increase_radius, no_error, !use_min_radius, move }, true); // a if (elem.distance_to_top < m_config.tip_layers) { - insertSetting({ AvoidanceType::FAST_SAFE, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); + insertSetting({ AvoidanceType::FastSafe, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); } - insertSetting({ AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, !use_min_radius, move }, true); // b - insertSetting({ AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); + insertSetting({ AvoidanceType::FastSafe, fast_speed, increase_radius, no_error, !use_min_radius, move }, true); // b + insertSetting({ AvoidanceType::FastSafe, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); } if (elem.use_min_xy_dist) @@ -1994,11 +1985,11 @@ void TreeSupport::increaseAreas(std::unordered_map& to if (elem.to_buildplate || (elem.to_model_gracious && intersection(*parent->area, m_volumes.getPlaceableAreas(radius, layer_idx)).empty())) // error case { // it is normal that we wont be able to find a new area at some point in time if we wont be able to reach layer 0 aka have to connect with the model - insertSetting({ AvoidanceType::FAST, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move }, true); + insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move }, true); } if (elem.distance_to_top < elem.dont_move_until && elem.can_use_safe_radius) // only do not move when holes would be avoided in every case. // Only do not move when already in a no hole avoidance with the regular xy distance. - insertSetting({ AvoidanceType::SLOW, 0, increase_radius, no_error, !use_min_radius, !move }, false); + insertSetting({ AvoidanceType::Slow, 0, increase_radius, no_error, !use_min_radius, !move }, false); Polygons inc_wo_collision; // Check whether it is faster to calculate the area increased with the fast speed independently from the slow area, or time could be saved by reusing the slow area to calculate the fast one. @@ -2057,7 +2048,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to else elem.result_on_layer = parent->result_on_layer; - elem.can_use_safe_radius = settings.type != AvoidanceType::FAST; + elem.can_use_safe_radius = settings.type != AvoidanceType::Fast; if (!settings.use_min_distance) elem.use_min_xy_dist = false; @@ -2368,7 +2359,7 @@ void TreeSupport::generateBranchAreas( Polygon branch_circle; // Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time. for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; ++ i) { - const AngleRadians angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); + const double angle = static_cast(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI); branch_circle.points.emplace_back(coord_t(cos(angle) * m_config.branch_radius), coord_t(sin(angle) * m_config.branch_radius)); } @@ -2594,7 +2585,7 @@ void TreeSupport::dropNonGraciousAreas( Polygons rest_support = layer_tree_polygons[linear_data[idx].first][elem]; LayerIndex counter = 1; while (area(rest_support) > tiny_area_threshold && counter < linear_data[idx].first) { - rest_support = diff_clipped(rest_support, m_volumes.getCollision(0, linear_data[idx].first - counter)); + rest_support = diff_clipped(rest_support, m_volumes.getCollision(0, linear_data[idx].first - counter, false)); dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support); counter++; } diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 1514c2e58..041344375 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -15,6 +15,7 @@ #include // For combining hashes #include "BoundingBox.hpp" +#include "Utils.hpp" #define TREE_SUPPORT_SHOW_ERRORS @@ -95,7 +96,7 @@ public: struct AreaIncreaseSettings { - AvoidanceType type { AvoidanceType::FAST }; + AvoidanceType type { AvoidanceType::Fast }; coord_t increase_speed { 0 }; bool increase_radius { false }; bool no_error { false }; @@ -116,7 +117,7 @@ public: target_height(target_height), target_position(target_position), next_position(target_position), next_height(target_height), effective_radius_height(distance_to_top), to_buildplate(to_buildplate), distance_to_top(distance_to_top), area(nullptr), result_on_layer(target_position), increased_to_model_radius(0), to_model_gracious(to_model_gracious), elephant_foot_increases(0), use_min_xy_dist(use_min_xy_dist), supports_roof(supports_roof), dont_move_until(dont_move_until), can_use_safe_radius(can_use_safe_radius), - last_area_increase(AreaIncreaseSettings{ AvoidanceType::FAST, 0, false, false, false, false }), missing_roof_layers(force_tips_to_roof ? dont_move_until : 0), skip_ovalisation(skip_ovalisation) + last_area_increase(AreaIncreaseSettings{ AvoidanceType::Fast, 0, false, false, false, false }), missing_roof_layers(force_tips_to_roof ? dont_move_until : 0), skip_ovalisation(skip_ovalisation) { } @@ -176,16 +177,19 @@ public: } // ONLY to be called in merge as it assumes a few assurances made by it. - explicit SupportElement(const SupportElement& first, const SupportElement& second, size_t next_height, Point next_position, coord_t increased_to_model_radius, const TreeSupportSettings& config) : next_position(next_position), next_height(next_height), area(nullptr), increased_to_model_radius(increased_to_model_radius), use_min_xy_dist(first.use_min_xy_dist || second.use_min_xy_dist), supports_roof(first.supports_roof || second.supports_roof), dont_move_until(std::max(first.dont_move_until, second.dont_move_until)), can_use_safe_radius(first.can_use_safe_radius || second.can_use_safe_radius), missing_roof_layers(std::min(first.missing_roof_layers, second.missing_roof_layers)), skip_ovalisation(false) + explicit SupportElement( + const SupportElement& first, const SupportElement& second, size_t next_height, Point next_position, + coord_t increased_to_model_radius, const TreeSupportSettings& config) : + next_position(next_position), next_height(next_height), area(nullptr), increased_to_model_radius(increased_to_model_radius), + use_min_xy_dist(first.use_min_xy_dist || second.use_min_xy_dist), supports_roof(first.supports_roof || second.supports_roof), + dont_move_until(std::max(first.dont_move_until, second.dont_move_until)), can_use_safe_radius(first.can_use_safe_radius || second.can_use_safe_radius), + missing_roof_layers(std::min(first.missing_roof_layers, second.missing_roof_layers)), skip_ovalisation(false) { - if (first.target_height > second.target_height) - { + if (first.target_height > second.target_height) { target_height = first.target_height; target_position = first.target_position; - } - else - { + } else { target_height = second.target_height; target_position = second.target_position; } @@ -199,8 +203,7 @@ public: AddParents(second.parents); elephant_foot_increases = 0; - if (config.diameter_scale_bp_radius > 0) - { + if (config.diameter_scale_bp_radius > 0) { coord_t foot_increase_radius = std::abs(std::max(config.getCollisionRadius(second), config.getCollisionRadius(first)) - config.getCollisionRadius(*this)); // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch // the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. @@ -406,7 +409,7 @@ public: xy_distance = std::max(xy_distance, xy_min_distance); // (logic) from getInterfaceAngles in FFFGcodeWriter. - auto getInterfaceAngles = [&](std::vector& angles, SupportMaterialInterfacePattern pattern) { + auto getInterfaceAngles = [&](std::vector& angles, SupportMaterialInterfacePattern pattern) { if (angles.empty()) { if (pattern == SupportMaterialInterfacePattern::smipConcentric) @@ -543,11 +546,11 @@ public: /*! * \brief User specified angles for the support infill. */ - std::vector support_infill_angles; + std::vector support_infill_angles; /*! * \brief User specified angles for the support roof infill. */ - std::vector support_roof_angles; + std::vector support_roof_angles; /*! * \brief Pattern used in the support roof. May contain non relevant data if support roof is disabled. */ @@ -613,7 +616,7 @@ public: || (settings.get("fill_outline_gaps") == other.settings.get("fill_outline_gaps") && settings.get("min_bead_width") == other.settings.get("min_bead_width") && - settings.get("wall_transition_angle") == other.settings.get("wall_transition_angle") && + settings.get("wall_transition_angle") == other.settings.get("wall_transition_angle") && settings.get("wall_transition_length") == other.settings.get("wall_transition_length") && settings.get("wall_split_middle_threshold") == other.settings.get("wall_split_middle_threshold") && settings.get("wall_add_middle_threshold") == other.settings.get("wall_add_middle_threshold") && diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 6a09e7fbf..73bb418af 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -192,6 +192,14 @@ inline INDEX_TYPE next_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count) return idx; } + +// Return dividend divided by divisor rounded to the nearest integer +template +inline INDEX_TYPE round_up_divide(const INDEX_TYPE dividend, const INDEX_TYPE divisor) +{ + return (dividend + divisor - 1) / divisor; +} + template inline typename CONTAINER_TYPE::size_type prev_idx_modulo(typename CONTAINER_TYPE::size_type idx, const CONTAINER_TYPE &container) { From 01031779b788be29e186d52090b140b254914948 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 18 Aug 2022 10:30:20 +0200 Subject: [PATCH 21/29] Performance improvement of GCodeProcessor: Replaced std::string with std::string_view for constant parameters Replaced boost:istarts_with() with boost::starts_with() --- src/libslic3r/GCode/GCodeProcessor.cpp | 9 +++++++-- src/slic3r/GUI/ImGuiWrapper.cpp | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index cfdcf6d1e..f85088b08 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -3721,8 +3721,13 @@ void GCodeProcessor::post_process() } auto process_used_filament = [&](std::string& gcode_line) { - auto process_tag = [](std::string& gcode_line, const std::string& tag, const std::vector& values) { - if (boost::algorithm::istarts_with(gcode_line, tag)) { + // Prefilter for parsing speed. + if (gcode_line.size() < 8 || gcode_line[0] != ';' || gcode_line[1] != ' ') + return false; + if (const char c = gcode_line[2]; c != 'f' && c != 't') + return false; + auto process_tag = [](std::string& gcode_line, const std::string_view tag, const std::vector& values) { + if (boost::algorithm::starts_with(gcode_line, tag)) { gcode_line = tag; char buf[1024]; for (size_t i = 0; i < values.size(); ++i) { diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 5ab4685bf..7345c73b4 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -544,7 +544,7 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; // let the label string start with "##" to hide the automatic label from ImGui::SliderFloat() - bool label_visible = !boost::algorithm::istarts_with(label, "##"); + bool label_visible = !boost::algorithm::starts_with(label, "##"); std::string str_label = label_visible ? std::string("##") + std::string(label) : std::string(label); // removes 2nd evenience of "##", if present From c03085a1f679c1104f9ae09ecb66902ef6fe5cef Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 18 Aug 2022 10:45:18 +0200 Subject: [PATCH 22/29] Performance improvement in G-code export for support material in suppression of retracts when traveling over support regions. --- src/libslic3r/GCode.cpp | 29 ++++++++++++++++++++--------- src/libslic3r/Layer.hpp | 2 ++ src/libslic3r/SupportMaterial.cpp | 9 +++++++-- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d836ac43c..1ddee6b7c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3134,15 +3134,26 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) return false; } - if (role == erSupportMaterial) { - const SupportLayer* support_layer = dynamic_cast(m_layer); - //FIXME support_layer->support_islands.contains should use some search structure! - if (support_layer != NULL && ! intersection_pl(travel, support_layer->support_islands).empty()) - // skip retraction if this is a travel move inside a support material island - //FIXME not retracting over a long path may cause oozing, which in turn may result in missing material - // at the end of the extrusion path! - return false; - } + if (role == erSupportMaterial) + if (const SupportLayer *support_layer = dynamic_cast(m_layer); + support_layer != nullptr && ! support_layer->support_islands_bboxes.empty()) { + BoundingBox bbox_travel = get_extents(travel); + Polylines trimmed; + bool trimmed_initialized = false; + for (const BoundingBox &bbox : support_layer->support_islands_bboxes) + if (bbox.overlap(bbox_travel)) { + const auto &island = support_layer->support_islands[&bbox - support_layer->support_islands_bboxes.data()]; + trimmed = trimmed_initialized ? diff_pl(trimmed, island) : diff_pl(travel, island); + trimmed_initialized = true; + if (trimmed.empty()) + // skip retraction if this is a travel move inside a support material island + //FIXME not retracting over a long path may cause oozing, which in turn may result in missing material + // at the end of the extrusion path! + return false; + // Not sure whether updating the boudning box isn't too expensive. + //bbox_travel = get_extents(trimmed); + } + } if (m_config.only_retract_when_crossing_perimeters && m_layer != nullptr && m_config.fill_density.value > 0 && m_layer->any_internal_region_slice_contains(travel)) diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 21de030cd..e11c740e6 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -197,6 +197,8 @@ public: // Polygons covered by the supports: base, interface and contact areas. // Used to suppress retraction if moving for a support extrusion over these support_islands. ExPolygons support_islands; + // Slightly inflated bounding boxes of the above, for faster intersection query. + std::vector support_islands_bboxes; // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 340449ec8..0f704c3d6 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -4312,8 +4312,13 @@ void generate_support_toolpaths( // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); } - if (! polys.empty()) - expolygons_append(support_layer.support_islands, union_ex(polys)); + assert(support_layer.support_islands.empty()); + if (! polys.empty()) { + support_layer.support_islands = union_ex(polys); + support_layer.support_islands_bboxes.reserve(support_layer.support_islands.size()); + for (const ExPolygon &expoly : support_layer.support_islands) + support_layer.support_islands_bboxes.emplace_back(get_extents(expoly).inflated(SCALED_EPSILON)); + } } // for each support_layer_id }); From 55835aa05076954f3960b35084ebcce000ddbe29 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 18 Aug 2022 13:32:30 +0200 Subject: [PATCH 23/29] Optimization of GCodeViewer vertex buffer generator: Rounding by round_to_bin() is now 2x faster on MSVC. --- src/libslic3r/libslic3r.h | 19 ++++++++++++++++++ src/slic3r/GUI/GCodeViewer.cpp | 9 +++++++-- tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_utils.cpp | 35 ++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/libslic3r/test_utils.cpp diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 6f71930ab..2285c29a6 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -345,6 +345,25 @@ constexpr T NaN = std::numeric_limits::quiet_NaN(); constexpr float NaNf = NaN; constexpr double NaNd = NaN; +// Rounding up. +// 1.5 is rounded to 2 +// 1.49 is rounded to 1 +// 0.5 is rounded to 1, +// 0.49 is rounded to 0 +// -0.5 is rounded to 0, +// -0.51 is rounded to -1, +// -1.5 is rounded to -1. +// -1.51 is rounded to -2. +// If input is not a valid float (it is infinity NaN or if it does not fit) +// the float to int conversion produces a max int on Intel and +-max int on ARM. +template +inline IntegerOnly fast_round_up(double a) +{ + // Why does Java Math.round(0.49999999999999994) return 1? + // https://stackoverflow.com/questions/9902968/why-does-math-round0-49999999999999994-return-1 + return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5)); +} + } // namespace Slic3r #endif diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 1e28d1287..8167507b4 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -51,7 +51,7 @@ static EMoveType buffer_type(unsigned char id) { // Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster. static float round_to_bin(const float value) { -// assert(value > 0); +// assert(value >= 0); constexpr float const scale [5] = { 100.f, 1000.f, 10000.f, 100000.f, 1000000.f }; constexpr float const invscale [5] = { 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f }; constexpr float const threshold[5] = { 0.095f, 0.0095f, 0.00095f, 0.000095f, 0.0000095f }; @@ -59,7 +59,12 @@ static float round_to_bin(const float value) int i = 0; // While the scaling factor is not yet large enough to get two integer digits after scaling and rounding: for (; value < threshold[i] && i < 4; ++ i) ; - return std::round(value * scale[i]) * invscale[i]; + // At least on MSVC std::round() calls a complex function, which is pretty expensive. + // our fast_round_up is much cheaper and it could be inlined. +// return std::round(value * scale[i]) * invscale[i]; + double a = value * scale[i]; + assert(std::abs(a) < double(std::numeric_limits::max())); + return fast_round_up(a) * invscale[i]; } void GCodeViewer::VBuffer::reset() diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index cf89b2246..c47fa6109 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable(${_TEST_NAME}_tests test_meshboolean.cpp test_marchingsquares.cpp test_timeutils.cpp + test_utils.cpp test_voronoi.cpp test_optimizers.cpp test_png_io.cpp diff --git a/tests/libslic3r/test_utils.cpp b/tests/libslic3r/test_utils.cpp new file mode 100644 index 000000000..74d409496 --- /dev/null +++ b/tests/libslic3r/test_utils.cpp @@ -0,0 +1,35 @@ +#include + +#include "libslic3r/libslic3r.h" + +SCENARIO("Test fast_round_up()") { + using namespace Slic3r; + + THEN("fast_round_up(1.5) is 2") { + REQUIRE(fast_round_up(1.5) == 2); + } + THEN("fast_round_up(1.499999999999999) is 1") { + REQUIRE(fast_round_up(1.499999999999999) == 1); + } + THEN("fast_round_up(0.5) is 1") { + REQUIRE(fast_round_up(0.5) == 1); + } + THEN("fast_round_up(0.49999999999999994) is 0") { + REQUIRE(fast_round_up(0.49999999999999994) == 0); + } + THEN("fast_round_up(-0.5) is 0") { + REQUIRE(fast_round_up(-0.5) == 0); + } + THEN("fast_round_up(-0.51) is -1") { + REQUIRE(fast_round_up(-0.51) == -1); + } + THEN("fast_round_up(-0.51) is -1") { + REQUIRE(fast_round_up(-0.51) == -1); + } + THEN("fast_round_up(-1.5) is -1") { + REQUIRE(fast_round_up(-1.5) == -1); + } + THEN("fast_round_up(-1.51) is -2") { + REQUIRE(fast_round_up(-1.51) == -2); + } +} From 781a9535dbd56c5ed8104bf825665961963cb2c6 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 19 Aug 2022 15:03:39 +0200 Subject: [PATCH 24/29] Fixed performance issue at backgorund processing update with a large number of modifier meshes: Bounding box of a chain of modifier meshes overlapping with an object bounding box was not correctly calculated (bounding boxes were unioned instead of intersected). --- src/libslic3r/PrintApply.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 4c085728c..1cae900e7 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -626,7 +626,8 @@ PrintObjectRegions::BoundingBox find_modifier_volume_extents(const PrintObjectRe const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; const PrintObjectRegions::BoundingBox *parent_extents = find_volume_extents(layer_range, *parent_region.model_volume); assert(parent_extents); - out.extend(*parent_extents); + out.clamp(*parent_extents); + assert(! out.isEmpty()); if (parent_region.model_volume->is_model_part()) break; parent_region_id = parent_region.parent; From 7949ea954462de83b127a0120aa1163ec2d59b3c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 22 Aug 2022 15:14:54 +0200 Subject: [PATCH 25/29] Fixed some compiler warnings. Removed some implicit type conversions of ExPolygon (to Points, Polylines, Polygons) Fixed obsolete boost::spirit includes. --- src/libslic3r/Arachne/utils/ExtrusionLine.cpp | 4 +-- src/libslic3r/Brim.cpp | 2 +- src/libslic3r/ClipperUtils.cpp | 8 ++++++ src/libslic3r/ClipperUtils.hpp | 3 +++ src/libslic3r/ExPolygon.cpp | 23 +--------------- src/libslic3r/ExPolygon.hpp | 16 ++++++++--- src/libslic3r/Fill/FillRectilinear.cpp | 2 +- src/libslic3r/Fill/Lightning/Generator.cpp | 2 +- src/libslic3r/PlaceholderParser.cpp | 10 +++---- src/libslic3r/SLA/Pad.cpp | 2 +- src/libslic3r/SVG.cpp | 8 +++--- src/libslic3r/SurfaceCollection.cpp | 27 ++++++++++++++----- src/libslic3r/SurfaceCollection.hpp | 3 ++- tests/libslic3r/test_clipper_utils.cpp | 2 +- 14 files changed, 62 insertions(+), 50 deletions(-) diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp index 6a6ac0f0f..a5734f478 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp @@ -268,13 +268,13 @@ void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extr { for (const ClipperLib_Z::Path &extrusion_path : extrusion_paths) { ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion_path); - Slic3r::append(dst, thick_polyline_to_extrusion_paths(thick_polyline, role, flow, scaled(0.05), SCALED_EPSILON)); + Slic3r::append(dst, thick_polyline_to_extrusion_paths(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON))); } } void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow) { ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion); - Slic3r::append(dst, thick_polyline_to_extrusion_paths(thick_polyline, role, flow, scaled(0.05), SCALED_EPSILON)); + Slic3r::append(dst, thick_polyline_to_extrusion_paths(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON))); } } // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index f48d7ff0a..9ed56bea4 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -332,7 +332,7 @@ static std::vector inner_brim_area(const Print append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly_holes_reversed)); if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, diff_ex(ExPolygon(ex_poly.contour), shrink_ex(ex_poly_holes_reversed, no_brim_offset, ClipperLib::jtSquare))); + append(no_brim_area_object, diff_ex(ex_poly.contour, shrink_ex(ex_poly_holes_reversed, no_brim_offset, ClipperLib::jtSquare))); append(holes_reversed_object, ex_poly_holes_reversed); } diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index c6617002a..061dd0379 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -556,6 +556,8 @@ Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &c { return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } +Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset) + { return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset) @@ -576,6 +578,8 @@ Slic3r::Polygons union_(const Slic3r::ExPolygons &subject) { return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); } Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2) { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ApplySafetyOffset::No); } +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &subject2) + { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonProvider(subject2), ApplySafetyOffset::No); } template static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero) @@ -585,6 +589,8 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygo { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::Polygon &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset) @@ -713,6 +719,8 @@ Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygon { return _clipper_pl_closed(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip) { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::SinglePathProvider(clip.points)); } +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip) + { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip) { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::PolygonsProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygons &clip) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index d913154e0..c06fc82a8 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -419,6 +419,7 @@ inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygon // Safety offset is applied to the clipping polygons only. Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); @@ -435,6 +436,7 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip); +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); @@ -457,6 +459,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject); Slic3r::Polygons union_(const Slic3r::ExPolygons &subject); Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType); Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2); +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &subject2); // May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative). Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero); Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject); diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 069c6e602..7e22127cd 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -12,27 +12,6 @@ namespace Slic3r { -ExPolygon::operator Points() const -{ - Points points; - Polygons pp = *this; - for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) { - for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point) - points.push_back(*point); - } - return points; -} - -ExPolygon::operator Polygons() const -{ - return to_polygons(*this); -} - -ExPolygon::operator Polylines() const -{ - return to_polylines(*this); -} - void ExPolygon::scale(double factor) { contour.scale(factor); @@ -149,7 +128,7 @@ bool ExPolygon::overlaps(const ExPolygon &other) const svg.draw_outline(*this); svg.draw_outline(other, "blue"); #endif - Polylines pl_out = intersection_pl((Polylines)other, *this); + Polylines pl_out = intersection_pl(to_polylines(other), *this); #if 0 svg.draw(pl_out, "red"); #endif diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 7eccf2ec8..3e42c8670 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -34,9 +34,6 @@ public: Polygon contour; Polygons holes; - operator Points() const; - operator Polygons() const; - operator Polylines() const; void clear() { contour.points.clear(); holes.clear(); } void scale(double factor); void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } @@ -281,6 +278,19 @@ inline ExPolygons to_expolygons(Polygons &&polys) return ex_polys; } +inline Points to_points(const ExPolygon &expoly) +{ + size_t cnt = expoly.contour.size(); + for (const Polygon &hole : expoly.holes) + cnt += hole.size(); + Points out; + out.reserve(cnt); + append(out, expoly.contour.points); + for (const Polygon &hole : expoly.holes) + append(out, hole.points); + return out; +} + inline void polygons_append(Polygons &dst, const ExPolygon &src) { dst.reserve(dst.size() + src.holes.size() + 1); diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index ba7461c5f..bb93d824b 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -414,7 +414,7 @@ public: // bool sticks_removed = remove_sticks(polygons_src); // if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!"; - polygons_outer = aoffset1 == 0 ? polygons_src : offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit); + polygons_outer = aoffset1 == 0 ? to_polygons(polygons_src) : offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit); if (aoffset2 < 0) polygons_inner = shrink(polygons_outer, float(aoffset1 - aoffset2), ClipperLib::jtMiter, miterLimit); // Filter out contours with zero area or small area, contours with 2 points only. diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index 48377f427..bd83bcfff 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -70,7 +70,7 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object // Remove the part of the infill area that is already supported by the walls. Polygons overhang = diff(offset(infill_area_here, -float(m_wall_supporting_radius)), infill_area_above); // Filter out unprintable polygons and near degenerated polygons (three almost collinear points and so). - overhang = opening(overhang, SCALED_EPSILON, SCALED_EPSILON); + overhang = opening(overhang, float(SCALED_EPSILON), float(SCALED_EPSILON)); m_overhang_per_layer[layer_nr] = overhang; infill_area_above = std::move(infill_area_here); diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index e84ddc503..109c949cd 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -40,11 +40,11 @@ #include #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index b04c3f219..ec9e216f5 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -204,7 +204,7 @@ public: void add(const ExPolygon &ep) { m_polys.emplace_back(ep); - m_index.insert(BoundingBox{ep}, unsigned(m_index.size())); + m_index.insert(get_extents(ep), unsigned(m_index.size())); } // Check an arbitrary polygon for intersection with the indexed polygons diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 6a743b1eb..8150d9db9 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -88,10 +88,8 @@ void SVG::draw(const ExPolygon &expolygon, std::string fill, const float fill_op this->fill = fill; std::string d; - Polygons pp = expolygon; - for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { - d += this->get_path_d(*p, true) + " "; - } + for (const Polygon &p : to_polygons(expolygon)) + d += this->get_path_d(p, true) + " "; this->path(d, true, 0, fill_opacity); } @@ -356,7 +354,7 @@ void SVG::export_expolygons(const char *path, const std::vector 0) for (const ExPolygon &expoly : exp_with_attr.first) - svg.draw((Points)expoly, exp_with_attr.second.color_points, exp_with_attr.second.radius_points); + svg.draw(to_points(expoly), exp_with_attr.second.color_points, exp_with_attr.second.radius_points); // Export legend. // 1st row diff --git a/src/libslic3r/SurfaceCollection.cpp b/src/libslic3r/SurfaceCollection.cpp index ec847d2a3..e84b44838 100644 --- a/src/libslic3r/SurfaceCollection.cpp +++ b/src/libslic3r/SurfaceCollection.cpp @@ -65,14 +65,11 @@ SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int nty return ss; } -void SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons) +void SurfaceCollection::filter_by_type(SurfaceType type, Polygons *polygons) const { - for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { - if (surface->surface_type == type) { - Polygons pp = surface->expolygon; - polygons->insert(polygons->end(), pp.begin(), pp.end()); - } - } + for (const Surface &surface : this->surfaces) + if (surface.surface_type == type) + polygons_append(*polygons, to_polygons(surface.expolygon)); } void SurfaceCollection::keep_type(const SurfaceType type) @@ -124,6 +121,22 @@ void SurfaceCollection::remove_type(const SurfaceType type) surfaces.erase(surfaces.begin() + j, surfaces.end()); } +void SurfaceCollection::remove_type(const SurfaceType type, Polygons *polygons) +{ + size_t j = 0; + for (size_t i = 0; i < surfaces.size(); ++ i) { + if (Surface &surface = surfaces[i]; surface.surface_type == type) { + polygons_append(*polygons, to_polygons(std::move(surface.expolygon))); + } else { + if (j < i) + std::swap(surfaces[i], surfaces[j]); + ++ j; + } + } + if (j < surfaces.size()) + surfaces.erase(surfaces.begin() + j, surfaces.end()); +} + void SurfaceCollection::remove_types(const SurfaceType *types, int ntypes) { size_t j = 0; diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index 7e01a68df..353e96aa1 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -32,7 +32,8 @@ public: void keep_types(const SurfaceType *types, int ntypes); void remove_type(const SurfaceType type); void remove_types(const SurfaceType *types, int ntypes); - void filter_by_type(SurfaceType type, Polygons* polygons); + void filter_by_type(SurfaceType type, Polygons *polygons) const; + void remove_type(const SurfaceType type, Polygons *polygons); void set_type(SurfaceType type) { for (Surface &surface : this->surfaces) surface.surface_type = type; diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index 048ebba10..a1230ac03 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -294,7 +294,7 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") { WHEN("clipping a line") { auto line = Polyline::new_scale({ { 152.742,288.086671142818 }, { 152.742,34.166466971035 } }); - Polylines intersection = intersection_pl(line, Polygons{ circle_with_hole }); + Polylines intersection = intersection_pl(line, to_polygons(circle_with_hole)); THEN("clipped to two pieces") { REQUIRE(intersection.front().length() == Approx((Vec2d(152742000, 215178843) - Vec2d(152742000, 288086661)).norm())); REQUIRE(intersection[1].length() == Approx((Vec2d(152742000, 35166477) - Vec2d(152742000, 108087507)).norm())); From 14e0cd0e960b5f384511541bc1ed8089eb4c2436 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 22 Aug 2022 15:41:36 +0200 Subject: [PATCH 26/29] Parallelized PrintObject::bridge_over_infill() --- src/libslic3r/PrintObject.cpp | 96 ++++++++++++++++------------- src/libslic3r/SurfaceCollection.cpp | 4 +- src/libslic3r/SurfaceCollection.hpp | 2 +- 3 files changed, 56 insertions(+), 46 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 030909641..9909fbb48 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1441,56 +1441,64 @@ void PrintObject::discover_vertical_shells() // PROFILE_OUTPUT(debug_out_path("discover_vertical_shells-profile.txt").c_str()); } -/* This method applies bridge flow to the first internal solid layer above - sparse infill */ +// This method applies bridge flow to the first internal solid layer above sparse infill. void PrintObject::bridge_over_infill() { BOOST_LOG_TRIVIAL(info) << "Bridge over infill..." << log_memory_info(); - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { - const PrintRegion ®ion = this->printing_region(region_id); - - // skip bridging in case there are no voids - if (region.config().fill_density.value == 100) - continue; + std::vector sparse_infill_regions; + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) + if (const PrintRegion ®ion = this->printing_region(region_id); region.config().fill_density.value < 100) + sparse_infill_regions.emplace_back(region_id); + if (this->layer_count() < 2 || sparse_infill_regions.empty()) + return; - for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) { - // skip first layer - if (layer_it == m_layers.begin()) - continue; - - Layer *layer = *layer_it; + // Collect sum of all internal (sparse infill) regions, because + // 1) layerm->fill_surfaces.will be modified in parallel. + // 2) the parallel loop works on a sum of surfaces over regions anyways, thus collecting the sparse infill surfaces + // up front is an optimization. + std::vector internals; + internals.reserve(this->layer_count()); + for (Layer *layer : m_layers) { + Polygons sum; + for (const LayerRegion *layerm : layer->m_regions) + layerm->fill_surfaces.filter_by_type(stInternal, &sum); + internals.emplace_back(std::move(sum)); + } + + // Process all regions and layers in parallel. + tbb::parallel_for(tbb::blocked_range(0, sparse_infill_regions.size() * (this->layer_count() - 1), sparse_infill_regions.size()), + [this, &sparse_infill_regions, &internals] + (const tbb::blocked_range &range) { + for (size_t task_id = range.begin(); task_id != range.end(); ++ task_id) { + const size_t layer_id = (task_id / sparse_infill_regions.size()) + 1; + const size_t region_id = sparse_infill_regions[task_id % sparse_infill_regions.size()]; + Layer *layer = this->get_layer(layer_id); LayerRegion *layerm = layer->m_regions[region_id]; Flow bridge_flow = layerm->bridging_flow(frSolidInfill); - // extract the stInternalSolid surfaces that might be transformed into bridges - Polygons internal_solid; - layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid); - + // Extract the stInternalSolid surfaces that might be transformed into bridges. + ExPolygons internal_solid; + layerm->fill_surfaces.remove_type(stInternalSolid, &internal_solid); + if (internal_solid.empty()) + // No internal solid -> no new bridges for this layer region. + continue; + // check whether the lower area is deep enough for absorbing the extra flow // (for obvious physical reasons but also for preventing the bridge extrudates // from overflowing in 3D preview) ExPolygons to_bridge; { - Polygons to_bridge_pp = internal_solid; - - // iterate through lower layers spanned by bridge_flow + Polygons to_bridge_pp = to_polygons(internal_solid); + // Iterate through lower layers spanned by bridge_flow. double bottom_z = layer->print_z - bridge_flow.height() - EPSILON; - for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) { - const Layer* lower_layer = m_layers[i]; - - // stop iterating if layer is lower than bottom_z - if (lower_layer->print_z < bottom_z) break; - - // iterate through regions and collect internal surfaces - Polygons lower_internal; - for (LayerRegion *lower_layerm : lower_layer->m_regions) - lower_layerm->fill_surfaces.filter_by_type(stInternal, &lower_internal); - - // intersect such lower internal surfaces with the candidate solid surfaces - to_bridge_pp = intersection(to_bridge_pp, lower_internal); + for (auto i = int(layer_id) - 1; i >= 0; -- i) { + // Stop iterating if layer is lower than bottom_z. + if (m_layers[i]->print_z < bottom_z) + break; + // Intersect lower sparse infills with the candidate solid surfaces. + to_bridge_pp = intersection(to_bridge_pp, internals[i]); } - // there's no point in bridging too thin/short regions //FIXME Vojtech: The offset2 function is not a geometric offset, // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. @@ -1500,12 +1508,16 @@ void PrintObject::bridge_over_infill() to_bridge_pp = opening(to_bridge_pp, min_width); } - if (to_bridge_pp.empty()) continue; - + if (to_bridge_pp.empty()) { + // Restore internal_solid surfaces. + for (ExPolygon &ex : internal_solid) + layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); + continue; + } // convert into ExPolygons to_bridge = union_ex(to_bridge_pp); } - + #ifdef SLIC3R_DEBUG printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); #endif @@ -1514,11 +1526,10 @@ void PrintObject::bridge_over_infill() ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, ApplySafetyOffset::Yes); to_bridge = intersection_ex(to_bridge, internal_solid, ApplySafetyOffset::Yes); // build the new collection of fill_surfaces - layerm->fill_surfaces.remove_type(stInternalSolid); for (ExPolygon &ex : to_bridge) - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalBridge, ex)); + layerm->fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex))); for (ExPolygon &ex : not_to_bridge) - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, ex)); + layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); /* # exclude infill from the layers below if needed # see discussion at https://github.com/alexrj/Slic3r/issues/240 @@ -1552,14 +1563,13 @@ void PrintObject::bridge_over_infill() } } */ - #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_slices_to_svg_debug("7_bridge_over_infill"); layerm->export_region_fill_surfaces_to_svg_debug("7_bridge_over_infill"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ m_print->throw_if_canceled(); } - } + }); } static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) diff --git a/src/libslic3r/SurfaceCollection.cpp b/src/libslic3r/SurfaceCollection.cpp index e84b44838..2ec071f7d 100644 --- a/src/libslic3r/SurfaceCollection.cpp +++ b/src/libslic3r/SurfaceCollection.cpp @@ -121,12 +121,12 @@ void SurfaceCollection::remove_type(const SurfaceType type) surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void SurfaceCollection::remove_type(const SurfaceType type, Polygons *polygons) +void SurfaceCollection::remove_type(const SurfaceType type, ExPolygons *polygons) { size_t j = 0; for (size_t i = 0; i < surfaces.size(); ++ i) { if (Surface &surface = surfaces[i]; surface.surface_type == type) { - polygons_append(*polygons, to_polygons(std::move(surface.expolygon))); + polygons->emplace_back(std::move(surface.expolygon)); } else { if (j < i) std::swap(surfaces[i], surfaces[j]); diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index 353e96aa1..8c27a507b 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -33,7 +33,7 @@ public: void remove_type(const SurfaceType type); void remove_types(const SurfaceType *types, int ntypes); void filter_by_type(SurfaceType type, Polygons *polygons) const; - void remove_type(const SurfaceType type, Polygons *polygons); + void remove_type(const SurfaceType type, ExPolygons *polygons); void set_type(SurfaceType type) { for (Surface &surface : this->surfaces) surface.surface_type = type; From 9aee934d530af9633e04dd842fd778b119039fbb Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 23 Aug 2022 11:28:25 +0200 Subject: [PATCH 27/29] Speed improvement of initial G-code preview: 1) Preallocating the vertex / index buffers to limit reallocation. 2) Inlining the pushing into the vertex / index buffers. 3) Running the vertex buffer generator on a limited number of threads as the generator does not scale well due to memory pressure. Not using all the threads leaves some of the threads to G-code generator. --- src/slic3r/GUI/3DScene.cpp | 7 +++++++ src/slic3r/GUI/GLCanvas3D.cpp | 6 ++++++ src/slic3r/GUI/GLModel.cpp | 25 ++----------------------- src/slic3r/GUI/GLModel.hpp | 15 +++++++++++++-- 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index f25b89da8..76ae5e755 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1429,6 +1429,13 @@ static void thick_lines_to_geometry( double width_initial = 0.0; double bottom_z_initial = 0.0; + // Reserve for a smooth path. Likley the path will not be that smooth, but better than nothing. + // Allocated 1.5x more data than minimum. + // Number of indices, not triangles. + geometry.reserve_more_indices((lines.size() * 8 * 3) * 3 / 2); + // Number of vertices, not floats. + geometry.reserve_more_vertices(((lines.size() + 1) * 4) * 3 / 2); + // loop once more in case of closed loops const size_t lines_end = closed ? (lines.size() + 1) : lines.size(); for (size_t ii = 0; ii < lines_end; ++ii) { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a5291481d..cc9e1307c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6873,6 +6873,11 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c return volume; }; const size_t volumes_cnt_initial = m_volumes.volumes.size(); + // Limit the number of threads as the code below does not scale well due to memory pressure. + // (most of the time is spent in malloc / free / memmove) + // Not using all the threads leaves some of the threads to G-code generator. + tbb::task_arena limited_arena(std::min(tbb::this_task_arena::max_concurrency(), 4)); + limited_arena.execute([&ctxt, grain_size, &new_volume, is_selected_separate_extruder, this]{ tbb::parallel_for( tbb::blocked_range(0, ctxt.layers.size(), grain_size), [&ctxt, &new_volume, is_selected_separate_extruder, this](const tbb::blocked_range& range) { @@ -7047,6 +7052,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c vol->indexed_vertex_array.shrink_to_fit(); #endif // ENABLE_LEGACY_OPENGL_REMOVAL }); + }); // task arena BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); // Remove empty volumes from the newly added volumes. diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index e6bdae415..6714630e5 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -94,22 +94,8 @@ void GLModel::Geometry::add_vertex(const Vec3f& position) void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec2f& tex_coord) { assert(format.vertex_layout == EVertexLayout::P3T2); - vertices.emplace_back(position.x()); - vertices.emplace_back(position.y()); - vertices.emplace_back(position.z()); - vertices.emplace_back(tex_coord.x()); - vertices.emplace_back(tex_coord.y()); -} - -void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal) -{ - assert(format.vertex_layout == EVertexLayout::P3N3); - vertices.emplace_back(position.x()); - vertices.emplace_back(position.y()); - vertices.emplace_back(position.z()); - vertices.emplace_back(normal.x()); - vertices.emplace_back(normal.y()); - vertices.emplace_back(normal.z()); + vertices.insert(vertices.end(), position.data(), position.data() + 3); + vertices.insert(vertices.end(), tex_coord.data(), tex_coord.data() + 2); } void GLModel::Geometry::add_index(unsigned int id) @@ -123,13 +109,6 @@ void GLModel::Geometry::add_line(unsigned int id1, unsigned int id2) indices.emplace_back(id2); } -void GLModel::Geometry::add_triangle(unsigned int id1, unsigned int id2, unsigned int id3) -{ - indices.emplace_back(id1); - indices.emplace_back(id2); - indices.emplace_back(id3); -} - Vec2f GLModel::Geometry::extract_position_2(size_t id) const { const size_t p_stride = position_stride_floats(format); diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index 3176780bf..71745acd9 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -4,6 +4,7 @@ #include "libslic3r/Point.hpp" #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Color.hpp" +#include "libslic3r/Utils.hpp" #include #include @@ -85,13 +86,19 @@ namespace GUI { ColorRGBA color{ ColorRGBA::BLACK() }; void reserve_vertices(size_t vertices_count) { vertices.reserve(vertices_count * vertex_stride_floats(format)); } + void reserve_more_vertices(size_t vertices_count) { vertices.reserve(next_highest_power_of_2(vertices.size() + vertices_count * vertex_stride_floats(format))); } void reserve_indices(size_t indices_count) { indices.reserve(indices_count); } + void reserve_more_indices(size_t indices_count) { indices.reserve(next_highest_power_of_2(indices.size() + indices_count)); } void add_vertex(const Vec2f& position); // EVertexLayout::P2 void add_vertex(const Vec2f& position, const Vec2f& tex_coord); // EVertexLayout::P2T2 void add_vertex(const Vec3f& position); // EVertexLayout::P3 void add_vertex(const Vec3f& position, const Vec2f& tex_coord); // EVertexLayout::P3T2 - void add_vertex(const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3 + void add_vertex(const Vec3f& position, const Vec3f& normal) { // EVertexLayout::P3N3 + assert(format.vertex_layout == EVertexLayout::P3N3); + vertices.insert(vertices.end(), position.data(), position.data() + 3); + vertices.insert(vertices.end(), normal.data(), normal.data() + 3); + } void set_vertex(size_t id, const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3 @@ -99,7 +106,11 @@ namespace GUI { void add_index(unsigned int id); void add_line(unsigned int id1, unsigned int id2); - void add_triangle(unsigned int id1, unsigned int id2, unsigned int id3); + void add_triangle(unsigned int id1, unsigned int id2, unsigned int id3){ + indices.emplace_back(id1); + indices.emplace_back(id2); + indices.emplace_back(id3); + } Vec2f extract_position_2(size_t id) const; Vec3f extract_position_3(size_t id) const; From b9e7cd2d7b3f4cdeec20a938b94e012029b8e9ae Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 23 Aug 2022 11:37:06 +0200 Subject: [PATCH 28/29] WIP TreeSupports: Improved speed of TreeModelVolumes by better parallelization, cleaned up the code by better structuring the collision caches with their mutexes. --- src/libslic3r/TreeModelVolumes.cpp | 796 ++++++++++++++--------------- src/libslic3r/TreeModelVolumes.hpp | 123 ++--- src/libslic3r/TreeSupport.cpp | 17 +- src/libslic3r/TreeSupport.hpp | 9 +- 4 files changed, 454 insertions(+), 491 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index c07669fe0..ccfd531af 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -26,6 +26,10 @@ namespace Slic3r { +// or warning +// had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL() +#define error_level_not_in_cache error + TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &print_object) { const PrintConfig &print_config = print_object.print()->config(); @@ -37,10 +41,11 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr assert(config.support_material); assert(config.support_material_style == smsTree); + // Calculate maximum external perimeter width over all printing regions, taking into account the default layer height. coordf_t external_perimeter_width = 0.; for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) { const PrintRegion ®ion = print_object.printing_region(region_id); - external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(print_object, frExternalPerimeter, config.layer_height).width())); + external_perimeter_width = std::max(external_perimeter_width, region.flow(print_object, frExternalPerimeter, config.layer_height).width()); } this->layer_height = scaled(config.layer_height.value); @@ -59,6 +64,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr this->support_material_buildplate_only = config.support_material_buildplate_only; // this->support_xy_overrides_z = this->support_xy_distance = scaled(config.support_material_xy_spacing.get_abs_value(external_perimeter_width)); + // Separation of interfaces, it is likely smaller than support_xy_distance. this->support_xy_distance_overhang = std::min(this->support_xy_distance, scaled(0.5 * external_perimeter_width)); this->support_top_distance = scaled(slicing_params.gap_support_object); this->support_bottom_distance = scaled(slicing_params.gap_object_support); @@ -79,6 +85,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr // this->support_offset = } +//FIXME Machine border is currently ignored. static Polygons calculateMachineBorderCollision(Polygon machine_border) { // Put a border of 1m around the print volume so that we don't collide. @@ -99,13 +106,13 @@ TreeModelVolumes::TreeModelVolumes( const BuildVolume &build_volume, const coord_t max_move, const coord_t max_move_slow, size_t current_mesh_idx, double progress_multiplier, double progress_offset, const std::vector& additional_excluded_areas) : - m_max_move{ std::max(max_move - 2, coord_t(0)) }, m_max_move_slow{ std::max(max_move_slow - 2, coord_t(0)) }, m_progress_multiplier{ progress_multiplier }, m_progress_offset{ progress_offset }, // -2 to avoid rounding errors + m_max_move{ std::max(max_move - 2, 0) }, m_max_move_slow{ std::max(max_move_slow - 2, 0) }, + m_progress_multiplier{ progress_multiplier }, m_progress_offset{ progress_offset }, m_machine_border{ calculateMachineBorderCollision(build_volume.polygon()) } { - std::unordered_map mesh_to_layeroutline_idx; - #if 0 + std::unordered_map mesh_to_layeroutline_idx; for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); ++ mesh_idx) { SliceMeshStorage mesh = storage.meshes[mesh_idx]; bool added = false; @@ -131,7 +138,6 @@ TreeModelVolumes::TreeModelVolumes( #else { m_anti_overhang = print_object.slice_support_blockers(); - mesh_to_layeroutline_idx[0] = 0; TreeSupportMeshGroupSettings mesh_settings(print_object); m_layer_outlines.emplace_back(mesh_settings, std::vector{}); m_current_outline_idx = 0; @@ -195,222 +201,188 @@ TreeModelVolumes::TreeModelVolumes( #endif } -void TreeModelVolumes::precalculate(coord_t max_layer) +void TreeModelVolumes::precalculate(const coord_t max_layer) { auto t_start = std::chrono::high_resolution_clock::now(); m_precalculated = true; - // Get the config corresponding to one mesh that is in the current group. Which one has to be irrelevant. Not the prettiest way to do this, but it ensures some calculations that may be a bit more complex like inital layer diameter are only done in once. + // Get the config corresponding to one mesh that is in the current group. Which one has to be irrelevant. + // Not the prettiest way to do this, but it ensures some calculations that may be a bit more complex + // like inital layer diameter are only done in once. TreeSupport::TreeSupportSettings config(m_layer_outlines[m_current_outline_idx].first); - // calculate which radius each layer in the tip may have. - std::unordered_set possible_tip_radiis; - for (size_t dtt = 0; dtt <= config.tip_layers; dtt++) { - possible_tip_radiis.emplace(ceilRadius(config.getRadius(dtt))); - possible_tip_radiis.emplace(ceilRadius(config.getRadius(dtt) + m_current_min_xy_dist_delta)); + { + // calculate which radius each layer in the tip may have. + std::vector possible_tip_radiis; + for (size_t distance_to_top = 0; distance_to_top <= config.tip_layers; ++ distance_to_top) { + possible_tip_radiis.emplace_back(ceilRadius(config.getRadius(distance_to_top))); + possible_tip_radiis.emplace_back(ceilRadius(config.getRadius(distance_to_top) + m_current_min_xy_dist_delta)); + } + sort_remove_duplicates(possible_tip_radiis); + // It theoretically may happen in the tip, that the radius can change so much in-between 2 layers, + // that a ceil step is skipped (as in there is a radius r so that ceilRadius(radius(dtt)) radius_until_layer; // while it is possible to calculate, up to which layer the avoidance should be calculated, this simulation is easier to understand, and does not need to be adjusted if something of the radius calculation is changed. // Overhead with an assumed worst case of 6600 layers was about 2ms - for (LayerIndex simulated_dtt = 0; simulated_dtt <= max_layer; simulated_dtt++) { - const LayerIndex current_layer = max_layer - simulated_dtt; - const coord_t max_regular_radius = ceilRadius(config.getRadius(simulated_dtt, 0) + m_current_min_xy_dist_delta); - const coord_t max_min_radius = ceilRadius(config.getRadius(simulated_dtt, 0)); // the maximal radius that the radius with the min_xy_dist can achieve - const coord_t max_initial_layer_diameter_radius = ceilRadius(config.recommendedMinRadius(current_layer) + m_current_min_xy_dist_delta); - if (!radius_until_layer.count(max_regular_radius)) - radius_until_layer[max_regular_radius] = current_layer; - if (!radius_until_layer.count(max_min_radius)) - radius_until_layer[max_min_radius] = current_layer; - if (!radius_until_layer.count(max_initial_layer_diameter_radius)) - radius_until_layer[max_initial_layer_diameter_radius] = current_layer; + for (LayerIndex distance_to_top = 0; distance_to_top <= max_layer; ++ distance_to_top) { + const LayerIndex current_layer = max_layer - distance_to_top; + auto update_radius_until_layer = [&radius_until_layer, current_layer](coord_t r) { + auto it = radius_until_layer.find(r); + if (it == radius_until_layer.end()) + radius_until_layer.emplace_hint(it, r, current_layer); + }; + // regular radius + update_radius_until_layer(ceilRadius(config.getRadius(distance_to_top, 0) + m_current_min_xy_dist_delta)); + // the maximum radius that the radius with the min_xy_dist can achieve + update_radius_until_layer(ceilRadius(config.getRadius(distance_to_top, 0))); + update_radius_until_layer(ceilRadius(config.recommendedMinRadius(current_layer) + m_current_min_xy_dist_delta)); } // Copy to deque to use in parallel for later. - std::deque relevant_avoidance_radiis; - std::deque relevant_avoidance_radiis_to_model; - relevant_avoidance_radiis.insert(relevant_avoidance_radiis.end(), radius_until_layer.begin(), radius_until_layer.end()); - relevant_avoidance_radiis_to_model.insert(relevant_avoidance_radiis_to_model.end(), radius_until_layer.begin(), radius_until_layer.end()); + std::vector relevant_avoidance_radiis{ radius_until_layer.begin(), radius_until_layer.end() }; // Append additional radiis needed for collision. - - radius_until_layer[ceilRadius(m_increase_until_radius, false)] = max_layer; // To calculate collision holefree for every radius, the collision of radius m_increase_until_radius will be required. + // To calculate collision holefree for every radius, the collision of radius m_increase_until_radius will be required. + radius_until_layer[ceilRadius(m_increase_until_radius + m_current_min_xy_dist_delta)] = max_layer; // Collision for radius 0 needs to be calculated everywhere, as it will be used to ensure valid xy_distance in drawAreas. radius_until_layer[0] = max_layer; if (m_current_min_xy_dist_delta != 0) radius_until_layer[m_current_min_xy_dist_delta] = max_layer; - std::deque relevant_collision_radiis; - relevant_collision_radiis.insert(relevant_collision_radiis.end(), radius_until_layer.begin(), radius_until_layer.end()); // Now that required_avoidance_limit contains the maximum of ild and regular required radius just copy. + // Now that required_avoidance_limit contains the maximum of ild and regular required radius just copy. + std::vector relevant_collision_radiis{ radius_until_layer.begin(), radius_until_layer.end() }; - - // ### Calculate the relevant collisions + // Calculate the relevant collisions calculateCollision(relevant_collision_radiis); // calculate a separate Collisions with all holes removed. These are relevant for some avoidances that try to avoid holes (called safe) - std::deque relevant_hole_collision_radiis; + std::vector relevant_hole_collision_radiis; for (RadiusLayerPair key : relevant_avoidance_radiis) if (key.first < m_increase_until_radius + m_current_min_xy_dist_delta) relevant_hole_collision_radiis.emplace_back(key); - // ### Calculate collisions without holes, build from regular collision + // Calculate collisions without holes, built from regular collision calculateCollisionHolefree(relevant_hole_collision_radiis); + // Let placables be calculated from calculateAvoidance() for better parallelization. + if (m_support_rests_on_model) + calculatePlaceables(relevant_avoidance_radiis); auto t_coll = std::chrono::high_resolution_clock::now(); - // ### Calculate the relevant avoidances in parallel as far as possible + // Calculate the relevant avoidances in parallel as far as possible { tbb::task_group task_group; - task_group.run([this, relevant_avoidance_radiis]{ calculateAvoidance(relevant_avoidance_radiis); }); + task_group.run([this, relevant_avoidance_radiis]{ calculateAvoidance(relevant_avoidance_radiis, true, m_support_rests_on_model); }); task_group.run([this, relevant_avoidance_radiis]{ calculateWallRestrictions(relevant_avoidance_radiis); }); - if (m_support_rests_on_model) - task_group.run([this, relevant_avoidance_radiis_to_model]{ - calculatePlaceables(relevant_avoidance_radiis_to_model); - calculateAvoidanceToModel(relevant_avoidance_radiis_to_model); - }); task_group.wait(); } auto t_end = std::chrono::high_resolution_clock::now(); auto dur_col = 0.001 * std::chrono::duration_cast(t_coll - t_start).count(); auto dur_avo = 0.001 * std::chrono::duration_cast(t_end - t_coll).count(); +// m_precalculated = true; BOOST_LOG_TRIVIAL(info) << "Precalculating collision took" << dur_col << " ms. Precalculating avoidance took " << dur_avo << " ms."; } -const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const +const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const { - coord_t orig_radius = radius; - if (!min_xy_dist) - radius += m_current_min_xy_dist_delta; - - // special case as if a radius 0 is requested it could be to ensure correct xy distance. As such it is beneficial if the collision is as close to the configured values as possible. - if (orig_radius != 0) - radius = ceilRadius(radius); - RadiusLayerPair key{ radius, layer_idx }; - std::optional> result = m_collision_cache.getArea(key); - if (result) + const coord_t radius = this->ceilRadius(orig_radius, min_xy_dist); + if (std::optional> result = m_collision_cache.getArea({ radius, layer_idx }); result) return result.value().get(); if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate collision at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; + BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Collision requested.", false); } - const_cast(this)->calculateCollision(key); + const_cast(this)->calculateCollision(radius, layer_idx); return getCollision(orig_radius, layer_idx, min_xy_dist); } -const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const +// Private. Only called internally by calculateAvoidance() and calculateAvoidanceToModel(), radius is already snapped to grid. +const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx) const { - coord_t orig_radius = radius; - if (!min_xy_dist) - radius += m_current_min_xy_dist_delta; - if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta) - return getCollision(orig_radius, layer_idx, min_xy_dist); - - // not needed as the radius should already be adjusted. - // radius = ceilRadius(radius); - RadiusLayerPair key{ radius, layer_idx }; - std::optional> result = m_collision_cache_holefree.getArea(key); - if (result) + assert(radius == this->ceilRadius(radius)); + assert(radius < m_increase_until_radius + m_current_min_xy_dist_delta); + if (std::optional> result = m_collision_cache_holefree.getArea({ radius, layer_idx }); result) return result.value().get(); if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate collision holefree at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; + BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision holefree at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Holefree Collision requested.", false); } - const_cast(this)->calculateCollisionHolefree(key); - return getCollisionHolefree(orig_radius, layer_idx, min_xy_dist); + const_cast(this)->calculateCollisionHolefree({ radius, layer_idx }); + return getCollisionHolefree(radius, layer_idx); } -const Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) const +const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) const { if (layer_idx == 0) // What on the layer directly above buildplate do i have to avoid to reach the buildplate ... - return getCollision(radius, layer_idx, min_xy_dist); + return getCollision(orig_radius, layer_idx, min_xy_dist); - coord_t orig_radius = radius; - - if (!min_xy_dist) - radius += m_current_min_xy_dist_delta; - radius = ceilRadius(radius); - - if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta && type == AvoidanceType::FastSafe) // no holes anymore by definition at this request + const coord_t radius = this->ceilRadius(orig_radius, min_xy_dist); + if (type == AvoidanceType::FastSafe && radius >= m_increase_until_radius + m_current_min_xy_dist_delta) + // no holes anymore by definition at this request type = AvoidanceType::Fast; - const RadiusLayerPair key{ radius, layer_idx }; - - const RadiusLayerPolygonCache *cache_ptr = nullptr; - if (!to_model && type == AvoidanceType::Fast) { - cache_ptr = &m_avoidance_cache; - } else if (!to_model && type == AvoidanceType::Slow) { - cache_ptr = &m_avoidance_cache_slow; - } else if (!to_model && type == AvoidanceType::FastSafe) { - cache_ptr = &m_avoidance_cache_holefree; - } else if (to_model && type == AvoidanceType::Fast) { - cache_ptr = &m_avoidance_cache_to_model; - } else if (to_model && type == AvoidanceType::Slow) { - cache_ptr = &m_avoidance_cache_to_model_slow; - } else if (to_model && type == AvoidanceType::FastSafe) { - cache_ptr = &m_avoidance_cache_holefree_to_model; - } else { - BOOST_LOG_TRIVIAL(error) << "Invalid Avoidance Request"; - TreeSupport::showError("Invalid Avoidance Request.\n", true); - } - - std::optional> result = cache_ptr->getArea(key); - if (result) + if (std::optional> result = + this->avoidance_cache(type, to_model).getArea({ radius, layer_idx }); + result) return result.value().get(); - if (to_model) { - if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate Avoidance to model at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; + if (m_precalculated) { + if (to_model) { + BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance to model at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Avoidance(to model) requested.", false); - } - const_cast(this)->calculateAvoidanceToModel(key); - } else { - if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate Avoidance at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; + } else { + BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Avoidance(to buildplate) requested.", false); } - const_cast(this)->calculateAvoidance(key); } - return getAvoidance(orig_radius, layer_idx, type, to_model, min_xy_dist); // retrive failed and correct result was calculated. Now it has to be retrived. + const_cast(this)->calculateAvoidance({ radius, layer_idx }, ! to_model, to_model); + // Retrive failed and correct result was calculated. Now it has to be retrived. + return getAvoidance(orig_radius, layer_idx, type, to_model, min_xy_dist); } -const Polygons& TreeModelVolumes::getPlaceableAreas(coord_t orig_radius, LayerIndex layer_idx) const +const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, LayerIndex layer_idx) const { + if (orig_radius == 0) + return this->getCollision(0, layer_idx, true); + const coord_t radius = ceilRadius(orig_radius); - std::optional> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); - if (result) + if (std::optional> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); result) return result.value().get(); if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; + BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError("Not precalculated Placeable areas requested.", false); } - if (orig_radius > 0) - const_cast(this)->calculatePlaceables({ radius, layer_idx }); - else - getCollision(0, layer_idx, true); + const_cast(this)->calculatePlaceables(radius, layer_idx); return getPlaceableAreas(orig_radius, layer_idx); } -const Polygons& TreeModelVolumes::getWallRestriction(coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const +const Polygons& TreeModelVolumes::getWallRestriction(const coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const { - if (layer_idx == 0) // Should never be requested as there will be no going below layer 0 ..., but just to be sure some semi-sane catch. Alternative would be empty Polygon. + assert(layer_idx > 0); + if (layer_idx == 0) + // Should never be requested as there will be no going below layer 0 ..., + // but just to be sure some semi-sane catch. Alternative would be empty Polygon. return getCollision(orig_radius, layer_idx, min_xy_dist); - min_xy_dist = min_xy_dist && m_current_min_xy_dist_delta > 0; + min_xy_dist &= m_current_min_xy_dist_delta > 0; - coord_t radius = ceilRadius(orig_radius); - std::optional> result = + const coord_t radius = ceilRadius(orig_radius); + if (std::optional> result = (min_xy_dist ? m_wall_restrictions_cache_min : m_wall_restrictions_cache).getArea({ radius, layer_idx }); - if (result) + result) return result.value().get(); if (m_precalculated) { - BOOST_LOG_TRIVIAL(warning) << "Had to calculate Wall restricions at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; + BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Wall restricions at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; TreeSupport::showError( min_xy_dist ? "Not precalculated Wall restriction of minimum xy distance requested )." : @@ -421,289 +393,276 @@ const Polygons& TreeModelVolumes::getWallRestriction(coord_t orig_radius, LayerI return getWallRestriction(orig_radius, layer_idx, min_xy_dist); // Retrieve failed and correct result was calculated. Now it has to be retrieved. } -void TreeModelVolumes::calculateCollision(std::deque keys) +void TreeModelVolumes::calculateCollision(const std::vector &keys) { tbb::parallel_for(tbb::blocked_range(0, keys.size()), [&](const tbb::blocked_range &range) { - for (size_t i = range.begin(); i != range.end(); ++ i) { - const coord_t radius = keys[i].first; - const size_t layer_idx = keys[i].second; - RadiusLayerPair key(radius, 0); - RadiusLayerPolygonCacheData data_outer; - RadiusLayerPolygonCacheData data_placeable_outer; - for (size_t outline_idx = 0; outline_idx < m_layer_outlines.size(); ++outline_idx) { - RadiusLayerPolygonCacheData data; - RadiusLayerPolygonCacheData data_placeable; - - const coord_t layer_height = m_layer_outlines[outline_idx].first.layer_height; - const bool support_rests_on_this_model = ! m_layer_outlines[outline_idx].first.support_material_buildplate_only; - const coord_t z_distance_bottom = m_layer_outlines[outline_idx].first.support_bottom_distance; - const size_t z_distance_bottom_layers = round_up_divide(z_distance_bottom, layer_height); - const coord_t z_distance_top_layers = round_up_divide(m_layer_outlines[outline_idx].first.support_top_distance, layer_height); - const LayerIndex max_required_layer = layer_idx + std::max(coord_t(1), z_distance_top_layers); - const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : m_layer_outlines[outline_idx].first.support_xy_distance; - // technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all not currently processing meshes as this delta will be added at request time. - // avoiding this would require saving each collision for each outline_idx separately. - // and later for each avoidance... But avoidance calculation has to be for the whole scene and can NOT be done for each outline_idx separately and combined later. - // so avoiding this inaccuracy seems infeasible as it would require 2x the avoidance calculations => 0.5x the performance. - coord_t min_layer_bottom = m_collision_cache.getMaxCalculatedLayer(radius) - int(z_distance_bottom_layers); - if (min_layer_bottom < 0) - min_layer_bottom = 0; - //FIXME parallel_for - for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; layer_idx++) { - key.second = layer_idx; - Polygons collision_areas = m_machine_border; - if (size_t(layer_idx) < m_layer_outlines[outline_idx].second.size()) - append(collision_areas, m_layer_outlines[outline_idx].second[layer_idx]); - // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. - // if a key does not exist when it is accessed it is added! - append(data[key], offset(union_ex(collision_areas), radius + xy_distance, ClipperLib::jtMiter, 1.2)); - } - - // Add layers below, to ensure correct support_bottom_distance. Also save placeable areas of radius 0, if required for this mesh. - for (int layer_idx = int(max_required_layer); layer_idx >= min_layer_bottom; -- layer_idx) { - key.second = layer_idx; - for (size_t layer_offset = 1; layer_offset <= z_distance_bottom_layers && layer_idx - coord_t(layer_offset) > min_layer_bottom; ++ layer_offset) - append(data[key], data[RadiusLayerPair(radius, layer_idx - layer_offset)]); - if (support_rests_on_this_model && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) { - RadiusLayerPair key_next_layer(radius, layer_idx + 1); - //data[key] = union_(data[key]); - Polygons above = data[key_next_layer]; - // just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly. - //FIXME Vojtech: Why m_anti_overhang.size() > layer_idx + 1? Why +1? - above = m_anti_overhang.size() > layer_idx + 1 ? union_(above, m_anti_overhang[layer_idx]) : union_(above); - data_placeable[key_next_layer] = union_(data_placeable[key_next_layer], diff(data[key], above)); - } - } - - // Add collision layers above to ensure correct support_top_distance. - for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; ++ layer_idx) { - key.second = layer_idx; - Polygons collisions = std::move(data[key]); - for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + layer_idx <= max_required_layer; ++ layer_offset) - append(collisions, data[RadiusLayerPair(radius, layer_idx + layer_offset)]); - data[key] = m_anti_overhang.size() > layer_idx ? union_(collisions, offset(union_ex(m_anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(collisions); - } - - for (int layer_idx = int(max_required_layer); layer_idx > keys[i].second; -- layer_idx) { - // all these dont have the correct z_distance_top_layers as they can still have areas above them - auto it = data.find(RadiusLayerPair(radius, layer_idx)); - if (it != data.end()) - data.erase(it); - } - - for (auto pair : data) - data_outer[pair.first] = union_(data_outer[pair.first], polygons_simplify(pair.second, m_min_resolution)); - if (radius == 0) { - for (auto pair : data_placeable) - data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], polygons_simplify(pair.second, m_min_resolution)); - } - } - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - { - std::lock_guard critical_section(*m_critical_progress); - if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL) { - m_precalculation_progress += TREE_PROGRESS_PRECALC_COLL / keys.size(); - Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - } - } -#endif - - m_collision_cache.insert(std::move(data_outer)); - if (radius == 0) - m_placeable_areas_cache.insert(std::move(data_placeable_outer)); + for (size_t ikey = range.begin(); ikey != range.end(); ++ ikey) { + const LayerIndex radius = keys[ikey].first; + const size_t max_layer_idx = keys[ikey].second; + // recursive call to parallel_for. + calculateCollision(radius, max_layer_idx); } }); } -void TreeModelVolumes::calculateCollisionHolefree(std::deque keys) +void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex max_layer_idx) +{ + assert(radius == this->ceilRadius(radius)); + + // Process the outlines from least layers to most layers so that the final union will run over the longest vector. + std::vector layer_outline_indices(m_layer_outlines.size(), 0); + std::iota(layer_outline_indices.begin(), layer_outline_indices.end(), 0); + std::sort(layer_outline_indices.begin(), layer_outline_indices.end(), + [this](size_t i, size_t j) { return m_layer_outlines[i].second.size() < m_layer_outlines[j].second.size(); }); + + const LayerIndex min_layer_last = m_collision_cache.getMaxCalculatedLayer(radius); + std::vector data(max_layer_idx + 1 - min_layer_last, Polygons{}); + const bool calculate_placable = m_support_rests_on_model && radius == 0; + std::vector data_placeable; + if (calculate_placable) + data_placeable = std::vector(max_layer_idx + 1 - min_layer_last, Polygons{}); + + for (size_t outline_idx : layer_outline_indices) + if (const std::vector &outlines = m_layer_outlines[outline_idx].second; ! outlines.empty()) { + const TreeSupportMeshGroupSettings &settings = m_layer_outlines[outline_idx].first; + const coord_t layer_height = settings.layer_height; + const bool support_rests_on_model = ! settings.support_material_buildplate_only; + const coord_t z_distance_bottom = settings.support_bottom_distance; + const int z_distance_bottom_layers = round_up_divide(z_distance_bottom, layer_height); + const int z_distance_top_layers = round_up_divide(settings.support_top_distance, layer_height); + const LayerIndex max_required_layer = std::min(outlines.size(), max_layer_idx + std::max(coord_t(1), z_distance_top_layers)); + const LayerIndex min_layer_bottom = std::max(0, min_layer_last - int(z_distance_bottom_layers)); + // technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all + // not currently processing meshes as this delta will be added at request time. + // avoiding this would require saving each collision for each outline_idx separately. + // and later for each avoidance... But avoidance calculation has to be for the whole scene and can NOT be done for each outline_idx separately and combined later. + // so avoiding this inaccuracy seems infeasible as it would require 2x the avoidance calculations => 0.5x the performance. + const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : settings.support_xy_distance; + + // 1) Calculate offsets of collision areas in parallel. + std::vector collision_areas_offsetted(max_required_layer + 1 - min_layer_bottom); + tbb::parallel_for(tbb::blocked_range(min_layer_bottom, max_required_layer + 1), + [&outlines, &machine_border = m_machine_border, offset_value = radius + xy_distance, min_layer_bottom, &collision_areas_offsetted] + (const tbb::blocked_range &range) { + for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) { + Polygons collision_areas = machine_border; + append(collision_areas, outlines[layer_idx]); + // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. + // if a key does not exist when it is accessed it is added! + collision_areas_offsetted[layer_idx - min_layer_bottom] = offset_value == 0 ? union_(collision_areas) : offset(union_ex(collision_areas), offset_value, ClipperLib::jtMiter, 1.2); + } + }); + + // 2) Sum over top / bottom ranges. + const bool last = outline_idx == layer_outline_indices.size(); + tbb::parallel_for(tbb::blocked_range(min_layer_last + 1, max_layer_idx + 1), + [&collision_areas_offsetted, &anti_overhang = m_anti_overhang, min_layer_bottom, radius, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, min_layer_last, last] + (const tbb::blocked_range& range) { + for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++layer_idx) { + Polygons collisions; + for (int i = -z_distance_bottom_layers; i <= z_distance_top_layers; ++ i) { + int j = layer_idx + i - min_layer_bottom; + if (j >= 0 && j < collision_areas_offsetted.size()) + append(collisions, collision_areas_offsetted[j]); + } + collisions = last && layer_idx < anti_overhang.size() ? union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(collisions); + auto &dst = data[layer_idx - (min_layer_last + 1)]; + if (last) { + if (! dst.empty()) + collisions = union_(collisions, dst); + dst = polygons_simplify(collisions, min_resolution); + } else + append(dst, collisions); + } + }); + + // 3) Optionally calculate placables. + if (calculate_placable) { + // Calculating both the collision areas and placable areas. + tbb::parallel_for(tbb::blocked_range(std::max(min_layer_last + 1, z_distance_bottom_layers + 1), max_layer_idx + 1), + [&collision_areas_offsetted, &anti_overhang = m_anti_overhang, min_layer_bottom, radius, z_distance_bottom_layers, z_distance_top_layers, last, min_resolution = m_min_resolution, &data_placeable, min_layer_last] + (const tbb::blocked_range& range) { + for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) { + LayerIndex layer_idx_below = layer_idx - (z_distance_bottom_layers + 1) - min_layer_bottom; + assert(layer_idx_below >= 0); + auto ¤t = collision_areas_offsetted[layer_idx - min_layer_bottom]; + auto &below = collision_areas_offsetted[layer_idx_below]; + auto placable = diff(below, layer_idx < anti_overhang.size() ? union_(current, anti_overhang[layer_idx - (z_distance_bottom_layers + 1)]) : current); + auto &dst = data_placeable[layer_idx - (min_layer_last + 1)]; + if (last) { + if (! dst.empty()) + placable = union_(placable, dst); + dst = polygons_simplify(placable, min_resolution); + } else + append(dst, placable); + } + }); + } else { + // Calculating just the collision areas. + } + } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + { + std::lock_guard critical_section(*m_critical_progress); + if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL) { + m_precalculation_progress += TREE_PROGRESS_PRECALC_COLL / keys.size(); + Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); + } + } +#endif + m_collision_cache.insert(std::move(data), min_layer_last + 1, radius); + if (calculate_placable) + m_placeable_areas_cache.insert(std::move(data_placeable), min_layer_last + 1, radius); +} + +void TreeModelVolumes::calculateCollisionHolefree(const std::vector &keys) { LayerIndex max_layer = 0; for (long long unsigned int i = 0; i < keys.size(); i++) max_layer = std::max(max_layer, keys[i].second); - tbb::parallel_for(tbb::blocked_range(0, max_layer + 1), + tbb::parallel_for(tbb::blocked_range(0, max_layer + 1, keys.size()), [&](const tbb::blocked_range &range) { + RadiusLayerPolygonCacheData data; for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - RadiusLayerPolygonCacheData data; - for (RadiusLayerPair key : keys) { - // Logically increase the collision by m_increase_until_radius - coord_t radius = key.first; - coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - ceilRadius(radius, true); - // this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area. - Polygons col = polygons_simplify(offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution), m_min_resolution); - data[RadiusLayerPair(radius, layer_idx)] = col; - } - - m_collision_cache_holefree.insert(std::move(data)); + for (RadiusLayerPair key : keys) + if (layer_idx <= key.second) { + // Logically increase the collision by m_increase_until_radius + coord_t radius = key.first; + assert(radius == this->ceilRadius(radius)); + assert(radius < m_increase_until_radius + m_current_min_xy_dist_delta); + coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - radius; + assert(increase_radius_ceil > 0); + // this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area. + data[RadiusLayerPair(radius, layer_idx)] = polygons_simplify( + offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), + 5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution), + m_min_resolution); + } } + m_collision_cache_holefree.insert(std::move(data)); }); } -void TreeModelVolumes::calculateAvoidance(std::deque keys) +void TreeModelVolumes::calculateAvoidance(const std::vector &keys, bool to_build_plate, bool to_model) { - // For every RadiusLayer pair there are 3 avoidances that have to be calculate, calculated in the same paralell_for loop for better paralellisation. - const std::vector all_types = { AvoidanceType::Slow, AvoidanceType::FastSafe, AvoidanceType::Fast }; - tbb::parallel_for(tbb::blocked_range(0, keys.size() * 3), - [&, keys, all_types](const tbb::blocked_range &range) { - for (size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { - size_t key_idx = iter_idx / 3; - { - size_t type_idx = iter_idx % all_types.size(); - AvoidanceType type = all_types[type_idx]; - const bool slow = type == AvoidanceType::Slow; - const bool holefree = type == AvoidanceType::FastSafe; + // For every RadiusLayer pair there are 3 avoidances that have to be calculated. + // Prepare tasks for parallelization. + struct AvoidanceTask { + AvoidanceType type; + coord_t radius; + LayerIndex max_required_layer; + bool to_model; + LayerIndex start_layer; - coord_t radius = keys[key_idx].first; - LayerIndex max_required_layer = keys[key_idx].second; + bool slow() const { return this->type == AvoidanceType::Slow; } + bool holefree() const { return this->type == AvoidanceType::FastSafe; } + }; - // do not calculate not needed safe avoidances - if (holefree && radius >= m_increase_until_radius + m_current_min_xy_dist_delta) - continue; + std::vector avoidance_tasks; + avoidance_tasks.reserve((int(to_build_plate) + int(to_model)) * keys.size() * size_t(AvoidanceType::Count)); - const coord_t offset_speed = slow ? m_max_move_slow : m_max_move; - RadiusLayerPair key(radius, 0); - LayerIndex start_layer = 1 + (slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_holefree : m_avoidance_cache).getMaxCalculatedLayer(radius); - if (start_layer > max_required_layer) { - BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; - continue; - } - std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); + for (int iter_idx = 0; iter_idx < 2 * keys.size() * size_t(AvoidanceType::Count); ++ iter_idx) { + AvoidanceTask task{ + AvoidanceType(iter_idx % int(AvoidanceType::Count)), + keys[iter_idx / 6].first, // radius + keys[iter_idx / 6].second, // max_layer + ((iter_idx / 3) & 1) != 0 // to_model + }; + // Ensure start_layer is at least 1 as if no avoidance was calculated yet getMaxCalculatedLayer() returns -1. + task.start_layer = std::max(1, 1 + avoidance_cache(task.type, task.to_model).getMaxCalculatedLayer(task.radius)); + if (task.start_layer > task.max_required_layer) { + BOOST_LOG_TRIVIAL(debug) << "Calculation requested for value already calculated?"; + continue; + } + if (! task.holefree() || task.radius < m_increase_until_radius + m_current_min_xy_dist_delta) + avoidance_tasks.emplace_back(task); + } - // Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1 - start_layer = std::max(start_layer, LayerIndex(1)); - // minDist as the delta was already added, also avoidance for layer 0 will return the collision. - Polygons latest_avoidance = getAvoidance(radius, start_layer - 1, type, false, true); - // ### main loop doing the calculation - for (LayerIndex layer = start_layer; layer <= max_required_layer; ++ layer) { - key.second = layer; - const Polygons &col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ? - getCollisionHolefree(radius, layer, true) : - getCollision(radius, layer, true); - latest_avoidance = polygons_simplify(union_(offset(union_ex(latest_avoidance), -offset_speed, ClipperLib::jtRound, m_min_resolution), col), m_min_resolution); - data[layer] = std::pair(key, latest_avoidance); - } - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - { - std::lock_guard critical_section(*m_critical_progress); - if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { - m_precalculation_progress += m_support_rests_on_model ? 0.4 : 1 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3); - Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - } - } -#endif - - (slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_holefree : m_avoidance_cache).insert(std::move(data)); + tbb::parallel_for(tbb::blocked_range(0, avoidance_tasks.size(), 1), + [this, &avoidance_tasks](const tbb::blocked_range &range) { + for (size_t task_idx = range.begin(); task_idx < range.end(); ++ task_idx) { + const AvoidanceTask &task = avoidance_tasks[task_idx]; + assert(! task.holefree() || task.radius < m_increase_until_radius + m_current_min_xy_dist_delta); + if (task.to_model) + // ensuring Placeableareas are calculated + getPlaceableAreas(task.radius, task.max_required_layer); + // The following loop propagating avoidance regions bottom up is inherently serial. + const bool collision_holefree = (task.slow() || task.holefree()) && task.radius < m_increase_until_radius + m_current_min_xy_dist_delta; + const float max_move = task.slow() ? m_max_move_slow : m_max_move; + // minDist as the delta was already added, also avoidance for layer 0 will return the collision. + Polygons latest_avoidance = getAvoidance(task.radius, task.start_layer - 1, task.type, task.to_model, true); + std::vector> data; + data.reserve(task.max_required_layer + 1 - task.start_layer); + for (LayerIndex layer_idx = task.start_layer; layer_idx <= task.max_required_layer; ++ layer_idx) { + latest_avoidance = union_( + // Propagate avoidance region from the layers below, adjust for allowed tilt of the tree branch. + offset(union_ex(latest_avoidance), -max_move, ClipperLib::jtRound, m_min_resolution), + // Add current layer collisions. + collision_holefree ? getCollisionHolefree(task.radius, layer_idx) : getCollision(task.radius, layer_idx, true)); + if (task.to_model) + latest_avoidance = diff(latest_avoidance, getPlaceableAreas(task.radius, layer_idx)); + latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution); + data.emplace_back(RadiusLayerPair{task.radius, layer_idx}, latest_avoidance); } +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + { + std::lock_guard critical_section(*m_critical_progress); + if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { + m_precalculation_progress += to_model ? + 0.4 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3) : + m_support_rests_on_model ? 0.4 : 1 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3); + Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); + } + } +#endif + avoidance_cache(task.type, task.to_model).insert(std::move(data)); } }); } -void TreeModelVolumes::calculatePlaceables(std::deque keys) + +void TreeModelVolumes::calculatePlaceables(const std::vector &keys) { tbb::parallel_for(tbb::blocked_range(0, keys.size()), - [&, keys](const tbb::blocked_range &range) { - for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { - const coord_t radius = keys[key_idx].first; - const LayerIndex max_required_layer = keys[key_idx].second; - std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); - RadiusLayerPair key(radius, 0); - - LayerIndex start_layer = 1 + m_placeable_areas_cache.getMaxCalculatedLayer(radius); - if (start_layer > max_required_layer) { - BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; - continue; - } - - if (start_layer == 0) { - data[0] = std::pair(key, diff(m_machine_border, getCollision(radius, 0, true))); - start_layer = 1; - } - - for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) { - key.second = layer; - Polygons placeable = getPlaceableAreas(0, layer); - placeable = polygons_simplify(placeable, m_min_resolution); // it is faster to do this here in each thread than once in calculateCollision. - placeable = offset(union_ex(placeable), - radius, jtMiter, 1.2); - data[layer] = std::pair(key, placeable); - } - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - { - std::lock_guard critical_section(*m_critical_progress); - if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { - m_precalculation_progress += 0.2 * TREE_PROGRESS_PRECALC_AVO / (keys.size()); - Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - } - } -#endif - - m_placeable_areas_cache.insert(std::move(data)); - } - }); + [&, keys](const tbb::blocked_range& range) { + for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) + this->calculatePlaceables(keys[key_idx].first, keys[key_idx].second); + }); } -void TreeModelVolumes::calculateAvoidanceToModel(std::deque keys) +void TreeModelVolumes::calculatePlaceables(const coord_t radius, const LayerIndex max_required_layer) { - // For every RadiusLayer pair there are 3 avoidances that have to be calculated, calculated in the same parallel_for loop for better parallelization. - const std::vector all_types = { AvoidanceType::Slow, AvoidanceType::FastSafe, AvoidanceType::Fast }; - tbb::parallel_for(tbb::blocked_range(0, keys.size() * 3), - [&, keys, all_types](const tbb::blocked_range &range) { - for (size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { - size_t key_idx = iter_idx / 3; - size_t type_idx = iter_idx % all_types.size(); - AvoidanceType type = all_types[type_idx]; - bool slow = type == AvoidanceType::Slow; - bool holefree = type == AvoidanceType::FastSafe; - coord_t radius = keys[key_idx].first; - LayerIndex max_required_layer = keys[key_idx].second; + LayerIndex start_layer = 1 + m_placeable_areas_cache.getMaxCalculatedLayer(radius); + if (start_layer > max_required_layer) { + BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; + return; + } - // do not calculate not needed safe avoidances - if (holefree && radius >= m_increase_until_radius + m_current_min_xy_dist_delta) - continue; + std::vector data(max_required_layer + 1 - start_layer, Polygons{}); - getPlaceableAreas(radius, max_required_layer); // ensuring Placeableareas are calculated - const coord_t offset_speed = slow ? m_max_move_slow : m_max_move; - std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); - RadiusLayerPair key(radius, 0); - - LayerIndex start_layer = 1 + (slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_holefree_to_model : m_avoidance_cache_to_model).getMaxCalculatedLayer(radius); - if (start_layer > max_required_layer) { - BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; - continue; - } - // Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1 - start_layer = std::max(start_layer, LayerIndex(1)); - // minDist as the delta was already added, also avoidance for layer 0 will return the collision. - Polygons latest_avoidance = getAvoidance(radius, start_layer - 1, type, true, true); - // ### main loop doing the calculation - for (LayerIndex layer = start_layer; layer <= max_required_layer; ++ layer) { - key.second = layer; - const Polygons &col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ? - getCollisionHolefree(radius, layer, true) : - getCollision(radius, layer, true); - latest_avoidance = polygons_simplify(diff(union_(offset(union_ex(latest_avoidance), -offset_speed, ClipperLib::jtRound, m_min_resolution), col), getPlaceableAreas(radius, layer)), m_min_resolution); - data[layer] = std::pair(key, latest_avoidance); - } + if (start_layer == 0) + data[0] = diff(m_machine_border, getCollision(radius, 0, true)); + tbb::parallel_for(tbb::blocked_range(std::max(1, start_layer), max_required_layer + 1), + [this, &data, radius, start_layer](const tbb::blocked_range& range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) + data[layer_idx - start_layer] = offset(union_ex(getPlaceableAreas(0, layer_idx)), - radius, jtMiter, 1.2); + }); #ifdef SLIC3R_TREESUPPORTS_PROGRESS - { - std::lock_guard critical_section(*m_critical_progress); - if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { - m_precalculation_progress += 0.4 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3); - Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - } - } -#endif - - (slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_holefree_to_model : m_avoidance_cache_to_model).insert(std::move(data)); + { + std::lock_guard critical_section(*m_critical_progress); + if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { + m_precalculation_progress += 0.2 * TREE_PROGRESS_PRECALC_AVO / (keys.size()); + Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); } - }); + } +#endif + m_placeable_areas_cache.insert(std::move(data), start_layer, radius); } -void TreeModelVolumes::calculateWallRestrictions(std::deque keys) +void TreeModelVolumes::calculateWallRestrictions(const std::vector &keys) { // Wall restrictions are mainly important when they represent actual walls that are printed, and not "just" the configured z_distance, because technically valid placement is no excuse for moving through a wall. // As they exist to prevent accidentially moving though a wall at high speed between layers like thie (x = wall,i = influence area,o= empty space,d = blocked area because of z distance) Assume maximum movement distance is two characters and maximum safe movement distance of one character @@ -742,65 +701,62 @@ void TreeModelVolumes::calculateWallRestrictions(std::deque key tbb::parallel_for(tbb::blocked_range(0, keys.size()), [&, keys](const tbb::blocked_range &range) { for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { - coord_t radius = keys[key_idx].first; - RadiusLayerPair key(radius, 0); - RadiusLayerPolygonCacheData data; - RadiusLayerPolygonCacheData data_min; - coord_t min_layer_bottom = m_wall_restrictions_cache.getMaxCalculatedLayer(radius); - if (min_layer_bottom < 1) - min_layer_bottom = 1; - - for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= keys[key_idx].second; layer_idx++) { - key.second = layer_idx; - LayerIndex layer_idx_below = layer_idx - 1; - Polygons wall_restriction = intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx_below, true)); // radius contains m_current_min_xy_dist_delta already if required - wall_restriction = polygons_simplify(wall_restriction, m_min_resolution); - data.emplace(key, wall_restriction); - if (m_current_min_xy_dist_delta > 0) { - Polygons wall_restriction_min = intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx_below, true)); - wall_restriction = polygons_simplify(wall_restriction_min, m_min_resolution); - data_min.emplace(key, wall_restriction_min); + const coord_t radius = keys[key_idx].first; + const LayerIndex max_required_layer = keys[key_idx].second; + const coord_t min_layer_bottom = std::max(1, m_wall_restrictions_cache.getMaxCalculatedLayer(radius)); + const size_t buffer_size = max_required_layer + 1 - min_layer_bottom; + std::vector data(buffer_size, Polygons{}); + std::vector data_min; + if (m_current_min_xy_dist_delta > 0) + data_min.assign(buffer_size, Polygons{}); + tbb::parallel_for(tbb::blocked_range(min_layer_bottom, max_required_layer + 1), + [this, &data, &data_min, radius, min_layer_bottom](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + data[layer_idx - min_layer_bottom] = polygons_simplify( + // radius contains m_current_min_xy_dist_delta already if required + intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx - 1, true)), + m_min_resolution); + if (! data_min.empty()) + data_min[layer_idx - min_layer_bottom] = + polygons_simplify( + intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx - 1, true)), + m_min_resolution); } - } - m_wall_restrictions_cache.insert(std::move(data)); - m_wall_restrictions_cache_min.insert(std::move(data_min)); + }); + m_wall_restrictions_cache.insert(std::move(data), min_layer_bottom, radius); + if (! data_min.empty()) + m_wall_restrictions_cache_min.insert(std::move(data_min), min_layer_bottom, radius); } }); } -coord_t TreeModelVolumes::ceilRadius(coord_t radius) const +coord_t TreeModelVolumes::ceilRadius(const coord_t radius) const { if (radius == 0) return 0; - if (radius <= m_radius_0) - return m_radius_0; - - if (SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION) - { + coord_t out = m_radius_0; + if (radius > m_radius_0) { // generate SUPPORT_TREE_PRE_EXPONENTIAL_STEPS of radiis before starting to exponentially increase them. - - coord_t exponential_result = SUPPORT_TREE_EXPONENTIAL_THRESHOLD * SUPPORT_TREE_EXPONENTIAL_FACTOR; - const coord_t stepsize = (exponential_result - m_radius_0) / (SUPPORT_TREE_PRE_EXPONENTIAL_STEPS + 1); - coord_t result = m_radius_0; - for (size_t step = 0; step < SUPPORT_TREE_PRE_EXPONENTIAL_STEPS; step++) { - result += stepsize; - if (result >= radius && !m_ignorable_radii.count(result)) - return result; + coord_t initial_radius_delta = SUPPORT_TREE_EXPONENTIAL_THRESHOLD - m_radius_0; + auto ignore = [this](coord_t r) { return std::binary_search(m_ignorable_radii.begin(), m_ignorable_radii.end(), r); }; + if (initial_radius_delta > SUPPORT_TREE_COLLISION_RESOLUTION) { + const int num_steps = round_up_divide(initial_radius_delta, SUPPORT_TREE_EXPONENTIAL_THRESHOLD); + const int stepsize = initial_radius_delta / num_steps; + out += stepsize; + for (auto step = 0; step < num_steps; ++ step) { + if (out >= radius && ! ignore(out)) + return out; + out += stepsize; + } + } else + out += SUPPORT_TREE_COLLISION_RESOLUTION; + while (out < radius || ignore(out)) { + assert(out * SUPPORT_TREE_EXPONENTIAL_FACTOR > out + SUPPORT_TREE_COLLISION_RESOLUTION); + out = out * SUPPORT_TREE_EXPONENTIAL_FACTOR; } - - while (exponential_result < radius || m_ignorable_radii.count(exponential_result)) - exponential_result = std::max(coord_t(exponential_result * SUPPORT_TREE_EXPONENTIAL_FACTOR), exponential_result + SUPPORT_TREE_COLLISION_RESOLUTION); - return exponential_result; - } - else - { // generates equidistant steps of size SUPPORT_TREE_COLLISION_RESOLUTION starting from m_radius_0. If SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION then this code is dead, and can safely be removed. - coord_t ceil_step_n = (radius - m_radius_0) / SUPPORT_TREE_COLLISION_RESOLUTION; - coord_t resulting_ceil = m_radius_0 + (ceil_step_n + ((radius - m_radius_0) % SUPPORT_TREE_COLLISION_RESOLUTION != 0)) * SUPPORT_TREE_COLLISION_RESOLUTION; - return - radius <= m_radius_0 && radius != 0 ? m_radius_0 : - m_ignorable_radii.count(resulting_ceil) ? ceilRadius(resulting_ceil + 1) : resulting_ceil; } + return out; } } diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 5f4b66772..18765be58 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -211,7 +211,8 @@ public: { Slow, FastSafe, - Fast + Fast, + Count }; /*! @@ -220,7 +221,7 @@ public: * Knowledge about branch angle is used to only calculate avoidances and collisions that may actually be needed. * Not calling precalculate() will cause the class to lazily calculate avoidances and collisions as needed, which will be a lot slower on systems with more then one or two cores! */ - void precalculate(coord_t max_layer); + void precalculate(const coord_t max_layer); /*! * \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. @@ -233,7 +234,7 @@ public: * \param min_xy_dist Is the minimum xy distance used. * \return Polygons object */ - const Polygons& getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const; + const Polygons& getCollision(const coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const; /*! * \brief Provides the areas that have to be avoided by the tree's branches @@ -277,8 +278,12 @@ public: * \param min_xy_dist is the minimum xy distance used. * \return The rounded radius */ - coord_t ceilRadius(coord_t radius, bool min_xy_dist) const { - return this->ceilRadius(min_xy_dist ? radius : radius + m_current_min_xy_dist_delta); + coord_t ceilRadius(const coord_t radius, const bool min_xy_dist) const { + assert(radius >= 0); + return min_xy_dist ? + this->ceilRadius(radius) : + // special case as if a radius 0 is requested it could be to ensure correct xy distance. As such it is beneficial if the collision is as close to the configured values as possible. + radius > 0 ? this->ceilRadius(radius + m_current_min_xy_dist_delta) : m_current_min_xy_dist_delta; } /*! * \brief Round \p radius upwards to the maximum that would still round up to the same value as the provided one. @@ -288,6 +293,7 @@ public: * \return The maximum radius, resulting in the same rounding. */ coord_t getRadiusNextCeil(coord_t radius, bool min_xy_dist) const { + assert(radius > 0); return min_xy_dist ? this->ceilRadius(radius) : this->ceilRadius(radius + m_current_min_xy_dist_delta) - m_current_min_xy_dist_delta; @@ -313,11 +319,22 @@ private: for (auto& d : in) this->data.emplace(d.first, std::move(d.second)); } - void insert(std::vector> && in) { + void insert(std::vector> &&in) { std::lock_guard guard(this->mutex); for (auto& d : in) this->data.emplace(d.first, std::move(d.second)); } + // by layer + void insert(std::vector> &&in, coord_t radius) { + std::lock_guard guard(this->mutex); + for (auto &d : in) + this->data.emplace(RadiusLayerPair{ radius, d.first }, std::move(d.second)); + } + void insert(std::vector &&in, coord_t first_layer_idx, coord_t radius) { + std::lock_guard guard(this->mutex); + for (auto &d : in) + this->data.emplace(RadiusLayerPair{ radius, first_layer_idx ++ }, std::move(d)); + } /*! * \brief Checks a cache for a given RadiusLayerPair and returns it if it is found * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. @@ -359,19 +376,20 @@ private: * The result is a 2D area that would cause nodes of given radius to * collide with the model or be inside a hole. * A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall. + * minimum xy distance is always used. * \param radius The radius of the node of interest * \param layer_idx The layer of interest * \param min_xy_dist Is the minimum xy distance used. * \return Polygons object */ - const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const; + const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx) const; /*! * \brief Round \p radius upwards to either a multiple of m_radius_sample_resolution or a exponentially increasing value * * \param radius The radius of the node of interest */ - coord_t ceilRadius(coord_t radius) const; + coord_t ceilRadius(const coord_t radius) const; /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. @@ -380,18 +398,8 @@ private: * collide with the model. Result is saved in the cache. * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. */ - void calculateCollision(std::deque keys); - /*! - * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. - * - * The result is a 2D area that would cause nodes of given radius to - * collide with the model. Result is saved in the cache. - * \param key RadiusLayerPairs the requested areas. The radius will be calculated up to the provided layer. - */ - void calculateCollision(RadiusLayerPair key) - { - calculateCollision(std::deque{ RadiusLayerPair(key) }); - } + void calculateCollision(const std::vector &keys); + void calculateCollision(const coord_t radius, const LayerIndex max_layer_idx); /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed. * @@ -400,7 +408,7 @@ private: * A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall. * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. */ - void calculateCollisionHolefree(std::deque keys); + void calculateCollisionHolefree(const std::vector &keys); /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed. @@ -412,7 +420,7 @@ private: */ void calculateCollisionHolefree(RadiusLayerPair key) { - calculateCollisionHolefree(std::deque{ RadiusLayerPair(key) }); + calculateCollisionHolefree(std::vector{ RadiusLayerPair(key) }); } /*! @@ -422,7 +430,7 @@ private: * collide with the model. Result is saved in the cache. * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. */ - void calculateAvoidance(std::deque keys); + void calculateAvoidance(const std::vector &keys, bool to_build_plate, bool to_model); /*! * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model. @@ -431,9 +439,9 @@ private: * collide with the model. Result is saved in the cache. * \param key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer. */ - void calculateAvoidance(RadiusLayerPair key) + void calculateAvoidance(RadiusLayerPair key, bool to_build_plate, bool to_model) { - calculateAvoidance(std::deque{ RadiusLayerPair(key) }); + calculateAvoidance(std::vector{ RadiusLayerPair(key) }, to_build_plate, to_model); } /*! @@ -441,38 +449,16 @@ private: * Result is saved in the cache. * \param key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer. */ - void calculatePlaceables(RadiusLayerPair key) - { - calculatePlaceables(std::deque{ key }); - } + void calculatePlaceables(const coord_t radius, const LayerIndex max_required_layer); + /*! * \brief Creates the areas where a branch of a given radius can be placed on the model. * Result is saved in the cache. * \param keys RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. */ - void calculatePlaceables(std::deque keys); + void calculatePlaceables(const std::vector &keys); - /*! - * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model without being able to place a branch with given radius on a single layer. - * - * The result is a 2D area that would cause nodes of radius \p radius to - * collide with the model in a not wanted way. Result is saved in the cache. - * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. - */ - void calculateAvoidanceToModel(std::deque keys); - - /*! - * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model without being able to place a branch with given radius on a single layer. - * - * The result is a 2D area that would cause nodes of radius \p radius to - * collide with the model in a not wanted way. Result is saved in the cache. - * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. - */ - void calculateAvoidanceToModel(RadiusLayerPair key) - { - calculateAvoidanceToModel(std::deque{ RadiusLayerPair(key) }); - } /*! * \brief Creates the areas that can not be passed when expanding an area downwards. As such these areas are an somewhat abstract representation of a wall (as in a printed object). * @@ -480,7 +466,7 @@ private: * * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. */ - void calculateWallRestrictions(std::deque keys); + void calculateWallRestrictions(const std::vector &keys); /*! * \brief Creates the areas that can not be passed when expanding an area downwards. As such these areas are an somewhat abstract representation of a wall (as in a printed object). @@ -489,7 +475,7 @@ private: */ void calculateWallRestrictions(RadiusLayerPair key) { - calculateWallRestrictions(std::deque{ RadiusLayerPair(key) }); + calculateWallRestrictions(std::vector{ RadiusLayerPair(key) }); } /*! @@ -556,9 +542,9 @@ private: */ std::vector m_anti_overhang; /*! - * \brief Radii that can be ignored by ceilRadius as they will never be requested. + * \brief Radii that can be ignored by ceilRadius as they will never be requested, sorted. */ - std::unordered_set m_ignorable_radii; + std::vector m_ignorable_radii; /*! * \brief Smallest radius a branch can have. This is the radius of a SupportElement with DTT=0. @@ -581,18 +567,39 @@ private: * \brief Caches to avoid holes smaller than the radius until which the radius is always increased, as they are free of holes. * Also called safe avoidances, as they are safe regarding not running into holes. */ - RadiusLayerPolygonCache m_avoidance_cache_holefree; - RadiusLayerPolygonCache m_avoidance_cache_holefree_to_model; + RadiusLayerPolygonCache m_avoidance_cache_holefree; + RadiusLayerPolygonCache m_avoidance_cache_holefree_to_model; + + RadiusLayerPolygonCache& avoidance_cache(const AvoidanceType type, const bool to_model) { + if (to_model) { + switch (type) { + case AvoidanceType::Fast: return m_avoidance_cache_to_model; + case AvoidanceType::Slow: return m_avoidance_cache_to_model_slow; + case AvoidanceType::FastSafe: return m_avoidance_cache_holefree_to_model; + } + } else { + switch (type) { + case AvoidanceType::Fast: return m_avoidance_cache; + case AvoidanceType::Slow: return m_avoidance_cache_slow; + case AvoidanceType::FastSafe: return m_avoidance_cache_holefree; + } + } + assert(false); + return m_avoidance_cache; + } + const RadiusLayerPolygonCache& avoidance_cache(const AvoidanceType type, const bool to_model) const { + return const_cast(this)->avoidance_cache(type, to_model); + } /*! * \brief Caches to represent walls not allowed to be passed over. */ - RadiusLayerPolygonCache m_wall_restrictions_cache; + RadiusLayerPolygonCache m_wall_restrictions_cache; // A different cache for min_xy_dist as the maximal safe distance an influence area can be increased(guaranteed overlap of two walls in consecutive layer) // is much smaller when min_xy_dist is used. This causes the area of the wall restriction to be thinner and as such just using the min_xy_dist wall // restriction would be slower. - RadiusLayerPolygonCache m_wall_restrictions_cache_min; + RadiusLayerPolygonCache m_wall_restrictions_cache_min; #ifdef SLIC3R_TREESUPPORTS_PROGRESS std::unique_ptr m_critical_progress { std::make_unique() }; diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 2d7debea9..0bcab9318 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -874,9 +874,9 @@ static std::optional> polyline_sample_next_point_at_dis if (area(expoly) <= 0.) ::MessageBoxA(nullptr, "TreeSupport infill negative area", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); #endif // _WIN32 - assert(intersecting_edges(expoly).empty()); + assert(intersecting_edges(to_polygons(expoly)).empty()); #ifdef _WIN32 - if (! intersecting_edges(expoly).empty()) + if (! intersecting_edges(to_polygons(expoly)).empty()) ::MessageBoxA(nullptr, "TreeSupport infill self intersections", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); #endif // _WIN32 Surface surface(stInternal, std::move(expoly)); @@ -1926,13 +1926,13 @@ void TreeSupport::increaseAreas(std::unordered_map& to constexpr bool increase_radius = true, no_error = true, use_min_radius = true, move = true; // aliases for better readability // Determine in which order configurations are checked if they result in a valid influence area. Check will stop if a valid area is found - std::deque order; + std::vector order; auto insertSetting = [&](AreaIncreaseSettings settings, bool back) { if (std::find(order.begin(), order.end(), settings) == order.end()) { if (back) order.emplace_back(settings); else - order.emplace_front(settings); + order.insert(order.begin(), settings); } }; @@ -1973,7 +1973,7 @@ void TreeSupport::increaseAreas(std::unordered_map& to if (elem.use_min_xy_dist) { - std::deque new_order; + std::vector new_order; // if the branch currently has to use min_xy_dist check if the configuration would also be valid with the regular xy_distance before checking with use_min_radius (Only happens when Support Distance priority is z overrides xy ) for (AreaIncreaseSettings settings : order) { @@ -2444,15 +2444,16 @@ void TreeSupport::generateBranchAreas( // if larger area did not fix the problem, all parts off the nozzle path that do not contain the center point are removed, hoping for the best if (nozzle_path.size() > 1) { Polygons polygons_with_correct_center; - for (const ExPolygon &part : nozzle_path) { + for (ExPolygon &part : nozzle_path) { if (part.contains(elem->result_on_layer)) polygons_with_correct_center = union_(polygons_with_correct_center, part); else { // try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... Point from = elem->result_on_layer; - moveInside(part, from, 0); + Polygons &to = to_polygons(std::move(part)); + moveInside(to, from, 0); if ((elem->result_on_layer - from).cast().norm() < scaled(0.025)) - polygons_with_correct_center = union_(polygons_with_correct_center, part); + polygons_with_correct_center = union_(polygons_with_correct_center, to); } } // Increase the area again, to ensure the nozzle path when calculated later is very similar to the one assumed above. diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 041344375..d4e04ef64 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -37,17 +37,16 @@ #define SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL false #define SUPPORT_TREE_AVOID_SUPPORT_BLOCKER true -#define SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION true -#define SUPPORT_TREE_EXPONENTIAL_THRESHOLD 1000 -#define SUPPORT_TREE_EXPONENTIAL_FACTOR 1.5 -#define SUPPORT_TREE_PRE_EXPONENTIAL_STEPS 1 -#define SUPPORT_TREE_COLLISION_RESOLUTION 500 // Only has an effect if SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION is false namespace Slic3r { using LayerIndex = int; +static constexpr const double SUPPORT_TREE_EXPONENTIAL_FACTOR = 1.5; +static constexpr const coord_t SUPPORT_TREE_EXPONENTIAL_THRESHOLD = scaled(1. * SUPPORT_TREE_EXPONENTIAL_FACTOR); +static constexpr const coord_t SUPPORT_TREE_COLLISION_RESOLUTION = scaled(0.5); + //FIXME class Print; class PrintObject; From bfbfdaedb9362d1a8697c06fa310079af88a4e90 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 23 Aug 2022 12:04:18 +0200 Subject: [PATCH 29/29] WIP TreeSupports: Fixed some compiler warnings and errors. --- src/libslic3r/TreeModelVolumes.hpp | 2 ++ src/libslic3r/TreeSupport.cpp | 12 +++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index 18765be58..efcdc54ae 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -575,12 +575,14 @@ private: switch (type) { case AvoidanceType::Fast: return m_avoidance_cache_to_model; case AvoidanceType::Slow: return m_avoidance_cache_to_model_slow; + case AvoidanceType::Count: assert(false); case AvoidanceType::FastSafe: return m_avoidance_cache_holefree_to_model; } } else { switch (type) { case AvoidanceType::Fast: return m_avoidance_cache; case AvoidanceType::Slow: return m_avoidance_cache_slow; + case AvoidanceType::Count: assert(false); case AvoidanceType::FastSafe: return m_avoidance_cache_holefree; } } diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 0bcab9318..c91824c66 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -186,7 +186,9 @@ static std::vector &overhangs, const TreeSupport::TreeSupportSettings &config, const std::vector &object_ids, TreeModelVolumes &volumes) +[[nodiscard]] static LayerIndex precalculate(const Print &print, const std::vector &overhangs, const TreeSupport::TreeSupportSettings &config, const std::vector &object_ids, TreeModelVolumes &volumes) { // calculate top most layer that is relevant for support LayerIndex max_layer = 0; @@ -2450,7 +2452,7 @@ void TreeSupport::generateBranchAreas( else { // try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... Point from = elem->result_on_layer; - Polygons &to = to_polygons(std::move(part)); + Polygons to = to_polygons(std::move(part)); moveInside(to, from, 0); if ((elem->result_on_layer - from).cast().norm() < scaled(0.025)) polygons_with_correct_center = union_(polygons_with_correct_center, to);